C++:类(通识版)

类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。

数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。

封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。

类要想实现数据抽象和封装,需要首先定义一个抽象数据类型(abstract data type)。在抽象数据类型中,由类的设计者负责考虑类的实现过程;使用该类的程序员则只需要抽象地思考类型做了什么,而无须了解类型的工作细节。

--引用自《C++ Primer》

一、如何去定义一个抽象类 

在C++中定义抽象数据类型(Abstract Data Type, ADT)​的核心是通过类(class)将数据和对数据的操作封装起来,并对外仅暴露接口(API),隐藏实现细节。


1、ADT的设计原则

  1. 接口与实现分离:用户只能通过公共成员函数(接口)操作数据,无法直接访问数据成员。
  2. 数据保护:所有数据成员设为private,避免外部直接修改。
  3. 完整性:接口应覆盖所有可能的操作,确保用户无需了解内部实现。

2、定义ADT的步骤

1. ​确定ADT的功能需求

栈(Stack)​为例,需支持以下操作:

  • 压栈(push):将元素添加到栈顶。
  • 弹栈(pop):移除栈顶元素。
  • 获取栈顶元素(top)。
  • 判断栈是否为空(isEmpty)。
  • 获取栈大小(size)。
2. ​设计公共接口(API)​

在头文件(.h)中声明类,仅暴露必要的接口:

// Stack.h
#ifndef STACK_H
#define STACK_H

class Stack {
public:
    Stack();                         // 默认构造函数
    ~Stack();                        // 析构函数
    void push(int value);           // 压栈
    void pop();                      // 弹栈
    int top() const;                 // 获取栈顶元素
    bool isEmpty() const;           // 判断栈是否为空
    int size() const;               // 获取栈大小

private:
    int* data_;                      // 存储栈元素的数组
    int capacity_;                   // 栈的容量
    int topIndex_;                   // 栈顶索引
    void resize(int newCapacity);    // 调整栈容量(内部使用)
};

#endif
3. ​实现私有成员和内部逻辑

在源文件(.cpp)中定义具体实现,隐藏细节:

// Stack.cpp
#include "Stack.h"
#include  // 用于抛出异常

Stack::Stack() : data_(new int[4]), capacity_(4), topIndex_(-1) {}

Stack::~Stack() {
    delete[] data_; // 释放动态内存
}

void Stack::push(int value) {
    if (topIndex_ == capacity_ - 1) {
        resize(capacity_ * 2); // 容量不足时扩容
    }
    data_[++topIndex_] = value;
}

void Stack::pop() {
    if (isEmpty()) {
        throw std::out_of_range("Stack is empty!");
    }
    topIndex_--;
}

int Stack::top() const {
    if (isEmpty()) {
        throw std::out_of_range("Stack is empty!");
    }
    return data_[topIndex_];
}

bool Stack::isEmpty() const {
    return topIndex_ == -1;
}

int Stack::size() const {
    return topIndex_ + 1;
}

void Stack::resize(int newCapacity) {
    int* newData = new int[newCapacity];
    for (int i = 0; i <= topIndex_; i++) {
        newData[i] = data_[i];
    }
    delete[] data_;
    data_ = newData;
    capacity_ = newCapacity;
}

3、ADT的关键技术点

1. ​封装数据成员
  • 所有数据成员(data_capacity_topIndex_)设为private,防止外部直接访问。
  • 用户只能通过pushpop等公共方法操作栈。
2. ​资源管理
  • 构造函数:初始化动态数组和容量。
  • 析构函数:释放动态内存,避免内存泄漏。
  • 拷贝控制:禁用默认拷贝和赋值(防止浅拷贝)。
    // 在类声明中添加以下代码(C++11+)
    Stack(const Stack&) = delete;            // 禁用拷贝构造
    Stack& operator=(const Stack&) = delete; // 禁用赋值运算符
3. ​异常处理
  • poptop中检查栈是否为空,抛出std::out_of_range异常。
  • 用户需使用try-catch处理异常:
    #include 
    int main() {
        Stack s;
        try {
            s.push(10);
            std::cout << s.top() << std::endl; // 输出10
            s.pop();
            s.pop(); // 抛出异常
        } catch (const std::exception& e) {
            std::cerr << "Error: " << e.what() << std::endl;
        }
        return 0;
    }
4. ​动态调整容量
  • 私有方法resize在栈满时自动扩容,用户无需关心内部扩容逻辑。

4、使用ADT的示例代码

用户只需包含头文件,调用公共接口:

#include "Stack.h"
#include 

int main() {
    Stack s;
    s.push(1);
    s.push(2);
    s.push(3);
    std::cout << "Top: " << s.top() << std::endl; // 3
    std::cout << "Size: " << s.size() << std::endl; // 3
    s.pop();
    std::cout << "Top after pop: " << s.top() << std::endl; // 2
    return 0;
}

5、ADT的扩展与优化

  1. 模板化(支持泛型)​
    template 
    class Stack {
    public:
        void push(const T& value);
        // ... 其他成员
    private:
        T* data_;
    };
  2. 使用智能指针管理内存​(C++11+):
    #include 
    private:
        std::unique_ptr data_;
  3. 移动语义(C++11+)​
    Stack(Stack&& other) noexcept;            // 移动构造函数
    Stack& operator=(Stack&& other) noexcept; // 移动赋值运算符

6、小结

定义抽象数据类型的关键步骤:

  1. 设计清晰的公共接口:覆盖所有用户操作需求。
  2. 隐藏实现细节:数据成员和辅助函数设为private
  3. 管理资源:构造函数分配资源,析构函数释放资源。
  4. 异常安全:检查非法操作并抛出异常。
  5. 扩展性:通过模板、智能指针等现代特性增强功能。

二、this关键字和const成员函数 

1、this关键字:​指向当前对象的指针

1. ​作用
  • this 是一个隐式指针,指向当前对象的地址。
  • 用途
    • 区分成员变量与局部变量(尤其是同名时)。
    • 返回当前对象的引用(链式调用)。
    • 在成员函数中传递当前对象。
2. ​示例代码
class MyClass {
private:
    int value; // 成员变量

public:
    // 场景1:同名参数与成员变量区分
    void setValue(int value) {
        this->value = value; // 使用this访问成员变量
    }

