学会到精通C++:面向对象编程精髓 - 类与对象 (5/100)

核心目标:深入理解C++面向对象编程的核心机制,掌握类设计原则与对象生命周期管理,为构建健壮、高效的软件系统奠定基础。

一、面向对象编程核心思想

1. 类与对象的关系

  • 类 (Class):用户自定义的抽象数据类型蓝图
  • 对象 (Object):类的具体实例(内存中的实体)
// 类定义:汽车蓝图
class Car {
    // 成员变量(属性)
    std::string brand;
    int year;
    double fuelLevel;
    
    // 成员函数(方法)
    void accelerate();
    void refuel(double amount);
};

// 对象实例化:具体汽车
Car myCar;      // 栈上对象
Car* companyCar = new Car();  // 堆上对象

2. OOP三大支柱

  1. 封装 (Encapsulation):隐藏实现细节,暴露接口
  2. 继承 (Inheritance):创建层次化的类关系(下篇重点)
  3. 多态 (Polymorphism):同一接口不同实现(下下篇重点)

二、类设计深度解析

1. 访问控制与封装

访问修饰符 类内 派生类 类外
private
protected
public

最佳实践

  • 数据成员通常设为private
  • 提供public接口进行受控访问
  • 使用protected为派生类保留扩展点
class BankAccount {
private:
    double balance; // 封装数据

public:
    // 受控访问接口
    double getBalance() const { return balance; }
    void deposit(double amount) { 
        if(amount > 0) balance += amount; 
    }
};

2. this指针机制

  • 每个非静态成员函数隐含的this参数
  • 指向调用该成员函数的对象
  • 底层实现:编译器将obj.method()转换为method(&obj)
class Counter {
    int count = 0;
public:
    void increment() {
        this->count++; // 显式使用this
        // 等效于 count++;
    }
    
    Counter* getAddress() {
        return this; // 返回当前对象地址
    }
};

三、构造函数与对象初始化

1. 构造函数类型

类型 特点 调用时机
默认构造函数 无参数 Car c;
参数化构造函数 带参数 Car c("Toyota", 2020);
拷贝构造函数 const T&参数 Car c2 = c1;
移动构造函数 T&&参数 (C++11) Car c3 = std::move(c1);

2. 成员初始化列表(关键!)

class Engine {
public:
    Engine(int hp) : horsepower(hp) {} // 正确初始化
private:
    const int horsepower; // const成员必须初始化
};

class Car {
public:
    // 使用初始化列表
    Car(std::string br, int yr, int hp) 
        : brand(br), year(yr), engine(hp)  // 效率更高
    {
        // 构造函数体
    }
    
private:
    std::string brand;
    int year;
    Engine engine; // 成员对象
};

必须使用初始化列表的场景

  1. const成员变量
  2. 引用成员变量
  3. 没有默认构造函数的成员对象
  4. 基类初始化(继承时)

3. 委托构造函数 (C++11)

class Rectangle {
public:
    // 主构造函数
    Rectangle(int w, int h) : width(w), height(h) {}
    
    // 委托给主构造函数
    Rectangle() : Rectangle(10, 10) {} 
    
    // 正方形委托
    Rectangle(int size) : Rectangle(size, size) {}
};

四、析构函数与资源管理

1. 析构函数特性

  • 名称:~ClassName()
  • 无参数、无返回值
  • 负责释放对象占用的资源
  • 调用时机:对象生命周期结束时
class FileHandler {
public:
    FileHandler(const char* filename) 
        : file(fopen(filename, "r")) 
    {
        if(!file) throw std::runtime_error("File open failed");
    }
    
    ~FileHandler() {
        if(file) fclose(file); // RAII资源释放
    }
    
private:
    FILE* file;
};

2. RAII (Resource Acquisition Is Initialization)

  • 核心思想:资源获取即初始化
  • 资源生命周期绑定对象生命周期
  • 使用智能指针(后续详解)实现自动管理

五、特殊成员函数与规则

1. Rule of Three/Five/Zero

规则 适用版本 需定义函数
Rule of Three C++98 析构函数、拷贝构造、拷贝赋值
Rule of Five C++11 增加移动构造、移动赋值
Rule of Zero 现代C++ 使用智能指针/容器管理资源
// Rule of Five 示例
class DynamicArray {
public:
    // 1. 构造函数
    DynamicArray(size_t size) : size(size), data(new int[size]) {}
    
    // 2. 析构函数
    ~DynamicArray() { delete[] data; }
    
    // 3. 拷贝构造函数
    DynamicArray(const DynamicArray& other)
        : size(other.size), data(new int[other.size]) 
    {
        std::copy(other.data, other.data + size, data);
    }
    
    // 4. 拷贝赋值运算符
    DynamicArray& operator=(const DynamicArray& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            std::copy(other.data, other.data + size, data);
        }
        return *this;
    }
    
    // 5. 移动构造函数 (C++11)
    DynamicArray(DynamicArray&& other) noexcept
        : size(other.size), data(other.data) 
    {
        other.size = 0;
        other.data = nullptr;
    }
    
    // 6. 移动赋值运算符 (C++11)
    DynamicArray& operator=(DynamicArray&& other) noexcept {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = other.data;
            other.size = 0;
            other.data = nullptr;
        }
        return *this;
    }

