C++ Class 类详解

78次阅读
没有评论

本文系统讲解 C++ 类(Class)的语法、特性与最佳实践,并提供可运行示例与构建指令。面向 C++17/20 读者,重点涵盖封装、构造/析构、拷贝/移动、继承与多态、静态与友元、运算符重载、模板与现代特性,以及内存管理与异常安全。

目录

  • 类基础与语法
  • 构造与析构;拷贝控制与移动语义
  • 继承与多态
  • 静态成员与友元
  • 运算符重载
  • 模板类与 CRTP(概览)
  • 现代 C++ 特性要点
  • 内存管理与异常安全(RAII、智能指针、规则)
  • 可运行示例与构建
  • 最佳实践与常见陷阱

类基础与语法

  • 概念:类是用户自定义类型,聚合数据与行为,实现封装与抽象;对象是类的实例。
  • 访问控制:public(对外接口)、protected(对子类)、private(内部实现)。struct 默认 publicclass 默认 private
  • 成员:数据成员、成员函数、静态成员、类型别名、嵌套类型。
  • 作用域解析:用 ClassName::member 指定成员或静态成员作用域。
  • this 指针:非静态成员函数内指向当前对象。

示例:

class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}
    int x() const { return x_; }
    int y() const { return y_; }
    void move(int dx, int dy) { x_ += dx; y_ += dy; }

private:
    int x_;
    int y_;
};

构造与析构;拷贝控制与移动语义

  • 构造函数:默认、有参、委托、聚合初始化(POD)。优先使用成员初始化列表,尤其是 const/引用成员。
  • 析构函数:负责资源清理;多态基类应有虚析构。
  • =default / =delete:显式请求或禁止编译器合成特殊成员函数。
  • 拷贝构造/赋值:深拷贝 vs 浅拷贝;明确所有权策略。
  • 移动构造/赋值:从临时或源对象转移资源,提升性能;常标注 noexcept
  • 规则:
    • Rule of Three:自定义析构通常也要自定义拷贝构造与拷贝赋值。
    • Rule of Five:再加移动构造与移动赋值。
    • Rule of Zero:组合 + RAII(std::vector/std::unique_ptr 等)避免自定义上述函数。

资源句柄示例:

class File {
public:
    explicit File(const std::string& path) : path_(path), fd_(open(path.c_str(), O_RDONLY)) {}
    ~File() { if (fd_ >= 0) close(fd_); }

    File() = delete;                     // 禁止无参构造
    File(const File&) = delete;          // 禁止拷贝
    File& operator=(const File&) = delete;

    File(File&& other) noexcept : path_(std::move(other.path_)), fd_(other.fd_) { other.fd_ = -1; }
    File& operator=(File&& other) noexcept {
        if (this != &other) {
            if (fd_ >= 0) close(fd_);
            path_ = std::move(other.path_);
            fd_ = other.fd_;
            other.fd_ = -1;
        }
        return *this;
    }

private:
    std::string path_;
    int fd_{-1};
};

继承与多态

  • 继承:class Derived : public Base {}public 继承保持对外接口语义。
  • 虚函数:启用动态绑定,通过基类指针/引用调用派生实现。
  • 纯虚函数:接口类(抽象类),virtual void f() = 0;
  • override/final:防止签名不匹配或继续重写。
  • 虚析构:抽象或多态基类必须 virtual ~Base() = default;

示例:

struct Shape {
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

struct Circle : Shape {
    explicit Circle(double r) : r(r) {}
    double area() const override { return 3.14159265358979323846 * r * r; }
    double r;
};

静态成员与友元

  • 静态成员变量/函数:属于类本身而非对象;用于计数、工厂、共享配置。
  • 类内常量:static constexpr 提供编译期常量。
  • 友元(friend):授予访问私有成员的权限;谨慎使用以免破坏封装。

示例:

class Counter {
public:
    static int next() { return ++count_; }
private:
    static int count_;
};
int Counter::count_ = 0;

运算符重载

  • 成员 vs 非成员:一元或修改左操作数的通常成员;对称二元(如 operator+)常用非成员以支持隐式转换与对称性。
  • 一致性:语义应与内置类型一致;遵循无惊讶原则。
  • 比较与三路:C++20 operator<=> 简化比较,配合 = default 自动生成。

示例:

class Vec2 {
public:
    double x{}, y{};
    Vec2(double x, double y) : x(x), y(y) {}
    Vec2& operator+=(const Vec2& rhs) { x += rhs.x; y += rhs.y; return *this; }
    friend Vec2 operator+(Vec2 lhs, const Vec2& rhs) { lhs += rhs; return lhs; }
    auto operator<=>(const Vec2&) const = default;
};

模板类与 CRTP(概览)