    // 场景2:返回当前对象的引用(链式调用)
    MyClass& increment() {
        value++;
        return *this; // 返回当前对象的引用
    }

    // 场景3:比较当前对象与其他对象
    bool compare(const MyClass& other) {
        return this->value > other.value;
    }
};

int main() {
    MyClass obj1, obj2;
    obj1.setValue(5);
    obj1.increment().increment(); // 链式调用:value变为7
    bool result = obj1.compare(obj2); // 比较obj1和obj2
    return 0;
}

2、const成员函数:​保证不修改对象状态

1. ​作用
  • 在成员函数声明后添加 const,表示该函数不会修改类的成员变量。
  • 核心规则
    • const成员函数只能读取成员变量,不能修改。
    • const对象(如 const MyClass obj;)只能调用 const成员函数。
2. ​语法
class MyClass {
public:
    int getValue() const { // const成员函数
        return value;
    }
};
3. ​示例代码
class Counter {
private:
    int count;

public:
    Counter() : count(0) {}

    // 非const成员函数:可修改成员变量
    void increment() {
        count++;
    }

    // const成员函数:只能读取成员变量
    int getCount() const {
        return count;
    }

    // 错误示例:尝试在const成员函数中修改成员变量
    /*
    void reset() const {
        count = 0; // 编译错误!const函数不能修改成员变量
    }
    */
};

int main() {
    Counter c1;
    c1.increment();
    cout << c1.getCount(); // 输出1

    const Counter c2;
    // c2.increment(); // 错误!const对象只能调用const成员函数
    cout << c2.getCount(); // 合法,调用const函数
    return 0;
}

3、thisconst成员函数的关系

1. ​this指针的类型
  • 在非const成员函数中:MyClass* const this(指针本身不可变,指向的对象可变)。
  • const成员函数中:const MyClass* const this(指针和指向的对象都不可变)。
2. ​示例:thisconst
class MyClass {
public:
    void nonConstFunc() {
        this->value = 10; // 允许修改成员变量
    }

    void constFunc() const {
        // this->value = 20; // 错误!const成员函数中this是const指针
        int temp = this->value; // 允许读取成员变量
    }
};

4、const正确性设计原则

  1. 所有不修改成员变量的函数都应声明为const
    • 提高代码安全性,防止意外修改对象状态。
    • 允许const对象调用这些函数。
  2. 重载const和非const成员函数
    • 根据调用对象的const性选择不同实现。
    class Data {
    private:
        std::vector data;
    public:
        // 非const版本:允许修改数据
        std::vector& getData() { return data; }
    
        // const版本:返回const引用,防止修改
        const std::vector& getData() const { return data; }
    };

5、小结

  1. this关键字:
    • 访问当前对象成员,解决名称冲突。
    • 支持链式调用和对象间比较。
  2. const成员函数:
    • 保证函数不修改对象状态。
    • 允许const对象调用,增强代码安全性。
  3. 联合使用场景
    • const成员函数中,this是一个指向const对象的指针。
    • 通过const正确性设计,提升代码健壮性。

三、类作用域和成员函数 

1、类作用域(Class Scope)

类作用域是指类内定义的成员(变量和函数)的可见性范围。所有成员变量和成员函数都位于类的作用域内,需通过对象或类名(静态成员)访问。

1. ​类作用域的特点
  • 访问方式:成员变量和函数必须通过对象(.运算符)或指针(->运算符)访问。
  • 与全局作用域隔离:类内成员名不会与全局作用域的同名变量或函数冲突。
  • 访问控制publicprivateprotected 修饰符影响成员的可见性。
2. 示例:类作用域的隔离性
int value = 100; // 全局变量

class MyClass {
private:
    int value = 10; // 类作用域内的成员变量(与全局变量同名)
public:
    void print() {
        std::cout << value;          // 输出10(访问类内成员)
        std::cout << ::value;        // 输出100(通过::访问全局变量)
    }
};

2、成员函数(Member Functions)

成员函数是定义在类作用域内的函数,用于操作类对象的状态(成员变量)。

1. ​成员函数的定义方式
  • 类内定义:隐式内联(适合简单函数)。
    class Calculator {
    public:
        int add(int a, int b) { return a + b; }
    };
  • 类外定义:需使用作用域解析运算符 ::(适合复杂函数)。
    class Calculator {
    public:
        int multiply(int a, int b); // 声明
    };
    
    // 类外定义
    int Calculator::multiply(int a, int b) {
        return a * b;
    }
2. ​成员函数的访问控制
  • public成员函数:可被任何代码调用。
  • private/protected成员函数:只能在类内或友元中调用。
class BankAccount {
private:
    double balance;
    void logTransaction(const std::string& msg) { /* 记录日志 */ }
public:
    void deposit(double amount) {
        balance += amount;
        logTransaction("Deposit"); // 合法:类内调用私有函数
    }
};

int main() {
    BankAccount acc;
    acc.deposit(1000);
    // acc.logTransaction("Test"); // 错误!私有函数不可外部调用
}

3、类作用域与成员函数的交互

1. ​成员函数中的变量查找顺序

当成员函数访问变量时,按以下顺序查找:

  1. 局部作用域:函数内的局部变量。
  2. 类作用域:类的成员变量。
  3. 全局作用域:全局变量(需用 :: 显式访问)。
int x = 1; // 全局变量

class Test {
private:
    int x = 2; // 类作用域变量
public:
    void print(int x = 3) { // 参数x(局部作用域)
        std::cout << x;        // 3(局部变量)
        std::cout << this->x;  // 2(类成员变量)
        std::cout << ::x;      // 1(全局变量)
    }
};
2. ​静态成员函数与类作用域

静态成员函数属于类而非对象,因此:

  • 只能访问静态成员变量。
  • 无 this 指针。
#include 
using namespace std;

class Logger {
private:
    static int logCount; // 静态成员变量
public:
    static void incrementLog() {
        logCount++; // 合法:访问静态成员
    }
};

int Logger::logCount = 0; // 静态成员初始化

int main() {
    Logger::incrementLog(); // 合法:调用静态成员函数
    return 0;
}

4、类作用域与友元(Friend)

友元函数或类可以突破类作用域限制,访问私有成员,但仍需注意作用域规则。

class Secret {
private:
    int code = 42;
    friend void hack(Secret& s); // 声明友元函数
};