private:
    size_t size;
    int* data;
};

2. = default= delete

class DefaultExample {
public:
    DefaultExample() = default; // 显式使用默认实现
    ~DefaultExample() = default;
    
    // 禁用拷贝
    DefaultExample(const DefaultExample&) = delete;
    DefaultExample& operator=(const DefaultExample&) = delete;
    
    // 禁用移动
    DefaultExample(DefaultExample&&) = delete;
    DefaultExample& operator=(DefaultExample&&) = delete;
};

六、静态成员与类级操作

1. 静态成员变量

  • 类范围内共享的变量
  • 必须在类外单独定义(除C++17内联静态变量)
class Employee {
public:
    Employee() { ++count; }
    ~Employee() { --count; }
    
    static int getCount() { return count; }

private:
    static int count; // 声明
};

int Employee::count = 0; // 类外定义

2. 静态成员函数

  • this指针
  • 只能访问静态成员
  • 可通过类名直接调用
class MathUtils {
public:
    static double pi() { return 3.1415926535; }
    static int max(int a, int b) { return a > b ? a : b; }
};

// 使用
double circleArea = MathUtils::pi() * radius * radius;

3. 单例模式实现(现代线程安全版)

class Database {
public:
    // 删除拷贝和移动操作
    Database(const Database&) = delete;
    Database& operator=(const Database&) = delete;
    Database(Database&&) = delete;
    Database& operator=(Database&&) = delete;
    
    // 获取单例实例
    static Database& instance() {
        static Database db; // C++11保证线程安全
        return db;
    }
    
    void connect() { /* ... */ }
    void query(const std::string& sql) { /* ... */ }

private:
    Database() { /* 私有构造函数 */ }
    ~Database() = default;
};

// 使用
Database::instance().connect();

七、类与结构体的区别

特性 class struct
默认访问权限 private public
使用场景 有行为的对象 数据聚合(POD类型)
继承默认 private继承 public继承
模板元编程 常用 较少使用

现代C++建议

  • 需要方法/私有状态时用class
  • 纯数据集合时用struct
  • 与C兼容的数据结构用struct

八、类设计最佳实践

  1. 遵循单一职责原则:每个类只负责一件事
  2. 优先组合而非继承:降低耦合度
  3. 使用const正确性
    class Vector {
    public:
        // const成员函数:承诺不修改对象状态
        int size() const { return size_; } 
        
        // 非const成员函数
        void push_back(int value) { /* ... */ }
    };
    
  4. 明智使用mutable
    class Cache {
        mutable std::mutex mtx; // 可在const函数中修改
        mutable std::vector<int> cachedData;
        
    public:
        int getData() const {
            std::lock_guard<std::mutex> lock(mtx); // 需要修改mutex
            if (cachedData.empty()) {
                // 填充缓存...
            }
            return cachedData.back();
        }
    };
    

九、对象生命周期与内存管理

1. 对象创建位置

位置 生命周期管理 访问方式
栈 (Stack) 自动销毁 直接访问
堆 (Heap) 手动管理(new/delete) 指针访问
静态存储区 程序运行期 全局/静态

2. 对象数组

// 栈上对象数组
Car parkingLot[10]; 

// 堆上对象数组
Car* fleet = new Car[50]; 
delete[] fleet; // 必须使用delete[]

十、实战:设计一个现代字符串类

class MyString {
public:
    // 构造函数
    MyString() : data(nullptr), length(0) {}
    explicit MyString(const char* str);
    
    // 规则五
    ~MyString();
    MyString(const MyString& other);
    MyString(MyString&& other) noexcept;
    MyString& operator=(const MyString& other);
    MyString& operator=(MyString&& other) noexcept;
    
    // 成员函数
    size_t size() const { return length; }
    const char* c_str() const { return data ? data : ""; }
    
private:
    char* data;
    size_t length;
    
    void copyFrom(const char* src, size_t len);
};

// 实现略(见完整代码示例)

总结与进阶方向

关键知识点

  1. 类设计与封装原则
  2. 构造函数与初始化列表
  3. 析构函数与RAII模式
  4. 特殊成员函数与规则五
  5. 静态成员与单例模式
  6. this指针机制
  7. 对象生命周期管理

性能提示

  • 优先使用初始化列表
  • 移动语义减少拷贝开销
  • 小对象在栈上创建
  • noexcept移动操作

下篇预告:第6篇《继承的艺术:构建类层次结构》

  • 继承类型与访问控制
  • 虚函数机制与vtable
  • 抽象类与接口设计
  • 多重继承与钻石问题

“C++中的类不是一种抽象机制,而是一种具体化机制。” —— Bjarne Stroustrup

练习任务

  1. 实现完整的MyString类(含移动语义)
  2. 设计线程安全的日志类(单例模式)
  3. 创建表示几何图形的类层次(为继承铺垫)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