# 对象和类 ## 目录 - [访问控制](#访问控制) - [作用域运算符](#作用域运算符) - [类的六大特殊成员函数](#类的六大特殊成员函数) - [RAII](#RAII) - [move语义](#move) - [右值引用](#右值引用) - [初始化列表](#初始化列表) - [const 成员函数](#const成员函数) - [this指针](#this指针) - [作用域为类的常量](#作用域为类的常量) - [作用域内枚举](#作用域内枚举) - [友元](#友元) - [类的自动转换和强制类型转换](#类的自动转换和强制类型转换) - [转换函数](#转换函数) - [继承](#继承) - [继承方式](#继承方式) - [虚函数](#virtual) - [设计理念](#) - [is a](#) - [AbstactBaseClass](#ABC) ## 访问控制 ```cpp class demo { public : // 公有接口 private: // 私有成员 protected: // 保护,对外部是私有 }; ``` ## 作用域运算符(::) 可用于在类体外指出函数所属的类(命名空间) **成员函数的参数名不可与类成员相同** ## 类的六大特殊成员函数(未定义时编译器提供默认版本) ```cpp demo::demo(); // 默认构造函数 demo::~demo(); // 默认析构函数 demo::demo(const demo&); // 复制构造函数 demo& demo::operator = (const demo&); // 赋值构造函数 demo::demo(demo&&); // 移动复制构造函数 demo& demo::operator = (demo&&); // 移动赋值构造函数 ``` `demo::demo() = default;`显式声明为默认 `demo::demo() = delete;`显式声明为禁用 ### RAII 在类的构造函数中申请堆内存,在析构函数中释放,这样可以保证类失效时内存被释放 ### move 移动语义的核心思想是转移而非复制。当一个对象被移动时,其资源(如动态分配的内存、文件句柄等)被转移到目标对象,而源对象被置于一种有效但未定义的状态。这种状态通常是清空的,以确保源对象的资源在其生命周期内不会被重复释放 ```cpp MyString(MyString&& other) noexcept : data(other.data) { // 移动构造函数 other.data = nullptr; } MyString& operator=(MyString&& other) noexcept { // 移动赋值运算符 if (this != &other) { delete[] data; // 释放当前对象的资源 data = other.data; other.data = nullptr; } return *this; } ``` `std::move` 是一个函数模板,用于将左值强制转换为右值。它本身并不执行移动操作,而是通过返回一个右值引用,触发移动构造函数或移动赋值运算符 `MyString str2 = std::move(str1); // 触发移动构造函数` #### 右值引用 分为两类 > 普通右值引用:直接声明的T&&,用于移动语义 > 转发引用:模板函数中声明的T&&,用于完美转发 - [函数引用重载](./func#重载) ### 初始化列表 初始化列表是一种在构造函数中初始化类成员变量的机制 1. 初始化常量成员变量:常量成员变量必须在构造函数的初始化列表中初始化,不能在构造函数体内赋值。 2. 初始化引用成员变量:引用成员变量必须在构造函数的初始化列表中初始化,不能在构造函数体内赋值。 3. 调用基类的构造函数:如果类继承自基类,需要在初始化列表中显式调用基类的构造函数。 4. 优化性能:对于一些复杂的成员变量(如类对象),使用初始化列表可以避免默认构造后再赋值,从而提高效率。 > example `MyClass(int a, int b, int c) : Base(a), myConst(b), myRef(c), myValue(10) {}` ## const成员函数 适合的成员函数要尽可能用,以帮助规避错误 `void show() const;` 声明 `void demo::show() const;` 定义 表明函数不会修改调用对象 ## this指针 成员函数引用整个调用对象,可以使用 *this ## 作用域为类的常量(无法用const) 因为声明类只是描述了对象的形式,没有创建对象 + 在类中声明一个枚举 ```cpp class const_demo { private: enum {Pi = 3.1415); double s = 2*Pi; }; ``` + 使用关键字 static 与其他静态常量存储在一起 ` class demo {static const int Months=12};` ## 作用域内枚举 `enum class egg {small, large};` 使用后需要通过枚举名限定枚举量 `egg demo1 = egg::large;` 并且关闭了隐式转换的特性 ## 友元 + 友元函数 + 友元类 + 友元成员函数 将函数声明放在类体内,public private 内都无所谓 在声明前面加上friend关键字 ## 类的自动转换和强制类型转换 当类有仅一个参数的构造函数时 遇到合适的会进行自动转换 explicit 禁止单个参数构造函数导致的隐式自动类型转换 仍然可以使用显式强制转换(当不存在二义性时) ### 转换函数 `int aaa = int(demo);` operator typeName(); + 必须是类成员 + 不能有参数 + 不能指定返回类型 ## 继承 基类对象需要在进入派生类的构造函数之前被创建,通常使用初始化列表解决 `demo::demo() : base();` ### 继承方式 1. public + 基类的公有成员在派生类中仍然是公有的。 + 基类的保护成员在派生类中仍然是保护的。 + 基类的私有成员在派生类中不可访问。 + 派生类*对象*可以访问基类的公有成员。 + 派生类*对象*不能直接访问基类的*私有成员* 2. protected + 基类的公有成员和保护成员在派生类中都变成保护的。 + 基类的私有成员在派生类中仍然不可访问。 + 派生类*对象*不能直接访问基类的*所有成员* 3. private + 基类所有成员变成私有 + 派生类*对象*不能直接访问基类的*所有成员* __保护成员在派生类中的访问性__ 无论继承方式,派生类都可以访问基类的 `protected` 这是因为保护成员的设计初衷就是允许派生类访问,但不允许*派生类的对象*访问 ### virtual 虚函数是通过在基类中使用关键字 virtual 声明的成员函数 虚函数的主要作用是实现动态绑定或运行时多态.当通过基类指针或引用调用虚函数时,程序会根据对象的实际类型(运行时类型)来调用相应的函数版本 为了实现动态绑定,C++ 编译器会在每个具有虚函数的类的对象中隐式添加一个指针,指向一个虚函数表(V-Table)。V-Table 是一个函数指针数组,存储了类中所有虚函数的地址。当通过指针或引用调用虚函数时,程序会通过 V-Table 查找并调用正确的函数版本 **虚函数的特点** + 必须是成员函数 + 派生类继承基类的虚函数,但可以重写它 + 覆盖:派生类中的虚函数与基类中的虚函数具有相同的签名 + 多态:通过基类指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数版本 **纯虚函数与抽象类** 如果一个虚函数在基类中没有实现,而是通过在函数声明后加上 = 0 来表示,那么这个函数称为纯虚函数。包含纯虚函数的类称为抽象类 `virtual void display() = 0; // 纯虚函数` ## 设计理念 ### is-a ### ABC 即使用[纯虚函数](#virtual)构造的抽象类 无法构建对象,只能用于构造其他派生类 用于提取一系列对象的共性以共用 即抽象类设计理念,把一系列对象的共性提取出来,创建一个*抽象*的类