// 友元函数定义(全局作用域)
void hack(Secret& s) {
    std::cout << s.code; // 合法:友元可访问私有成员
}

5、关键总结

  1. 类作用域规则
    • 成员变量和函数需通过对象或类名访问。
    • 类内名称优先于全局名称。
  2. 成员函数的核心作用
    • 操作类对象的状态。
    • 受访问控制修饰符限制。
  3. 静态成员函数
    • 属于类作用域,无 this 指针。
  4. 友元
    • 突破封装,但需谨慎使用。

6、综合示例:学生类

#include 
#include 

class Student {
private:
    std::string name; // 类作用域变量
    int score;

public:
    // 构造函数
    Student(const std::string& n, int s) : name(n), score(s) {}

    // 类内定义的成员函数(隐式内联)
    void printBasicInfo() {
        std::cout << "Name: " << name << std::endl;
    }

    // 类外定义的成员函数
    void printGrade();

    // 静态成员函数
    static void printSchool() {
        std::cout << "MIT" << std::endl;
    }
};

// 类外定义成员函数
void Student::printGrade() {
    if (score >= 90) std::cout << "A";
    else std::cout << "B";
}

int main() {
    Student alice("Alice", 95);
    alice.printBasicInfo(); // 输出Name: Alice
    alice.printGrade();     // 输出A
    Student::printSchool(); // 输出MIT
    return 0;
}

四、非成员函数

1、什么是非成员函数?

非成员函数是指不属于任何类的函数,但与类有逻辑关联,用于操作或扩展类的功能
核心特点

  • 独立于类定义:在类外部定义,不属于类的成员。
  • 访问权限受限:默认无法访问类的私有成员(除非声明为友元)。
  • 灵活性:可封装通用逻辑,减少类之间的耦合。

2、非成员函数的应用场景

1. ​运算符重载​(如 <<+==

当运算符需要支持对称操作(如 a + b 与 b + a)时,非成员函数更灵活。

class Complex {
public:
    Complex(double real, double imag) : real_(real), imag_(imag) {}
    double real() const { return real_; }
    double imag() const { return imag_; }

private:
    double real_, imag_;
};

// 非成员函数:重载加法运算符
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real() + b.real(), a.imag() + b.imag());
}

// 使用
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2; // 调用operator+
2. ​流操作符重载​(如 << 输出)
#include 
// 非成员函数:重载<<运算符
std::ostream& operator<<(std::ostream& os, const Complex& c) {
    os << "(" << c.real() << ", " << c.imag() << ")";
    return os;
}

// 使用
std::cout << c3; // 输出 (4, 6)
3. ​工具函数​(无需访问私有成员)

提供与类相关的辅助功能,但无需依赖私有数据。

// 非成员工具函数:计算复数的模
double magnitude(const Complex& c) {
    return std::sqrt(c.real() * c.real() + c.imag() * c.imag());
}

// 使用
double mod = magnitude(c3);
4. ​工厂函数​(创建对象)

封装对象创建的复杂逻辑。

// 非成员工厂函数
Complex createFromPolar(double r, double theta) {
    return Complex(r * cos(theta), r * sin(theta));
}

// 使用
Complex c4 = createFromPolar(5.0, M_PI / 4);

3、非成员函数与友元函数

1. ​友元函数(Friend Functions)​
  • 声明在类内,赋予访问私有成员的权限。
  • 仍是非成员函数,但可突破封装限制。
class Complex {
    // 声明友元函数
    friend Complex operator*(const Complex& a, const Complex& b);
private:
    double real_, imag_;
};

// 友元函数可访问私有成员
Complex operator*(const Complex& a, const Complex& b) {
    return Complex(
        a.real_ * b.real_ - a.imag_ * b.imag_,
        a.real_ * b.imag_ + a.imag_ * b.real_
    );
}
2. ​何时使用友元?
  • 需要访问类的私有成员,但无法通过公有接口实现。
  • 慎用:过度使用友元会破坏封装性。

4、非成员函数 vs 成员函数

