What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
cs:programming:cpp:courses:cpp_basic_deep:chpt_12 [2024/11/12 06:02] – 移除 - 外部编辑 (未知日期) 127.0.0.1cs:programming:cpp:courses:cpp_basic_deep:chpt_12 [2024/11/12 06:02] (当前版本) – ↷ 页面名由cs:programming:cpp:courses:cpp_basic_deep:chpt_7改为cs:programming:cpp:courses:cpp_basic_deep:chpt_12 codinghare
行 1: 行 1:
 +======类与面向对象编程======
 +//第 11 章笔记//
 +----
 +====结构体与对象聚合====
 +  * 将多个对象放置聚合在一起,视作整体
 +  * 结构体的定义:需要接分号结束(C语言的原因,需要一个分号来确定是结构体的定义)
 +  * 结构体的声明:incomplete type
 +<code cpp>
 +// 不可以声明结构体
 +Str1 myStr1; //error
 +// 但可以声明结构体的指针
 +Str1* myStr1;
 +</code>
 +  * 一处定义原则:类和结构体都处于单元级别。每个单元都只能有一个结构体定义,但程序可能拥有多个结构体定义。编译的时候结构体必须对翻译单元可见。
 +===数据成员的初始化===
 +  * 不能使用 auto 定义数据成员:
 +<code cpp>
 +// 结构体定义
 +struct Str
 +{
 +     // 类中初始化
 +    int x = 3;
 +    decltype{3} y; // C++11:使用 decltype 定义变量
 +    // 不能使用 auto
 +    auto z; // error
 +    // 可以使用 const,引用,限定
 +    const int g = 3;
 +   
 +};
  
 +// 隐式定义了结构体的内部成员
 +// 定义发生在这里,而不是在结构体中
 +Str m_str; 
 +
 +// 类中元素会进行默认初始化
 +// 默认初始化跟结构体中的变量排序和数量有关
 +Str m_str2 = {3}; // x = 3, y = 0, g = 3
 +
 +//C++20,聚合初始化的指派
 +Str m_str3 = {.x=3, .y = 4, .g = 5};
 +</code>
 +==mutable 限定符==
 +  * 当结构体对象为 const 时,无法改变结构体内部的值
 +  * 这时可以将需要修改的变量修饰为 mutable,即可对其修改
 +<code cpp>
 +struct Str1
 +{
 +   mutable int x = 0;
 +};
 +// 修改常量对象中的变量
 +const Str1 myStr1;
 +myStr1.x = 1;
 +
 +</code>
 +===静态数据成员===
 +  * 多个对象共享的类数据成员
 +  * 一般静态成员在**类外定义**,''const'' 静态成员可以在**类内**初始化
 +<code cpp>
 +struct Str
 +{
 +    // 声明
 +    static int x;
 +    int y;
 +};
 +
 +// 类外定义(c++98)
 +// Str::表明 x 属于 Str 域内
 +// 为了共享,专门引入一个文件用于定义
 +// 编译器处理顺序 header->发现静态成员->寻找其他翻译单元内的定义,引入定义编译结构体
 +int Str::x;
 +
 +int main(int argc, char const *argv[])
 +{
 +    Str str1;
 +    Str str2;
 +
 +    str2.x = 100;
 +    // 两个 x 都为100
 +    std::cout << str1.x << " "<< str2.x << std::endl;
 +
 +    return 0;
 +}
 +</code>
 +<WRAP center round box 100%>
 +c++98 中 提供了在类中可以定义静态成员的功能。这是因为某些静态成员需要作为类型的一部分进行初始化。比如使用静态成员作为数组的长度,此时该静态成员是常量。如果使用该静态成员定义数组,如果不允许在类中初始化静态成员,那么对应的数组将无法定义。因此 C++ 98 中规定可以在类中这么用: ''const static int array_size = 100;'' 实际上,编译器会将 ''array_size'' 替换为 ''100''。\\ \\ 
 +其限制在于,不能使用其地址对其访问(undefined reference)。因为值替换是编译期行为,不会构造存储空间给 ''array_size''。但是通过地址访问时运行期行为。此时,只有使用类外静态成员定义,才能构造该常量静态成员的地址,才可以通过地址访问。
 +</WRAP>
 +==内联静态成员==
 +C++ 17 中提供了内联静态成员。由于静态成员和内联函数的相似性(多个翻译单元 / 多个类对象共用一份),因此可以如下方式在类中声明静态成员:
 +<code cpp>
 +// 只保留一份静态成员的定义
 +// 不需要是常量
 +// 还可以使用 auto 推断成员
 +struct Str
 +{
 +    inline int array_size = 100;
 +    inline auto array_size2 = 100;
 +   
 +};
 +// 还可以直接进行修改
 +Str str;
 +str.array_size = 50;
 +Str *strPt = &str;
 +// 直接访问需要指定域
 +// 静态成员能被所有对象访问
 +Str::array_size;
 +// 可以通过指针访问
 +strPt->array_size;
 +</code>
 +<WRAP center round box 100%>
 +一般会专门提供一个文件用于存放静态成员的定义。
 +</WRAP>
 +==类内部声明相同类型的静态数据成员==
 +<code cpp>
 +// 不使用 inline
 +// 类外定义
 +Str Str::member; 
 +// 使用 inline 
 +// 类中使用 inline 是不完全类型,需要在外部定义
 +inline Str Str::member;
 +</code>
 +===成员函数===
 +  * 在结构体中定义的函数,作为结构的一部分
 +  * 结构体与其成员和成员函数组成类,类是一种抽象数据类型
 +  * 结构体和外部的桥梁:对内操作数据,对外提供接口
 +  * 关键字 ''class''
 +  * 类拥有自己的域
 +==声明和定义==
 +  * 类内定义在类结构简单的时候使用
 +  * 类外定义在类结构复杂的的使用;外部定义做成链接库,用户通过 header 去调用
 +<code cpp>
 +class Str
 +{
 +    // 类内定义
 +    // 隐式内联,避免多次包含 header 导致的重定义
 +    void fun1() {};
 +
 +    // 类外定义的声明
 +    void fun2();
 +};
 +
 +// 类外定义
 +// 非内联
 +// 将声明存储于 Header, 将定义存储于另外一个翻译单元
 +void Str::fun2() {};
 +// 类外内联定义
 +inline void Str::fun2() {};
 +</code>
 +==类会经过编译器的两遍处理==
 +类中处理函数和数据成员的逻辑与外部不同。简单来说,不是通过从上到下的顺序来执行的。对于类来说,成员函数较为重要(接口),往往实现会先写函数(约定俗成)。这种情况下,如果按照外部的编译顺序,当函数调用内部数据成员时,成员是不可见的。为了处理这个问题,C++ 会对类进行两遍处理:
 +  - 函数可见时,并不会立即处理函数,而是接着处理其余的部分
 +  - 函数内部的逻辑会在第二次扫描中处理。
 +<WRAP center round important 100%>
 +两次处理区分的是函数外部的内容与函数内部的逻辑内容。
 +</WRAP>
 +==尾部类型返回与成员函数==
 +通常情况下,C++ 在做函数的类外定义时,必须指定所有参与定义部分的来源(域)
 +<code cpp>
 +struct Str
 +{
 +    int x;
 +    using MyRes = int;
 +    MyRes fun();
 +};
 +
 +// 注意返回类型 MyRes 是在类中定义的
 +// 因此必须指定域
 +Str::MyRes Str::fun()
 +{
 +    return x;
 +}
 +</code>
 +但如果使用尾部返回的写法,C++可以自动推断出该返回类型的来源:
 +<code cpp>
 +// 通常返回复杂的类型可以使用这种写法
 +auto Str::fun() -> MyRes
 +{
 +    return x;
 +}
 +</code>
 +==成员函数与this指针==
 +  * ''this'' 指针指向了当前的对象
 +  * ''this'' 指针可以配合箭头操作符使用,指定当前变量的域(访问类变量)
 +<code cpp>
 +struct Str
 +{
 +    int x = 3;
 +    // 实际上参数是 fun(Str *this)
 +    void fun()
 +    {
 +        std::cout << x << std::endl;
 +    }
 +    void fun2(int x)
 +    {
 +        // 如果不使用 this, 则返回的是 fun2 的参数 x(局部变量)
 +        // std::cout << x << std::endl;
 +        // 使用 this 返回类对象中的 x
 +        std::cout << this->x << std::endl;
 +    }
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    Str myStr;
 +    Str* myStrP = &myStr;
 +    // 调用的是 Str::fun(&myStr)
 +    myStr.fun();
 +    // 使用箭头操作符
 +    // 等同 (*myStr).fun2()
 +    // 打印 类中的 x,值为 3
 +    myStrP->fun2(5);
 +
 +    return 0;
 +}
 +</code>
 +<WRAP center round box 100%>
 +''this'' 指针的类型是指向类对象的指针。由于指向不能变换,''this'' 本身不能被修改;但因为我们要通过 ''this'' 修改对象中的内容,因此 ''this'' 是 //top-const// 类型的指针(''const Str*''
 +</WRAP>
 +==常量成员函数==
 +由于 ''this'' 只能保证自身无法被修改,当需要阻止成员函数修改类中数据成员时,我们需要将成员函数声明为常量成员函数:
 +<code cpp>
 +// 不允许该接口改变类成员
 +// 实际上,是将 this 的类型从 const Str* 转换为了 const Str* const
 +void fun() const {...}
 +</code>
 +==基于 const 的重载==
 +基于上述的特性,C++ 允许同名的函数基于 constness 进行重载:
 +<code cpp>
 +// 这是两个不同的函数
 +// 参数类型不同
 +void fun() {...} // plain this, const Str*
 +void fun() const {...} // low-const this, const Str* const
 +</code>
 +
 +==成员函数的查找与隐藏==
 +  * 函数内部的名称 > 类内部的名称 > 类外部的名称
 +  * 都找不到会返回错误
 +  * 需要访问指定的名字时,需要指定**域**(依赖性查找)
 +==静态成员函数==
 +  * 可以被所有类对象**共享**的函数
 +  * 用于描述与类(而不是对象)相关的内容
 +    * 比如表述固定长度数组的类,其数组长度与类对象的具体信息无关。如果想取回该类所表示数组的长度信息,可以使用静态成员函数来处理。
 +  * 可以返回静态成员
 +  * 只能对共享对象进行操作
 +<WRAP center round box 100%>
 +从原理上来说,成员函数使用 ''this'' 指针来访问内部成员。而静态成员函数因为被共享,其参数不再是 ''this''(编译器也不可能知道当前的类对象是哪个)。因此,对象无法通过静态成员函数来访问当前对象中的普通成员。
 +</WRAP>
 +使用静态成员函数的两种方式:
 +<code cpp>
 +// 使用静态函数返回静态成员
 +struct Str {
 +    static int size()
 +    {
 +        return x;
 +    }
 +    inline static int x = 11;
 +};
 +// 对象调用
 +myStr1.fun();
 +// 域调用
 +Str::fun();
 +</code>
 +==注意局部静态成员与类静态成员的区别==
 +<code cpp>
 +static int size()
 +{
 +    // 局部静态成员,生存周期从函数被调用到程序结束
 +    static int x; 
 +    // 会返回上面的 x,而不是类中静态成员 x
 +    return x;
 +}
 +</code>
 +<WRAP center round box 100%>
 +  * 这个特性可以利用起来。当处理需要按需使用的共享资源时,可以将静态存储于静态成员函数中;这样只有调用静态成员函数时,才会申请该资源。
 +  * 另外一种应用更有名:singleton
 +</WRAP>
 +
 +==基于引用限定符的重载==
 +<code cpp>
 +// 该重载基于调用者的左右值属性来重载
 +// C++11 的写法:不可与 98 混用。只要后面有一个加了 &,那么所有的重载都要加 (&)
 +// 调用者为左值时调用
 +void foo() &;
 +// 调用者为右值时调用
 +void foo() &&;
 +// 调用者为常量左值时
 +void foo() const &;
 +// 通常不使用
 +void foo() const &&;
 +</code>
 +<WRAP center round box 100%>
 +关键字:ref-qualified-member-functions。
 +</WRAP>
 +====访问限定符和友元====
 +  * 允许对内部数据进行封装,再使用成员函数作为外部接口
 +  * struct 的默认访问权限是 //public//,class 的默认访问权限是 //private//
 +  * 三种限定符:
 +    * ''private'':只允许类域内部的访问
 +    * ''public'':所有类外都可访问
 +    *  ''protected'':只允许类域内部和子类域内部访问
 +===友元函数 / 类===
 +  * 允许从类外访问类中的私有成员
 +  * 关键字 ''friend''
 +  * 声明在**类中声明**
 +<WRAP center round box 100%>
 +  * 友元破坏封装,慎用
 +  * 友元的权限由**被访问的类**提供
 +  * 友元的访问是**单向**的
 +  * 友元不受访问限定符的限定
 +</WRAP>
 +==函数 / 类的声明可以以友元的方式在类中声明==
 +函数的定义受声明顺序的影响,因此在某些函数的定义牵涉到在其之后(不可见的)类型时,需要对这些类型进行前置声明。如果函数会被定义为友元函数,C++ 允许以友元的方式在**类中**完成首次声明:
 +<code cpp>
 +class Str
 +{
 +    // fun() 被声明,并视作为 Str 的友元函数
 +    friend void fun();
 +    // Str2 类被声明,并被视作 Str 的友元类
 +    friend class Str2;
 +    
 +    // 注意:使用域限定符会破坏友元的首次声明
 +    // 此时必须要提前对其作出声明
 +    // friend void ::fun();
 +    
 +    int x = 100;
 +};
 +
 +class Str2 {
 +public:
 +    void print()
 +    {
 +        Str str;
 +        std::cout << str.x << std::endl;
 +    }
 +};
 +
 +void fun()
 +{
 +    Str str;
 +    std::cout << str.x << std::endl;
 +}
 +</code>
 +==友元函数的类内定义和类外定义==
 +在类中定义的友元函数会被隐藏:
 +<code cpp>
 +class Str
 +{
 +    // 隐藏友元
 +    // fun() 是友元,不是成员
 +    // 因此 fun() 的作用域处于 Str 外部
 +    // 此时编译器会隐藏友元的首次定义,也就是认为 fun() 并没有声明
 +    friend void fun()
 +    {
 +        Str val;
 +        std::cout << val.x << std::endl;
 +    }
 +    int x = 100;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    // undefined
 +    fun();
 +    
 +    return 0;
 +}
 +</code>
 +<WRAP center round box 100%>
 +  * 减轻编译器负担:友元函数的声明会使搜寻范围扩大。
 +  * 使用友元函数的类外定义即可解决问题
 +  * 或使用参数,使用 const 实参类型的依赖查找来实现
 +</WRAP>
 +<code cpp>
 +class Str
 +{
 +    // 使用实参依赖关系进行类中的友元函数定义
 +    friend void fun2(Str& val)
 +    {
 +        std::cout << val.x << std::endl;
 +    }
 +    int x = 100;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    Str val;
 +    fun2(val);
 +    return 0;
 +}
 +</code>
 +====特殊成员:构造/析构/拷贝====
 +===构造函数===
 +  * 与类同名,无返回值,允许重载
 +==委托构造函数==
 +  * 某个构造函数调用已有构造函数的功能,提高复用性
 +<code cpp>
 +class Str
 +{
 +    public:
 +
 +    // 委托构造函数
 +    // 委托单参数版本给 x 赋值 3
 +
 +    // 2. 再执行委托构造函数
 +    Str():Str(3) { // 最后执行委托构造函数的函数体}
 +
 +    // 1. 先执行被调用的函数
 +    Str(int x)
 +    {
 +        this->x = x;
 +    }
 +
 +    void fun() const 
 +    {
 +        std::cout << x << std::endl;
 +    }
 +    private:
 +    int x;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    Str str;
 +    str.fun();
 +    return 0;
 +}
 +</code>
 +==构造函数的初始化列表==
 +  * 初始化:使用初始值
 +  * 赋值:复制,使用函数体
 +  * 提高初始化的性能
 +  * 不能通过拷贝构造的成员必须通过初始化列表初始化(比如引用)
 +  * 初始化顺序取决于声明顺序,与初始化列表的顺序无关
 +  * 初始化列表会覆盖类成员的初始化
 +<code cpp>
 +class Str
 +{
 +    public:
 +    Str(const std::string& strVal)
 +    {
 +        // val 以默认初始化构造
 +        // 复制初始化,低效
 +        val = strVal;
 +    }
 +
 +    // 初始化列表版本
 +    // 可读性:初始化列表需要与声明顺序一致
 +    // 初始化列表会覆盖类内成员初始化
 +    Str(const std::string& strVal, int& refSource): val(strVal), y(0),ref(refSource) {}
 +
 +    std::string val;
 +    // y 被初始化为 0,而不是 2
 +    int y = 2;
 +    // 必须通过初始化列表初始化
 +    int& ref;
 +};
 +</code>
 +<WRAP center round box 100%>
 +C++ 中构造与销毁是一个栈的结构,先创建后销毁。如果变量的初始化根据初始化列表中的顺序进行,那么变量的构造销毁顺序就是基于构造函数来进行。这样做会导致 C++ 必须记录每一个构造函数的初始化列表顺序来进行构造和销毁,这对性能会有非常大的影响。因此,初始化顺序只与类中成员声明顺序有关。
 +</WRAP>
 +==默认构造函数==
 +  * 不需要提供任何参数就能进行初始化的构造函数
 +  * 如果类中没有定义构造函数,则编译器会自动合成一个默认构造函数
 +  * 合成默认构造函数通过拷贝的方式来进行构造,如果元素中存在无法拷贝初始化的类型,那么合成默认构造函数无法创建
 +  * 对于抽象数据类型,会调用该类型的默认构造函数
 +  * 调用缺省构造函数不应该加上括号:''Str m'' 而不是 ''Str ()'',后者:''Str'' 是返回类型,''a()'' 是函数
 +  * 显式定义合成默认构造函数:''Str() = default;''
 +==单一参数构造函数==
 +  * 可以视作一种类型转换函数(比如从buit-in 类型到抽象类型)
 +<code cpp>
 +struct Str
 +{
 +    Str(int x)
 +        : val(x)
 +        {}
 +
 +    int val;
 +};
 +
 +void fun(Str str)
 +{}
 +
 +int main(int argc, char const *argv[])
 +{
 +    // 内置->抽象:将 int 类型转化为了 Str 类型
 +    Str str = 3;
 +    // 该转换支持隐式转换
 +    // 函数调用时,int 转换为了 Str 类型
 +    fun(3);
 +    return 0;
 +}
 +</code>
 +  * 如果希望阻止该转换,则使用 ''explicit'' 关键字要求必须采用显式转换
 +<code cpp>
 +
 +explicit Str(int x): val(x) {}
 +// 使用括号,大括号显式转化即可
 +fun(Str{3});
 +fun(Str(3));
 +// 或者使用显式的 cast
 +Str a = static_cast<Str>(3);
 +fun(a);
 +</code>
 +==拷贝构造函数==
 +  * 用于拷贝相同类型的对象
 +  * 第一个参数是常量类类型的引用 ''Foo(const classType& para)''
 +<code cpp>
 +struct Str
 +{
 +    Str() = default;
 +    //拷贝构造函数的定义
 +    Str(const Str& x):val(x.val) { std::cout << "copy cstr called!"; }
 +
 +    int val = 3;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    Str m1;
 +    // 第一种拷贝方式
 +    Str m2 = m1;
 +    // 第二种拷贝方式
 +    Str m3(m1);
 +    return 0;
 +}
 +</code>
 +<WRAP center round box 100%>
 +  *// 为什么使用// ''const classType&'' ?
 +拷贝构造函数需要类对象的拷贝作为参数进行初始化;如果不使用引用,那么参数的传递需要通过拷贝构造函数来进行。这样就进入了一个死循环。
 +</WRAP>
 +==默认拷贝构造函数==
 +<code cpp>
 +// 默认拷贝构造函数的定义
 +Str(const Str&) = default;
 +</code>
 +==移动构造函数==
 +  * 从输入的对象接过,而不是拷贝资源(类似指针换指向,而不是将指针指向的内容复制到新的空间)
 +  * 输入的对象不会再使用,所以接收参数的类型是 xvalue,**类对象的右值引用** ''Str(Str&&)''
 +  * 基于 ''std::move()'' 实现
 +  * 偷窃资源之后,传入的对象需要保证是合法的(可访问)
 +  * 没有拷贝构造函数时会尝试自动合成移动构造函数(如果存在不能移动的成员那么尝试会失败)
 +<code cpp>
 +class Str
 +{
 +public:
 +    Str() = default;
 +    Str(const Str&) = default;
 +
 +    // 拷贝内置类型,移动抽象类型
 +    // 注意不能加 const,移动本身就会修改类对象的内容,而拷贝不会
 +    Str(Str&& newStr):val(newStr.val), a(std::move(newStr.a)) { std::cout << "move cstr called." ; }
 +
 +    // 打印函数
 +    void fun()
 +    {
 +    std::cout << val << " " << a << std::endl;
 +    }
 +
 +private:
 +    int val = 3;
 +    std::string a = "abc";
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +
 +    Str myStr;
 +    myStr.fun();
 +    // 调用拷贝构造函数
 +    Str myCopiedStr = myStr;
 +    // 调用移动构造函数
 +    Str myMovedStr = std::move(myStr);
 +    // 3 是拷贝的,myStr 中存在 3
 +    // "abc" 是移动的,因此 myStr 中不存在 "abc"
 +    myStr.fun();
 +    return 0;
 +}
 +</code>
 +<WRAP center round box 100%>
 +编译器能否成功合成特殊函数的逻辑类似:
 +  * 合成的核心点是对每一个数据成员都采取相同的构造方法(比如拷贝都拷贝,移动都移动)
 +  * 如果是内置类型,则直接拷贝,如果是抽象类型,则调用抽象类型中对应的构造函数(拷贝构造调用拷贝构造等等)
 +  * 如果没有对应的构造函数时:
 +    * 拷贝构造会失败
 +    * 移动构造会查看是否有拷贝构造的方式,如果没有,也失败 (//C++ 14//)
 +</WRAP>
 +<code cpp>
 +
 +struct Str2
 +{
 +    // 没有该构造函数时,Str 也无法使用合成构造函数
 +    // 构造抽象类型成员时,所有的合成构造函数都会调用其本身的构造函数
 +    Str2() = default;
 +    Str2(const Str2&) {std::cout << "copy cstr called."; }
 +    // 开启或关闭看看区别
 +    // Str2(Str2&&) {std::cout << "mv cstr called."; }
 +};
 +
 +class Str
 +{
 +public:
 +    Str() = default;
 +    Str(const Str&) = default;
 +    Str(Str&&) = default;
 +
 +    // 打印函数
 +    void fun()
 +{
 +    std::cout << val << " " << a << std::endl;
 +}
 +
 +private:
 +    int val = 3;
 +    std::string a = "abc";
 +    // 当成员包含抽象数据成员时,会调用对应类型的拷贝 / 移动构造函数
 +    Str2 str2;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +
 +    Str myStr;
 +    // 有 Str2 的移动构造函数则会调用移动构造函数
 +    // 否则,有拷贝构造函数调用拷贝构造函数
 +    // 否则报错
 +    Str myMovedStr = std::move(myStr);
 +    return 0;
 +}
 +</code>
 +==移动构造函数与异常==
 +  * 异常通常会出现在拷贝过程中
 +  * 移动操作不涉及拷贝,不会抛出异常
 +  * 这种情况下,通常使用 ''noexcept'' 标记函数,说明该操作不用考虑抛出异常
 +<code cpp>
 +Str(Str&&) noexcept = default;
 +</code>
 +<WRAP center round box 100%>
 +不引入异常的好处:
 +  * 异常会带来额外的逻辑,会带来性能上的损失
 +  * 移动机制决定了其不能抛出异常。假设移动的过程是从旧的内存区域到新的区域(比如 vector 的扩容),如果移动过程中出现异常,则:
 +    * 旧区域的内容已经被移动
 +    * 内容没有移动到新的区域
 +结果就会导致数据完全丢失。实际上,vector 的实现中,如果移动构造函数没有 ''noexcept'' 时,vector 会在扩容期间调用拷贝构造函数来确保数据安全。
 +</WRAP>
 +<WRAP center round important 100%>
 +''noexcept'' 也有链式效应。如果上游类型定义了 ''noexcept'' 的移动构造函数,那么下游构造函数也必须是不会抛出异常的。否则当下游函数抛出异常,程序将无法处理这种现象。
 +</WRAP>
 +==右值引用对象作为表达式时是左值==
 +<code cpp>
 +// 函数体中的 x 是左值
 +// 参数的类型是右值引用,意味着可以从里面做移动操作
 +// 但在具体的执行中,我们决定不移动操作,而是进行访问
 +// 此时是将 x 作为左值来使用
 +Str(Str&& x) { std::string temp = x.a; } 
 +</code>
 +===Copy & Move Assignment===
 +  * 本质是以类对象为左算子,对 ''operator='' 运算符的重载
 +  * 返回类型为 ''Str&'',是因为需要支持赋值的右结合的特性;比如 ''Str3 = Str2 = Str1''。正常的逻辑是 ''Str1'' 的内容赋值到 ''Str2'' 中,再将 ''Str2'' 的内容赋值到 ''Str3'' 中,因此 ''Str2 = Str1'' 应该返回的是 ''Str2'' 本身(而不是临时对象)。因此,返回类型应该是 ''Str&''
 +  * 参数的 constness 与特殊构造函数一致:拷贝不改变参数(const),移动改变参数
 +  * 函数返回值为 ''*this''
 +==自我赋值的处理==
 +以移动元素为例,如果参数是指针,通常赋值经过三步:
 +  * 将被赋值的对象占用的资源释放(通过指针):也就是 ''delete ptr''
 +  * 使用当前的指针指向需要移动的对象所在位置:''ptr = rhs.ptr''
 +  * 然后将被移动对象的指针空置,避免不必要的访问:即 ''rhs.ptr = nullptr''
 +但问题在于,如果赋值的两遍都是同一个对象,上面这套逻辑会在第一步就清除掉所有信息。在第二步进行的时候,无论是 ''ptr'' 还是 ''rhs.ptr'',都成为了悬挂指针。
 +解决办法是提前做一次判断:如果赋值运算符两边为同一个对象,则直接返回当前的类对象:
 +<code cpp>
 +if(&rhs == this) { return *this; }
 +</code>
 +===析构函数===
 +  * 负责对象资源的释放
 +  * 不需要返回类型,不需要提供参数,与类同名 ''~Str()''
 +  * 先创建后释放;内存会在析构函数**调用完毕之后**才会释放。析构函数的函数体可以用来做一些额外收尾操作(主要是对象的销毁)
 +  * 如果不主动声明,析构函数会合成一个,内部逻辑是调用类成员自身的析构函数。
 +  * 析构函数不应该出异常。
 +===特殊成员函数的补充===
 +==指针类==
 +<code cpp>
 +class PtrStr
 +{
 +public:
 +    // 构造函数
 +    PtrStr(): ptr(new int()) {}
 +
 +    // 拷贝构造函数
 +    PtrStr(const PtrStr& rhs):ptr(new int())
 +    {
 +        std::cout << "call copy cstr\n";
 +        *ptr = *(rhs.ptr);
 +    }
 +
 +    // 拷贝赋值运算符
 +    PtrStr& operator=(const PtrStr& rhs)
 +    {
 +        std::cout << "call copy assignment\n";
 +        if(this == &rhs)
 +        {
 +            return *this;
 +        }
 +        // 如果不需要 reallocate
 +        *ptr = *(rhs.ptr);
 +
 +        // 如果需要 reallocate
 +        // delete ptr;
 +        // ptr = new int(*rhs.ptr);
 +        return *this;
 +    }
 +
 +    // 移动构造函数
 +    PtrStr(PtrStr&& rhs) noexcept 
 +        :ptr(rhs.ptr)
 +    {
 +        std::cout << "call move cstr\n";
 +        rhs.ptr = nullptr;
 +    }
 +    
 +    // 移动赋值运算符
 +    PtrStr& operator=(PtrStr&& rhs)
 +    {
 +        std::cout << "call move assignment\n";
 +        if(this == &rhs)
 +        {
 +            return *this;
 +        }
 +        delete ptr;
 +        ptr = rhs.ptr;
 +        rhs.ptr = nullptr;
 +        return *this;
 +    }
 +
 +    // 析构函数
 +    // 析构函数只能释放当前对象拥有的资源
 +    ~PtrStr()
 +    {
 +        std::cout << "call dstr\n";
 +        delete ptr;
 +    }
 +private:
 +    int* ptr;
 +};
 +
 +int main(int argc, char const *argv[])
 +{
 +    PtrStr myStr;
 +    PtrStr myCopyStr = myStr;
 +    PtrStr myMoveStr = std::move(myStr);
 +    PtrStr myCopyAss, myMoveAss;
 +    myCopyAss = myCopyStr;
 +    myMoveAss = std::move(myMoveStr);
 +    return 0;
 +}
 +
 +</code>
 +==default 关键字==
 +  * 只对特殊函数有效
 +==delete 关键字==
 +  * 对所有函数都有效
 +  * 表示函数**无法被调用**
 +  * 定义方式:
 +<code cpp>
 +void fun(int) = delete;
 +</code>
 +  * 和未声明的区别:一个是无法调用,一个是找不到
 +    * 无法调用的一个大的影响是,某些合成函数不会自动进行合成。
 +  * 不要为移动构造 / 赋值引入 ''delete''
 +    * 希望可以拷贝,但不能移动:只需要定义拷贝构造即可。编译器不会合成移动
 +    * 如果不需要拷贝,那么申明拷贝构造为 ''delete'' 即可。由于拷贝构造的存在,编译器依然不会合成移动
 +    * C++17以及以后:C++ 17 中,移动初始化会被其他方式顶替。C++17 以后的编译器会忽视被删除的移动构造函数。
 +==特殊成员的合成行为列表==
 +  * 行:用户声明(定义)的特殊函数
 +  * 列:基于用户声明的特殊函数,编译器是否声明,或者生成合成函数的结果。
 +  * 红色:可能会被废除的行为(查看标准)\\ 
 +{{ :cs:programming:cpp:courses:cpp_basic_deep:smf.jpg?600 |}}\\ 
 +[[https://www.youtube.com/watch?v=vLinb2fgkHk|Ref: Engineering Distinguished Speaker Series: Howard Hinnant ]]
 +====字面值类 / 成员指针 / bind====
 +===字面值类===
 +字面值类(//Literal class//)指可以用于构造**编译期常量对象类型**的类。要求:
 +  * 所有数据成员必须是数据类型
 +  * 构造函数必须是 ''constexpr'' 或者 ''consteval'' 类型的构造函数
 +  * 必须要有一个平凡的析构函数
 +  * 成员函数必须是 ''constexpr'' 或者 ''consteval'' 类型
 +<<WRAP center round tip 100%>
 +  * ''constexpr'' 函数:可以在编译期和运行期调用的函数
 +  * ''constveal'' 函数:只能返回编译期常量的函数(意味着其参数也应该是编译期常量)
 +</WRAP>
 +<WRAP center round info 100%>
 +C++ 14 之后允许在 ''constexpr'' 函数内部实现复杂的逻辑,因此取消了在 C++11 中默认 ''constexpr'' 函数的 ''this'' 指针只读的限制。
 +</WRAP>
 +
 +<code cpp>
 +class Str
 +{
 +    public:
 +    // 常量版本
 +    constexpr Str(int val):x(val) {};
 +    // 运行期执行版本
 +    Str(double val):x(val) {};
 +
 +    // 平凡的析构函数
 +    ~Str() = default;
 +
 +    // 接口成员函数
 +    // 需要是 constexpr 或 consteval
 +    // C++14 之后默认 constexpr 函数不带 const
 +    constexpr int fun() const
 +    {
 +        return x + 1;
 +    }
 +    private:
 +    // 字面值类型
 +    int x = 3;
 +};
 +
 +// error
 +// 字面值类要求构造函数的类型必须是 constexpr 或 consteval
 +constexpr Str Stra(1);
 +</code>
 +
 +==注意 constexpr 与 consteval 的混用问题==
 +由于 ''constexpr'' 函数在编译期和运行期均可以被调用;因此 ''constexpr'' 内部允许存在运行期的构造函数版本。但如果使用该版本进行构造,那么得到的对象不是编译期常量对象,因此不能调用 ''consteval'' 版本的成员函数:
 +<code cpp>
 +// error, b 不是常量表达式,无法调用 consteval 成员
 +class Str
 +{
 +    //....
 +    consteval int fun() const
 +    {
 +        return x + 1;
 +    }
 +}
 +int main {
 +    // error, b 不是常量表达式,无法调用 consteval 成员
 +    Str b(3.0);
 +    b.fun();
 +}
 +</code>
 +===类成员指针===
 +C++ 允许定义对指定类域成员进行访问的指针。
 +<WRAP center round info 100%>
 +类成员指针**不支持**指针的**相减**操作。
 +</WRAP>
 +
 +==类成员指针声明==
 +声明只要求有**类**的声明,不需要类的定义:
 +<code cpp>
 +class Str
 +{
 +    public:
 +    int x;
 +    void fun() {};
 +};
 +int main(int argc, char const *argv[])
 +{
 +// 可以用 auto 简化声明的写法
 +
 +// 数据成员指针的声明
 +// 类名 + 指针名
 +int Str::*mem_ptr = &Str::x;
 +
 +// 成员函数指针的声明
 +// 返回类型 + 域::函数指针名 + 参数列表
 +void (Str::*memFunc_ptr)() = &Str::fun;
 +}
 +</code>
 +==通过成员指针访问==
 +当通过成员指针访问指定成员时,访问的是具体对象中的成员。因此,成员必须实例化。访问方式有两种:
 +  * 使用成员指针访问:操作符 ''.*''
 +  * 使用对象指针访问:操作符 ''->*''
 +<code cpp>
 +int main(int argc, char const *argv[])
 +{
 +    // 访问前必须初始化类对象
 +    Str obj;
 +    // 定义类指针
 +    Str *ptr_obj = &obj;
 +    // 通过成员指针访问 x
 +    obj.*mem_ptr;
 +    // 通过成员指针访问函数 fun
 +    // 注意括号不能省
 +    (obj.*memFunc_ptr)();
 +    // 通过类对象指针访问 x
 +    ptr_obj->*mem_ptr;
 +    // 通过类对象指针访问成员函数 fun
 +    (ptr_obj->*memFunc_ptr)();
 +}
 +</code>
 +===std::bind===
 +''std::bind'' 允许将成员函数标定到一个特定的对象上,从而使得可以通过调用该对象来对成员函数进行调用。该函数定义于 ''<functional>'' 头文件中,写法如下:
 +<code cpp>
 +std::bind(&className::Function, classInstance, placeholder...);
 +</code>
 +应用实例:
 +<code cpp>
 +class MyClass {
 +public:
 +    void display(int x) {
 +        cout << "Display called with value: " << x << endl;
 +    }
 +
 +    int add(int a, int b) {
 +        return a + b;
 +    }
 +};
 +
 +int main() {
 +    MyClass obj;
 +
 +    // 使用 std::bind 绑定 display 成员函数
 +    auto boundDisplay = std::bind(&MyClass::display, &obj, std::placeholders::_1);
 +    boundDisplay(10);  // 调用 boundDisplay,相当于 obj.display(10)
 +
 +    // 使用 std::bind 绑定 add 成员函数
 +    auto boundAdd = std::bind(&MyClass::add, &obj, std::placeholders::_1, std::placeholders::_2);
 +    int result = boundAdd(5, 7);  // 调用 boundAdd,相当于 obj.add(5, 7)
 +    cout << "Result of add: " << result << endl;
 +
 +    return 0;
 +}
 +</code>