  • 类模板:通过类型参数化构建通用类。
  • 特化:偏特化/全特化为特定类型提供实现。
  • CRTP:编译期“多态”,以模板实现静态分发。
  • 概念(C++20):为模板参数添加契约,提升错误信息与可读性。

示例:

template <typename T>
class Box {
public:
    explicit Box(T value) : value_(std::move(value)) {}
    const T& get() const { return value_; }
private:
    T value_;
};

现代 C++ 特性要点

  • explicit:避免意外隐式转换(单参构造尤需)。
  • override/final:明确重写关系,防止静默失败。
  • noexcept:声明不抛异常,影响优化与异常安全。
  • =default / =delete:控制特殊成员函数的合成与禁用。
  • [[nodiscard]]:防止忽略关键返回值。

内存管理与异常安全

  • RAII:以对象生命周期管理资源;构造获取、析构释放。
  • 智能指针:std::unique_ptr(独占)、std::shared_ptr(共享)、std::weak_ptr(观测)。
  • 异常安全:提供强保证/基本保证;优先使用不抛异常的移动;拷贝-交换习惯用法。
  • 拷贝消除与 RVO:鼓励以值返回,减少不必要拷贝。

可运行示例与构建

文件结构建议:

include/geometry/vec2.hpp
src/vec2.cpp
main.cpp

include/geometry/vec2.hpp

#pragma once
#include 
<compare>

namespace geometry {
class Vec2 {
public:
    Vec2() = default;
    Vec2(double x, double y);
    double x() const;
    double y() const;
    void move(double dx, double dy);
    Vec2& operator+=(const Vec2& rhs);
    friend Vec2 operator+(Vec2 lhs, const Vec2& rhs);
    auto operator<=>(const Vec2&) const = default;

private:
    double x_{}, y_{};
};
} // namespace geometry

src/vec2.cpp

#include "geometry/vec2.hpp"

namespace geometry {
Vec2::Vec2(double x, double y) : x_(x), y_(y) {}
double Vec2::x() const { return x_; }
double Vec2::y() const { return y_; }
void Vec2::move(double dx, double dy) { x_ += dx; y_ += dy; }
Vec2& Vec2::operator+=(const Vec2& rhs) { x_ += rhs.x_; y_ += rhs.y_; return *this; }
Vec2 operator+(Vec2 lhs, const Vec2& rhs) { lhs += rhs; return lhs; }
} // namespace geometry

main.cpp

#include 
<iostream>
#include "geometry/vec2.hpp"

int main() {
    geometry::Vec2 a(1.0, 2.0), b(3.0, 4.0);
    auto c = a + b;
    c.move(-1.0, 0.5);
    std::cout << c.x() << "," << c.y() << "\n";
    return 0;
}

构建与运行(macOS zsh,使用 clang++g++):

mkdir -p build
clang++ -std=gnu++20 -Iinclude src/vec2.cpp main.cpp -o build/app
./build/app

最佳实践与常见陷阱

  • 封装边界:数据成员尽量 private,最小化对外接口。
  • 显式语义:使用 explicitoverridefinalnoexcept[[nodiscard]]
  • 组合优于继承:继承仅用于明确的 is-a 关系抽象。
  • 倾向 Rule of Zero:资源交由标准容器/智能指针管理。
  • 头/源分离:减少编译耦合与 ODR 问题。
  • 避免裸资源:不直接管理裸指针/文件句柄,使用 RAII 包装。

常见陷阱:

  • 对象切片:以值方式将派生赋给基类对象丢失派生部分;使用引用/指针。
  • 隐式生成:编译器合成默认/拷贝/赋值/移动/析构;用 =delete/=default 明确意图。
  • 虚析构缺失:通过基类指针删除派生对象导致未定义行为;务必 virtual ~Base()
  • 初始化顺序:成员初始化按声明顺序而非列表顺序;依赖成员调整声明顺序。
  • 悬挂引用:返回局部对象引用/指针会悬空;返回值或智能指针。
  • 异常安全漏洞:赋值中先释放后分配失败导致资源丢失;使用拷贝-交换或强保证策略。

正文完
 0
评论(没有评论)

YanQS's Blog