特性 非成员函数 成员函数
定义位置 类外部 类内部
访问权限 仅能访问公有成员(除非声明为友元) 可访问所有成员(包括私有)
运算符重载对称性 更好(如 a + b 和 b + a 需要第一个操作数是类的对象
封装性 高(减少类之间的依赖) 低(与类紧密绑定)

5、设计原则:优先使用非成员函数

  1. 降低类的复杂度:将不依赖类内部状态的逻辑移至非成员函数。
  2. 增强扩展性:非成员函数可独立修改,不影响类定义。
  3. 遵循单一职责原则:类负责核心数据,非成员函数负责辅助操作。

6、实际应用:日期类工具函数

class Date {
public:
    Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
    bool isLeapYear() const; // 成员函数
    int year() const { return year_; }
    int month() const { return month_; }
    int day() const { return day_; }

private:
    int year_, month_, day_;
};

// 非成员函数:判断两个日期是否连续
bool isConsecutive(const Date& a, const Date& b) {
    // 只能通过公有接口访问数据
    if (a.year() == b.year() && a.month() == b.month() && a.day() + 1 == b.day()) {
        return true;
    }
    // 处理跨月、跨年等复杂逻辑...
    return false;
}

7、小结

  1. 非成员函数的核心价值:解耦逻辑、增强复用性、支持运算符重载。
  2. 合理使用友元:仅在必要时赋予访问私有成员的权限。
  3. 设计准则:优先通过类的公有接口实现功能,必要时使用非成员函数。

五、构造函数

1、构造函数的基本概念

构造函数是类的一种特殊成员函数,用于初始化对象的状态(成员变量)。
核心特性

  • 命名:与类名完全相同。
  • 无返回值:不指定返回类型(包括void)。
  • 自动调用:创建对象时自动执行,无需手动调用。
  • 重载支持:可定义多个参数不同的构造函数。

2、构造函数的类型与用法

1. ​默认构造函数(Default Constructor)​
  • 定义:无参数,或所有参数都有默认值。
  • 作用:初始化对象为默认状态。
  • 编译器生成:若未定义任何构造函数,编译器自动生成空默认构造函数;否则需显式定义。
class MyClass {
public:
    MyClass() { // 默认构造函数
        value = 0;
        std::cout << "默认构造函数调用\n";
    }
private:
    int value;
};

int main() {
    MyClass obj; // 调用默认构造函数
    return 0;
}
2. ​参数化构造函数(Parameterized Constructor)​
  • 定义:接受参数的构造函数。
  • 作用:根据传入参数初始化对象。
class Point {
public:
    Point(int x, int y) { // 参数化构造函数
        this->x = x;
        this->y = y;
    }
private:
    int x, y;
};

int main() {
    Point p(3, 4); // 调用参数化构造函数
    return 0;
}
3. ​初始化列表(Initializer List)​
  • 语法:在构造函数参数后使用冒号初始化成员变量。
  • 优势:更高效,避免先默认初始化再赋值
  • 强制场景:初始化const成员、引用成员、没有默认构造函数的类成员。
class Circle {
public:
    Circle(double r, int color) 
        : radius(r), color(color) { // 初始化列表
        // 函数体可留空
    }
private:
    const double radius; // const成员必须用初始化列表
    int color;
};
4. ​拷贝构造函数(Copy Constructor)​
  • 定义:接受同类型对象的引用(通常为const引用)。
  • 作用:用现有对象初始化新对象。
  • 编译器生成:浅拷贝(若未定义,编译器自动生成)。
class String {
public:
    String(const String& other) { // 拷贝构造函数
        size = other.size;
        data = new char[size];
        std::memcpy(data, other.data, size);
    }
private:
    char* data;
    int size;
};
5. ​移动构造函数(Move Constructor, C++11+)​
  • 定义:接受右值引用(&&),转移资源而非复制。
  • 作用:优化临时对象资源管理,提升性能。
class String {
public:
    String(String&& other) noexcept // 移动构造函数
        : data(other.data), size(other.size) {
        other.data = nullptr; // 置空原对象指针
        other.size = 0;
    }
private:
    char* data;
    int size;
};
6. ​委托构造函数(Delegating Constructor, C++11+)​
  • 定义:一个构造函数调用同类的其他构造函数。
  • 作用:减少代码重复。
class Date {
public:
    Date() : Date(2024, 1, 1) {} // 委托给三参数构造函数
    Date(int year, int month, int day) 
        : year(year), month(month), day(day) {}
private:
    int year, month, day;
};

3、构造函数的调用规则

1. ​隐式调用与显式调用
  • 隐式调用:通过对象声明自动调用。
    MyClass obj; // 隐式调用默认构造函数
  • 显式调用:使用new或临时对象语法。
    MyClass* p = new MyClass(); // 显式调用
2. ​拷贝构造函数的触发场景
  • 对象作为函数参数按值传递。
  • 函数返回对象按值返回。
  • 用已有对象初始化新对象:
    String s1("Hello");
    String s2 = s1; // 调用拷贝构造函数
3. ​移动构造函数的触发场景
  • 使用std::move显式转移资源。
  • 返回临时对象(编译器优化为移动构造)。

4、构造函数的注意事项

  1. explicit关键字:禁止隐式类型转换。

    class MyClass {
    public:
        explicit MyClass(int x) { /* ... */ }
    };
    
    MyClass obj = 5; // 错误:禁止隐式转换
    MyClass obj(5);  // 正确:显式调用
  2. 深拷贝与浅拷贝:拷贝构造函数需处理动态内存,避免内存泄漏。

  3. 资源管理:构造函数中分配的资源应在析构函数中释放。


5、综合示例:动态数组类

#include 
#include 

class DynamicArray {
public:
    // 默认构造函数
    DynamicArray() : size(0), data(nullptr) {}

    // 参数化构造函数
    explicit DynamicArray(int size) : size(size) {
        data = new int[size];
        std::memset(data, 0, size * sizeof(int));
    }

    // 拷贝构造函数(深拷贝)
    DynamicArray(const DynamicArray& other) : size(other.size) {
        data = new int[size];
        std::memcpy(data, other.data, size * sizeof(int));
    }

    // 移动构造函数(C++11+)
    DynamicArray(DynamicArray&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }

    // 析构函数
    ~DynamicArray() {
        delete[] data;
    }

private:
    int* data;
    int size;
};

int main() {
    DynamicArray arr1(5);       // 参数化构造函数
    DynamicArray arr2 = arr1;   // 拷贝构造函数
    DynamicArray arr3 = std::move(arr1); // 移动构造函数
    return 0;
}

6、小结

  1. 构造函数是对象生命周期的起点:负责初始化成员变量和资源分配。
  2. 类型多样:默认构造、参数化构造、拷贝构造、移动构造各有适用场景。
  3. 初始化列表优先:提升效率,支持特殊成员初始化。
  4. 资源管理:构造函数与析构函数需配对设计,确保资源安全。

六、拷贝、赋值与析构

拷贝控制成员(拷贝构造函数、拷贝赋值运算符、析构函数)共同管理对象的资源生命周期,是编写健壮C++类的关键。


1、拷贝控制成员的核心作用

成员函数 触发场景 默认行为 核心职责
拷贝构造函数 用同类型对象初始化新对象 浅拷贝(成员逐一复制) 深拷贝资源,避免共享
拷贝赋值运算符 将同类型对象赋值给已有对象 浅拷贝 释放旧资源,深拷贝新值
析构函数 对象销毁时 无操作 释放所有分配的资源

2、拷贝构造函数(Copy Constructor)

1. 定义与语法
class MyClass {
public:
    // 拷贝构造函数(参数为const引用)
    MyClass(const MyClass& other) {
        // 深拷贝逻辑
    }
};
2. 触发场景
  • 显式拷贝初始化MyClass obj2(obj1); 或 MyClass obj2 = obj1;
  • 函数传参:按值传递对象时
  • 函数返回值:按值返回对象时
3. 深拷贝示例(动态数组)(沿用上例)
class DynamicArray {
public:
    int* data;
    int size;

    // 拷贝构造函数
    DynamicArray(const DynamicArray& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
    }
};

3、拷贝赋值运算符(Copy Assignment Operator)

1. 定义与语法
class MyClass {
public:
    // 拷贝赋值运算符
    MyClass& operator=(const MyClass& other) {
        if (this != &other) { // 处理自赋值
            // 释放旧资源 + 深拷贝新资源
        }
        return *this;
    }
};
2. 触发场景
  • 显式赋值obj2 = obj1;
  • 链式赋值obj3 = obj2 = obj1;
3. 深拷贝示例(动态数组)
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;
}

4、析构函数(Destructor)

1. 定义与语法
class MyClass {
public:
    ~MyClass() {
        // 释放资源(动态内存、文件句柄等)
    }
};
2. 触发场景
  • 对象离开作用域:如局部对象在函数结束时
  • 手动删除对象delete ptr;
  • 容器销毁:如vector析构时
3. 资源释放示例
~DynamicArray() {
    delete[] data; // 释放动态数组
    data = nullptr;
}

5、三法则(Rule of Three)

原则:如果一个类需要自定义拷贝构造函数、拷贝赋值运算符或析构函数中的任意一个,则通常需要自定义全部三个

示例场景:管理动态内存
class String {
private:
    char* data;
    size_t length;

public:
    // 1. 析构函数
    ~String() { delete[] data; }

    // 2. 拷贝构造函数
    String(const String& other) : length(other.length) {
        data = new char[length + 1];
        strcpy(data, other.data);
    }

    // 3. 拷贝赋值运算符
    String& operator=(const String& other) {
        if (this != &other) {
            delete[] data; // 关键:释放旧资源
            length = other.length;
            data = new char[length + 1];
            strcpy(data, other.data);
        }
        return *this;
    }
};

6、C++11扩展:五法则(Rule of Five)(3+2)

新增移动构造函数和移动赋值运算符,适用于资源转移优化:

class String {
public:
    // 移动构造函数
    String(String&& other) noexcept 
        : data(other.data), length(other.length) {
        other.data = nullptr; // 置空原对象
        other.length = 0;
    }

    // 移动赋值运算符
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            length = other.length;
            other.data = nullptr;
            other.length = 0;
        }
        return *this;
    }
};

7、关键设计原则

  1. 深拷贝 vs 浅拷贝
    • 浅拷贝:仅复制指针(默认行为),多个对象共享资源,易导致重复释放。
    • 深拷贝:复制指针指向的内容,每个对象独立拥有资源。
  2. 自赋值处理
    • 拷贝赋值运算符必须正确处理a = a的情况。
  3. 异常安全
    • 在拷贝赋值运算符中,先分配新资源再释放旧资源,避免资源泄漏。
  4. 移动语义优化
    • 对临时对象(右值)使用移动语义,避免不必要的深拷贝。

8、综合示例:动态数组类(优化了一下)

class DynamicArray {
public:
    // 构造函数
    DynamicArray(int size) : size(size), data(new int[size]) {}

    // 1. 析构函数
    ~DynamicArray() { delete[] data; }

    // 2. 拷贝构造函数
    DynamicArray(const DynamicArray& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
    }

    // 3. 拷贝赋值运算符
    DynamicArray& operator=(const DynamicArray& other) {
        if (this != &other) {
            int* newData = new int[other.size]; // 先分配新资源
            std::copy(other.data, other.data + other.size, newData);
            delete[] data; // 再释放旧资源
            data = newData;
            size = other.size;
        }
        return *this;
    }

private:
    int* data;
    int size;
};

9、小结

  1. 拷贝控制成员管理资源生命周期:确保深拷贝正确性,避免资源泄漏。
  2. 遵循三/五法则:当类持有资源时,必须自定义拷贝控制成员。
  3. 优先使用现代C++特性:如移动语义(C++11+)优化性能。
  4. 测试边界条件:如自赋值、空指针、异常安全等场景。

七、访问控制与封装

1、访问控制的基础

访问控制通过三个关键字实现:​publicprivateprotected,它们决定了类成员的可见性范围。

1. ​public成员
  • 访问权限:所有代码均可访问。
  • 用途:暴露类的接口(API),供外部调用。
2. ​private成员
  • 访问权限:仅类内部和友元(friend)可访问。
  • 用途:隐藏实现细节,保护数据完整性。
3. ​protected成员
  • 访问权限:类内部、派生类和友元可访问。
  • 用途:支持继承体系中的共享数据,但依然避免完全暴露。

2、封装的实现:访问控制的典型应用

示例:银行账户类
class BankAccount {
public:
    // 公有接口:暴露给用户的API
    BankAccount(const std::string& owner, double balance)
        : owner_(owner), balance_(balance) {}

    void deposit(double amount) {
        if (amount > 0) balance_ += amount;
    }

    bool withdraw(double amount) {
        if (amount > 0 && balance_ >= amount) {
            balance_ -= amount;
            return true;
        }
        return false;
    }

    double getBalance() const {
        return balance_;
    }

private:
    // 私有数据:外部无法直接操作
    std::string owner_;
    double balance_;
};

int main() {
    BankAccount acc("Alice", 1000);
    acc.deposit(500);          // 合法:调用公有接口
    // acc.balance_ = 10000;   // 错误!无法直接访问私有成员
    return 0;
}

关键点

  • depositwithdraw:通过公有接口控制对balance_的修改,确保业务规则(如金额有效性检查)。
  • getBalance:提供只读访问,防止数据被意外修改。

3、访问控制的规则与限制

1. ​类内默认访问权限
  • class关键字:默认成员为private
  • struct关键字:默认成员为public
class MyClass {     // 默认private
    int x;          // private
};

struct MyStruct {   // 默认public
    int y;          // public
};
2. ​友元(friend)突破封装
  • 作用:允许特定函数或类访问私有成员。
  • 慎用:仅在必要时使用(如运算符重载、测试类)。
class Secret {
private:
    int code = 42;
    friend void hackSecret(Secret& s); // 声明友元函数
};

// 友元函数可以访问私有成员
void hackSecret(Secret& s) {
    std::cout << s.code << std::endl; // 合法
}

4、封装的核心优势

  1. 数据保护:防止外部代码直接修改对象状态。
  2. 接口稳定性:公有接口不变时,内部实现可自由修改。
  3. 错误预防:通过接口验证输入,避免非法操作。

5、protected访问权限的合理使用

示例:继承中的共享数据
class Shape {
protected:
    // 派生类可访问,外部不可访问
    double x_, y_; // 形状的坐标
public:
    Shape(double x, double y) : x_(x), y_(y) {}
    virtual double area() const = 0; // 纯虚函数
};

class Circle : public Shape {
private:
    double radius_;
public:
    Circle(double x, double y, double r) 
        : Shape(x, y), radius_(r) {}
    double area() const override {
        return 3.14159 * radius_ * radius_;
    }
};

int main() {
    Circle c(0, 0, 5);
    // c.x_ = 10; // 错误!protected成员只能在派生类内部访问
    return 0;
}

6、封装与设计原则

  1. 最小暴露原则:只暴露必要的接口,尽可能减少公有成员。
  2. 使用Getter/Setter:对私有数据的访问提供可控入口。
    class Person {
    private:
        int age_;
    public:
        void setAge(int age) {
            if (age >= 0) age_ = age; // 验证逻辑
        }
        int getAge() const { return age_; }
    };
  3. 避免友元泛滥:过度使用friend会破坏封装性。

7、小结

  1. 访问控制是封装的技术基础:通过publicprivateprotected实现数据保护。
  2. 封装的核心目标:分离接口与实现,提升代码的健壮性和可维护性。
  3. 合理使用访问权限
    • public:定义用户交互的API。
    • private:隐藏实现细节和数据。
    • protected:支持继承体系中的共享逻辑。
  4. 现代C++扩展:结合const成员函数、移动语义等特性,构建更安全的类。

八、类间的友元关系

友元关系是C++中一种允许特定外部类或函数访问另一个类的私有(private)或保护(protected)成员的机制。通过friend关键字声明,友元能够突破封装性,适用于需要紧密协作的场景,但需谨慎使用以避免破坏代码结构。

1、友元的类型与语法(沿用上例)

  1. 友元函数

    • 非成员函数,可访问类的私有成员。
    • 声明示例
      class MyClass {
          friend void friendFunction(MyClass& obj); // 友元函数声明
      private:
          int secret;
      };
      
      void friendFunction(MyClass& obj) {
          obj.secret = 42; // 允许访问私有成员
      }
  2. 友元类

    • 另一个类的所有成员函数均可访问当前类的私有成员。
    • 声明示例
      class FriendClass;
      
      class MyClass {
          friend class FriendClass; // 友元类声明
      private:
          int secret;
      };
      
      class FriendClass {
      public:
          void modifySecret(MyClass& obj) {
              obj.secret = 100; // 允许访问私有成员
          }
      };
  3. 类的成员函数作为友元

    • 仅某个类的特定成员函数可访问当前类的私有成员。
    • 声明示例
      class OtherClass {
      public:
          void specialAccess(MyClass& obj);
      };
      
      class MyClass {
          friend void OtherClass::specialAccess(MyClass& obj); // 成员函数友元声明
      private:
          int secret;
      };
      
      void OtherClass::specialAccess(MyClass& obj) {
          obj.secret = 200; // 允许访问
      }

2、友元的核心特性

  1. 单向性
    友元关系是单向的。若类A声明类B为友元,类B可访问A的私有成员,但类A不能自动访问类B的私有成员。

  2. 非传递性
    友元关系不可传递。若类A是类B的友元,类B是类C的友元,类A不自动成为类C的友元。

  3. 不受访问控制符限制
    友元声明可置于类的任何区域(public、private或protected),不影响其权限。

  4. 不可继承
    派生类不继承基类的友元关系。若基类Base有友元类Friend,派生类Derived的友元不包括Friend。


3、友元的应用场景

  1. 运算符重载
    重载输入输出运算符<<>>时,常需声明为友元以访问私有数据。

    class MyClass {
        int data;
        friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
    public:
        MyClass(int d) : data(d) {}
    };
    
    std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
        os << obj.data; // 访问私有成员
        return os;
    }
  2. ​紧密协作的类
    如树(Tree)与节点(Node)类,相互访问内部结构。

    class Tree; // 前向声明
    
    class Node {
        int value;
        Tree* parentTree;
        friend class Tree; // Tree可访问Node的私有成员
    };
    
    class Tree {
        Node* root;
    public:
        void updateRoot(Node* newRoot) {
            newRoot->parentTree = this; // 访问Node的私有成员
            root = newRoot;
        }
    };
  3. 日志或调试工具类
    日志类需要访问其他类的私有数据以记录详细信息。

    class BankAccount {
        double balance;
        friend class Logger; // Logger可访问余额
    public:
        BankAccount(double b) : balance(b) {}
    };
    
    class Logger {
    public:
        static void logBalance(const BankAccount& acc) {
            std::cout << "Current balance: " << acc.balance << std::endl;
        }
    };

4、友元的使用注意事项

  1. 避免过度使用
    过度依赖友元会破坏封装性,增加代码耦合度。优先考虑通过公有接口(如Getter/Setter)实现功能。

  2. 替代方案评估
    在以下场景中,可能不需要友元:

    • 数据访问可通过公有方法实现。
    • 使用设计模式(如观察者模式)解耦类间依赖。
  3. 模板类中的友元
    模板类声明友元时需注意模板参数的匹配。

    template
    class Box {
        T content;
        friend void peek(const Box& box) { // 每个模板实例生成对应友元函数
            std::cout << box.content << std::endl;
        }
    };
  4. 安全性与异常处理
    友元函数或类可能修改私有数据,需确保操作的安全性,如参数验证和异常处理。


九、聚合类(类似C中结构体)

在C++中,​聚合类(Aggregate Class)​ 是一种特殊类型的类,允许通过初始化列表(花括号 {})直接初始化其成员。


1、聚合类的定义条件

一个类必须满足以下所有条件,才能被视为聚合类

  1. 无用户声明的构造函数
    • 包括默认构造函数、拷贝构造函数、移动构造函数等。
  2. 所有非静态数据成员均为 public
    • 没有私有(private)或保护(protected)的非静态成员。
  3. 无基类(即无继承关系)​
    • 不能是派生类。
  4. 无虚函数(包括虚析构函数)​
    • 没有虚函数表(vtable)。

2、聚合类的核心特性

  1. 聚合初始化
    可以直接通过初始化列表 {} 按顺序初始化所有成员,无需定义构造函数。

    struct Point {  // 聚合类
        int x;
        int y;
    };
    
    Point p1 = {1, 2};  // 聚合初始化
    Point p2{3, 4};     // C++11起支持直接初始化
  2. 支持嵌套聚合初始化
    如果聚合类的成员本身也是聚合类型,可以嵌套使用 {} 初始化。

    struct Rectangle {  // 聚合类
        Point topLeft;
        Point bottomRight;
    };
    
    Rectangle rect = {{0, 0}, {10, 20}};
  3. 允许默认成员初始化(C++11+)​
    聚合类的成员可以指定默认值,但初始化列表仍按顺序覆盖默认值。

    struct Circle {
        int x = 0;  // 默认值
        int y = 0;
        int radius = 1;
    };
    
    Circle c1;                // 使用默认值:x=0, y=0, radius=1
    Circle c2 = {5, 5, 10};   // 覆盖默认值:x=5, y=5, radius=10
    Circle c3 = {5, 5};       // 部分覆盖:x=5, y=5, radius=1(保留默认)

3、聚合类的限制

  1. 初始化列表必须严格按顺序
    成员的初始化顺序必须与类中声明的顺序一致。

    struct Data {
        int id;
        std::string name;
    };
    
    Data d1 = {42, "Alice"};  // 正确
    Data d2 = {"Bob", 10};    // 错误!类型和顺序不匹配
  2. 不能跳过成员初始化
    必须为所有成员提供初始化值,或依赖默认成员初始化(C++11+)。

    struct Person {
        std::string name;
        int age = 0;  // 默认值
    };
    
    Person p1 = {"Alice"};       // 正确:age使用默认值0
    Person p2 = {"Bob", 30};     // 正确:age被覆盖为30
    Person p3 = {};             // 正确:name初始化为空字符串,age=0
  3. 不允许窄化转换
    初始化列表中不能发生窄化转换(如 double → int 需要显式转换)。

    struct Value {
        int x;
    };
    
    Value v1 = {3.14};          // 错误!double→int是窄化转换
    Value v2 = {static_cast(3.14)}; // 正确

4、聚合类的常见形式

  1. 结构体(struct)​
    默认所有成员为 public,天然适合作为聚合类。

    struct Vec3 {  // 聚合类
        float x, y, z;
    };
  2. 类(class)​
    若显式将所有成员设为 public,且满足其他条件,也可作为聚合类。

    class RGB {  // 聚合类
    public:
        int r, g, b;
    };
    
    RGB color = {255, 128, 64};

5、聚合类的典型应用场景

  1. 数据容器(POD类型)​
    用于存储简单数据,如坐标、颜色、配置参数等。

    struct Config {  // 配置文件参数
        int width;
        int height;
        bool fullscreen;
    };
    
    Config config = {1920, 1080, true};
  2. 与C语言兼容的结构体
    在混合C/C++编程时,确保数据布局兼容。

    extern "C" {
        struct CCompatibleStruct {  // 可被C代码使用
            int a;
            double b;
        };
    }
  3. 序列化与反序列化
    便于将聚合类转换为字节流或JSON格式。

    #include 
    using json = nlohmann::json;
    
    struct User {
        std::string username;
        int age;
    };
    
    User u = {"Alice", 30};
    json j = u;  // 自动序列化为 {"username": "Alice", "age": 30}

6、聚合类与非聚合类的对比

特性 聚合类 非聚合类
构造函数 无用户声明 可有用户声明构造函数
数据成员访问权限 所有非静态成员为 public 可包含 private/protected
初始化方式 直接使用 {} 初始化列表 需调用构造函数
继承关系 无基类 可有基类

7、小结

  1. 聚合类的核心优势:无需构造函数即可直接初始化,简化代码。
  2. 适用场景:简单数据封装、C兼容性、序列化等。
  3. 设计要点
    • 保持成员顺序和类型明确。
    • 优先使用结构体(struct)定义聚合类。
    • 在C++11+中合理使用默认成员初始化。

十、字面值常量类

字面值常量类(Literal Class)​,这是C++11引入的特性,允许用户自定义类型在编译期进行常量计算。


1、字面值常量类的核心要求

一个类要成为字面值常量类,必须满足以下条件:

  1. 所有数据成员均为字面值类型​(如intdouble、其他字面类等)。
  2. 必须含有至少一个constexpr构造函数
  3. 若有析构函数,必须是默认的​(隐式或显式=default)。
  4. 所有非静态成员的初始化必须使用常量表达式

2、定义字面值常量类的步骤

1. ​定义类的基本结构
class Distance {
private:
    double meters; // 数据成员必须是字面值类型

public:
    // 步骤2:添加constexpr构造函数
    constexpr Distance(double m) : meters(m) {}

    // 步骤3:定义constexpr成员函数(可选)
    constexpr double toKilometers() const {
        return meters / 1000.0;
    }

    // 步骤4:析构函数必须默认(可省略,编译器自动生成)
    ~Distance() = default;
};
2. ​确保数据成员为字面值类型
  • 成员类型如doubleint、其他字面类等。
  • 不支持动态内存、虚函数等非字面值特性。
3. ​定义constexpr构造函数
  • 构造函数必须用constexpr修饰。
  • 初始化列表中的表达式必须是常量。
constexpr Distance(double m) : meters(m) {}
4. ​实现constexpr成员函数(可选)​
  • 允许在编译期进行成员函数调用。
  • 函数体必须简单,仅包含可在编译期执行的操作。
constexpr double toKilometers() const {
    return meters / 1000.0;
}

3、使用字面值常量类

1. ​编译期初始化对象
constexpr Distance d1(500.0); // 编译期初始化
2. ​在编译期进行计算
constexpr double km = d1.toKilometers(); // 编译期计算:0.5 km
3. ​作为模板参数
template 
void printDistance() {
    std::cout << D.toKilometers() << " km\n";
}

int main() {
    printDistance(); // 输出 2 km
    return 0;
}

4、完整示例:编译期单位转换

#include 

class Distance {
private:
    double meters;

public:
    constexpr Distance(double m) : meters(m) {}

    constexpr double toKilometers() const {
        return meters / 1000.0;
    }

    constexpr Distance operator+(const Distance& other) const {
        return Distance(meters + other.meters);
    }
};

// 用户定义的字面量(C++11)
constexpr Distance operator"" _m(long double m) {
    return Distance(m);
}

constexpr Distance operator"" _km(long double km) {
    return Distance(km * 1000.0);
}

int main() {
    constexpr Distance d1 = 500.0_m;      // 500米
    constexpr Distance d2 = 2.5_km;       // 2500米
    constexpr Distance total = d1 + d2;   // 3000米

    // 编译期计算
    constexpr double km = total.toKilometers(); // 3.0 km

    std::cout << "Total distance: " << km << " km\n";
    return 0;
}

5、关键限制与注意事项

  1. 数据成员限制
    • 不支持动态内存、引用、非字面类型成员。
  2. 成员函数限制
    • 不能有虚函数(虚函数表在编译期无法确定)。
  3. 析构函数必须平凡
    • 不可自定义资源释放逻辑。
  4. 模板元编程兼容性
    • 字面值常量类可用作模板非类型参数。

6、应用场景

  1. 高性能计算:编译期计算减少运行时开销。
  2. 嵌入式系统:常量数据直接嵌入固件。
  3. 单位库:编译期单位转换(如上述示例)。
  4. 模板元编程:编译期类型操作和值计算。

7、小结

  1. 字面值常量类允许编译期对象初始化与计算,提升性能。
  2. 核心要求constexpr构造函数、字面类型成员、平凡析构。
  3. 适用场景:高性能计算、模板编程、嵌入式开发等。
  4. 结合用户定义字面量可极大提升代码可读性。

十一、静态成员

1、静态成员的核心概念

  • 静态成员变量:属于类本身,所有对象共享同一份数据。
  • 静态成员函数:属于类,不依赖对象实例,只能访问静态成员。

2、静态成员变量的定义与初始化

1. ​声明(类内)​
class Counter {
public:
    static int count; // 声明静态成员变量
    Counter() { count++; }
    ~Counter() { count--; }
};
2.定义与初始化(类外)​

静态成员变量必须在类外单独定义(分配内存)。

int Counter::count = 0; // 初始化,不写static关键字
3. ​访问方式
Counter obj1, obj2;
cout << Counter::count; // 通过类名访问(推荐)

3、静态成员函数的定义与使用

1. ​声明(类内)与定义(类外)
class MathUtils {
public:
    static double pi() { // 静态成员函数
        return 3.1415926;
    }
    static int add(int a, int b); // 声明
};

// 类外定义
int MathUtils::add(int a, int b) {
    return a + b;
}
2. ​调用方式
double pi = MathUtils::pi(); // 无需对象
MathUtils utils;
int sum = utils.add(3, 5);    // 允许但不推荐
3. ​限制
  • 不能访问非静态成员变量或调用非静态成员函数。
  • this指针。

4、静态成员的应用场景

1. ​统计对象数量
class InstanceCounter {
public:
    static int instanceCount;
    InstanceCounter() { instanceCount++; }
    ~InstanceCounter() { instanceCount--; }
};
int InstanceCounter::instanceCount = 0;

int main() {
    InstanceCounter a, b;
    cout << InstanceCounter::instanceCount; // 输出2
    return 0;
}
2. ​共享配置或资源
class Logger {
private:
    static std::ofstream logFile; // 共享日志文件
public:
    static void log(const std::string& message) {
        logFile << message << std::endl;
    }
};
std::ofstream Logger::logFile("app.log");
3. ​工具函数
class StringUtils {
public:
    static std::string toUpper(const std::string& str) {
        std::string result = str;
        std::transform(result.begin(), result.end(), result.begin(), ::toupper);
        return result;
    }
};

5、C++11后的改进:类内初始化静态成员

  • 条件:静态成员为const整型、枚举或字面值类型。
class Settings {
public:
    static const int MAX_USERS = 100; // 类内初始化
    static constexpr double VERSION = 2.0; // C++11 constexpr
};
// 类外定义(仍需提供,但不赋值)
const int Settings::MAX_USERS;
constexpr double Settings::VERSION;

6、注意事项

  1. 线程安全:静态成员在多线程环境中需加锁保护。
  2. 初始化顺序:不同编译单元的静态变量初始化顺序不确定。
  3. 单例模式:静态成员常用于实现单例。
    class Singleton {
    public:
        static Singleton& getInstance() {
            static Singleton instance; // C++11起线程安全
            return instance;
        }
        Singleton(const Singleton&) = delete;
        void operator=(const Singleton&) = delete;
    private:
        Singleton() {} // 私有构造函数
    };

7、小结

  • 静态成员变量:全局唯一,节省内存,共享数据。
  • 静态成员函数:独立于对象,提供工具方法或管理静态数据。
  • 适用场景:计数器、共享资源、工具类、单例模式。
  • 慎用:避免滥用导致代码耦合或线程安全问题。

十二、类的继承(Inheritance)

1. 基本语法

class Derived : access-specifier Base {
    // 派生类成员
};
  • 访问说明符publicprotectedprivate,决定基类成员在派生类中的访问权限。

2. 示例:公有继承

class Animal {
public:
    void eat() { cout << "Eating" << endl; }
};

class Dog : public Animal {
public:
    void bark() { cout << "Barking" << endl; }
};

Dog d;
d.eat();  // 继承自Animal
d.bark();

十三、多态(Polymorphism)与虚函数

1. 虚函数(Virtual Function)

  • 允许派生类重写基类方法。
  • 动态绑定(运行时多态)。
class Shape {
public:
    virtual double area() const { return 0.0; } // 虚函数
    virtual ~Shape() {} // 虚析构函数(重要!)
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
};

2. 纯虚函数与抽象类

class AbstractShape {
public:
    virtual void draw() const = 0; // 纯虚函数
};

class Circle : public AbstractShape {
public:
    void draw() const override { /* 实现 */ }
};

十四、类的设计原则

  1. 封装(Encapsulation)​:隐藏实现细节,暴露接口。
  2. 单一职责原则(SRP)​:一个类只负责一个功能。
  3. 优先组合而非继承:减少类之间的耦合。
  4. 遵循RAII(Resource Acquisition Is Initialization)​:资源管理绑定对象生命周期。

十五、总结

  1. 类是数据和行为的封装单元,通过访问控制保护内部状态。
  2. 构造函数/析构函数管理对象生命周期,资源获取即初始化(RAII)。
  3. 继承与多态实现代码复用和扩展,虚函数支持动态绑定。
  4. 现代C++特性(移动语义等)​优化资源管理和性能。
  5. 遵循OOP设计原则提升代码可维护性和可扩展性。

你可能感兴趣的:(C++,c++,开发语言,数据结构,算法)