t # 纲要 > 主干纲要、Hint/线索/路标 - **`this` 指针** - 类成员函数的**定义方式**:类内、类外 - 类成员函数的**说明符与限定符** %% # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% <br><br> # 类成员函数的定义方式 类成员函数的**定义**方式有两种: - **类内直接定义(==内联定义==)** - 在类中定义的成员函数默认为 "**==内联成员函数==**"( `inline` ),当修改函数的实现时**需要重新编译包含类声明的所有文件**。 - **类外定义**(常规定义) - 在类外定义成员函数可以**将接口(声明)与实现(定义)分离**,有助于代码的模块化。当修改函数的实现时**只需重新编译包含函数体定义的文件**。 > [!NOTE] **在类定义中直接定义的函数默认为==内联函数==** > 等价于:在类中声明函数原型,并在类外定义时显式声明为 `inline`。 > > ```cpp > struct MyClass{ > // 类中直接定义的函数, 默认为内联函数. > void inlineFunc() { ... } > }; > ``` > <br><br> # this 指针 类的"**非静态成员函数**" 具有一个 **==隐式参数==**:**`this` 指针,指向调用该成员函数的实例对象** [^1] (P231)。 (发生调用时,由编译器自动传递该隐藏参数) 在非静态成员函数内部,任何**对类成员的直接访问**都视为 **==`this` 的隐式引用==**,也可**显式通过 `this` 指针访问**。 > [!NOTE] 调用**对象的非静态成员函数**的语句 **`obj.func(param)` 等价于 `MyClass::func(&obj, param)`** > [!important] `this` 指针的类型 > > `this` 指针是一个常量指针(const pointer),默认类型是 **=="指向类类型 `non-const` 对象的常量指针==**" 。 > > 注: > > - `this` 指针不能绑定到一个常量对象上,故**不能在一个==const 对象==上调用 `non-const`成员函数**。 > - `const` 修饰类的成员函数时的作用是: 修改 `this` 指针的类型为 "**==指向类类型常量对象的常量指针==**" `const T* const`。 > > ^rnwv5h <br><br> # 类成员函数总览 类的成员函数加上**不同修饰符**时,具有**不同的特性**: - **内联成员函数**(`inline`),参见 [[#inline 说明符(内联成员函数)]] - **常量成员函数**(`const`),参见 [[#const 限定符(常量成员函数)]] - **静态成员函数**(`static`),参见 [[#static 说明符(静态成员函数)]] - **虚函数** (`virtual`) <br><br> # 类成员函数的说明符与限定符 ⭐ 类成员函数声明中可使用的**函数说明符与限定符**包括: - 位于**函数名前**: - `explicit` specifier - `inline` specifier - `static` specifier - `virtual` specifier - `friend` specifier - `constexpr` specifier - 位于**形参列表之后:(==严格按下列顺序==) - 位于尾置返回类型**之前**: - `const` qualifiers、`volatile` qualifiers - `&`、`&&` qualifiers(引用限定符) - `noexcept` specifier - 位于尾置返回类型**之后: - `=default`,`=delete`,`=0` specifier - `override` specifier - `final` specifier ```c++ [constexpr|static|explicit|virtual|friend|inline] 返回类型 函数名(参数列表) [const|volatile|&|&&|noexcept] -> 尾置返回类型 [=default|=delete|=0|override|final] : 成员初始化列表; ``` > [!caution] 必须同时出现在 "函数声明" 和 "函数定义" 处的修饰符 > > - 必须同时出现在 "函数声明" 和 "函数定义" 处的修饰符如下,这些修饰符是 "**函数签名**" 的一部分: > - **位于形参列表之后的所有说明符**:**`const`** 、**引用限定符**、`noexcept`、 `final`、`override`; > - `constexpr` 说明符; > - 只需要在 "声明" 时出现的修饰符:`explicit`、`inline`、`static` 、`virtual` > [!summary] 成员函数修饰符的功能总结 > ![image-20231015173549366|848](_attachment/02-开发笔记/01-cpp/类与对象/cpp-类成员函数的基本说明.assets/IMG-cpp-类成员函数的基本说明-9F4251CD6E08A60AA70FB7F958314FA4.png) <br><br> ## 声明顺序 函数的形参列表后,正确的声明顺序是: 1. `const` 限定符; 2. 引用限定符 `&` 与 `&&`; 3. `noexcept` 说明符 4. 尾置返回类型; 6. `=default`,`=delete`,`=0` 说明符 5. `override` 说明符; 7. `final` 说明符 说明示例: ```cpp title:func_specifier_user_order.cpp // 函数声明符的使用顺序说明 class Base { public: // 基类实现 virtual void someFunction() const & = 0; }; class Derived : public Base { public: // 函数形参列表后的函数声明符顺序: // 1. const限定符 // 2. 引用限定符&或&& // 3. noexcept说明符 // 4. 尾置返回类型 // 5. override说明符 // 6. final说明符 virtual auto someFunction() const & noexcept -> void override final { } }; ``` <br><br> ## inline 说明符(内联成员函数) 其作用是显式声明 "**==内联成员函数==**",有两种方式: 1. 在类定义**内部**使用 `inline` 显式**声明成员函数**,而在类外部定义; 2. 在类定义**外部**直接使用 `inline` 显式**修饰函数定义**。 只需在**声明或定义中的一处**使用 `inline` 关键字,无需同时使用。 > [!info] 类定义内直接 "定义" 的函数,默认为 "内联成员函数"。 ##### 用法示例 方式一: ```cpp struct MyClass { void OtherFunc() { ... } // 类内部定义函数, 隐式内联 inline void InlineFunc(); // 显式声明内联成员函数, 类外定义 }; void InlineFunc() { // 类外对已声明的内联函数进行定义 // ... } ``` 方式二: ```cpp class MyClass { public: void InlineFunc(); // 类定义内只做声明, 不带有`inline`. }; // 类定义之外, 定义内联函数时使用`inline`修饰 inline void InlineFunc() { // ... } ``` 与在头文件中定义内联函数的原因一样,**"内联成员函数的定义"也应当与"类定义"放在同一头文件中**。 当其他文件引用该头文件时,都会看到这个函数的定义,**但由于它被视为内联,所以不会导致重复定义的链接错误**。 <br><br> ## explicit 说明符 `explicit` 说明符**只能**用于类中的 "**==构造函数==**" 和 "==**转换函数**==" [^1] (P265) , 其作用是:**声明该函数只能被==显式调用==**,而**禁止编译器隐式调用**(即禁止在需要隐式类型转换时调用)。 ##### 可能发生"隐式调用"的场景: 1. 执行**拷贝初始化**的时候(使用了"="运算符) ```cpp // 用 const char* 类型创建了一个 strng 对象并初始化, 调用了 string 的单参数构造函数 string s = "abcdefg"; ``` 2. 执行 **"赋值"** 的时候 ```cpp string s; s = "abcdefg"; // 若没有重载"="运算符, 则是先调用单参数构造函数创建了一个临时 string 对象, 再将其赋给 s; // 如果重载了对应的"="运算符, 则不会执行隐式转换, 不再调用单参数构造函数; ``` 3. **函数参数为类类型对象**,调用该函数时**传入的参数并非"类对象"**,但"类对象"具有接受该参数的构造函数,则编译器使用该参数**隐式调用了对应的构造函数**,隐式创建了类对象。 4. 函数返回类型为类类型时,同上。 <br><br> ## static 说明符(静态成员函数) `static` 修饰的成员函数为 "**静态成员函数**"。 类的静态成员函数**不包含** `this`指针,其属于 "**整个类**",而**不与某个特定实例所关联**。 特性如下: 1. **独立于对象实例**:可以在没有类的任何对象实例的情况下调用静态成员函数。 2. **仅限于访问静态成员**: - 由于没有`this`指针,静态成员函数内 **==只能访问类中的其他静态成员==**,**无法访问非静态成员**。 ```cpp class MyClass { public: static void staticFunction() { // 这里不能使用this指针 // 且只能访问静态成员(静态数据成员或其它静态成员函数) } int instanceVar; // 非静态成员变量 static int staticVar; // 静态成员变量 }; int MyClass::staticVar = 0; // 必须在类外初始化静态成员变量 void test() { MyClass::staticFunction(); // 无需创建对象实例 } ``` <br><br> ## const 限定符(常量成员函数) `const` 关键字可用于修饰 "**非静态成员函数**"(不可用于静态成员函数),表示为 "**==常量成员函数==**"。 其本质作用是 **"修改 ==this 指针== 类型**",使其由 "**指向非常量的常量指针**" `T* const` 变为 "**指向==常量对象==的常量指针**" `const T* const`。 因此[^4], const 成员函数具有以下特性: - 函数内 **不能修改 "==非静态数据成员=="**(除非声明为 `mutable` 成员;静态数据成员始终可修改); - 函数内 **不能调用 "non-const 非静态成员函数"**; - const 成员函数可被 ==**常量对象**== 直接调用 or 通过 "==**指向常量的指针或引用**== "调用。 > [!info] > > - **常量对象**只能调用 `const` 成员函数。 > - **非常量对象**可调用 “普通成员函数” 以及 "`const` 成员函数"。 > ![[02-开发笔记/01-cpp/类与对象/cpp-类成员函数的基本说明#^rnwv5h]] > [!NOTE] 若 `const` 成员函数需要返回 `*this`,则**返回类型需要为==常量引用==** > ```cpp > struct MyClass { > void myFunc() const { // 可被所有实例对象(非常量与常量)调用. > // 该函数内不能修改MyClass对象的任何非`mutable`的数据成员. > // ... > } > const MyClass& otherFunc() const { // 只能返回一个"常量引用" > // 该函数内不能修改MyClass对象的任何非`mutable`的数据成员. > // ... > return *this; > } > }; > ``` > 使用示例: ```cpp class MyClass { private: int value; public: MyClass(int v) : value(v) {} // const function. Promises not to modify any member variables. int getValue() const { return value; } // This function is not const, so it can modify member variables. void setValue(int v) { value = v; } }; int getValue (const MyClass& rs) { // 此处形参是const对象, 只能调用const声明的成员函数. // 如果这一成员函数未加const修饰, 则该语句会编译报错. return rs.getValue(); } int main() { const MyClass obj(10); int x = obj.getValue(); // This is okay. // obj.setValue(20); // 编译错误; obj是const对象, 不能调用普通成员函数. return 0; } ``` 基于 const 的成员函数重载: ```cpp struct Screen { Screen &display(std::ostream &os); const Screen &display(std::ostream &os) const; //... }; int main() { Screen myScreen(5,3); const Screen blank(5, 3); myScreen.display(cout); // 调用非常量版本 blank.display(cout); // 调用常量版本 } ``` <br><br> ## volatile 限定符 - `volatile` 对象**只能调用 `volatile` 成员函数**。 - `volatile` 成员函数**可以被 `volatile` 或 `non-volatile` 对象调用**。 - **==`volatile` 与 `const` 限定符彼此独立==,互不影响,可同时存在**, <br> ## 引用限定符 引用限定符(reference qualifier)用于声明 "**==非静态成员函数==**" 可以被**哪种值类别的对象**调用:**左值** 或 **右值** [^5]。 > [!important] 引用限定符本质上是 **"==限定 `this`== 所能指向的是左值还是右值"**。 引用限定符有两种: - `&`:该非静态成员函数**只能由==左值对象==调用** - `&&`:该非静态成员函数**只能由==右值对象==调用** > [!NOTE] 一个函数可以同时使用 "`const` 限定符"和" 引用限定符",该情况下 **==引用限定符必须跟在 const 之后==**。 > > `const &` 表示该成员函数**可被任何值类别的对象调用**,包括 `const` / `non-const` 左值,以及右值对象。 > 其中,优先匹配 `const` 左值对象。** > > [!caution] 如果一个成员函数具有 "引用限定符",则 "**同名同参数列表的所有版本**" 都必须带有 "引用限定符"。 > > 即如果定义了两个或以上的同名、同参数列表的非静态成员函数,则**必须对所有函数都加上引用限定符,或者都不加**。例如: > > ```cpp > struct MyClass { > void Func () &&; > // void Func () const; // error: 必须也加上引用限定符, 如下 > void Func () const &; > } > ``` > 说明示例: ```cpp title:ref_qualifers.cpp #include <iostream> using namespace std; struct MyClass { void show() & { // 该非静态成员函数只能由左值对象调用 cout << "Called by lvalue\n"; } void show() && { // 该非静态成员函数只能由右值对象调用 cout << "Called by rvalue\n"; } // 该非静态成员函数可用于任何类型的对象, 相当于const左值引用 // 优先匹配const左值对象使用, 但也可匹配non-const左值对象,或者匹配右值对象. void show() const & { cout << "Called by const lvalue\n"; } }; int main() { MyClass obj; // 左值对象. 调用 `void show() &`; obj.show(); const MyClass cst_obj; // const左值对象. 调用`void show() const &`; cst_obj.show(); MyClass().show(); // 右值对象. 调用 `void show() &&`; } ``` 引用限定符的一个典型使用场景是,**为右值对象实现高效的资源转移逻辑**,如下: ```cpp #include <vector> using namespace std; class ResourceHolder { private: std::vector<int> data; public: std::vector<int>& getData() & { // 用于左值对象 return data; // 返回左值. } std::vector<int> getData() && { // 用于右值对象,允许移动数据 return std::move(data); // 返回右值. } }; ResourceHolder createResourceHolder() { // 工厂函数, 返回一个右值对象 return ResourceHolder(); } int main() { ResourceHolder holder; auto data1 = holder.getData(); // 调用左值版本 auto data2 = createResourceHolder().getData(); // 调用右值版本,允许移动data } ``` <br><br> ## noexcept 说明符 用以**标识该函数**一定不会 "**抛出异常**" [^2]。 参见 [[02-开发笔记/01-cpp/函数相关/cpp-函数相关#`noexcept` 说明符|cpp-函数相关#noexcept说明符]] 。 <br><br> ## override 说明符 "`override`" 关键字用于明确指出**派生类中的该成员函数**是对 **"基类中==虚函数==**" 的重写。 使用 `override` 标记函数后,**编译器**将**检查该函数是否确实==匹配某个基类虚函数==**,如果不匹配则将报编译错误,有助于防止疏忽时误添加了一个新的独立函数而非覆写已有的虚函数。 > [!NOTE] 要对一个函数进行应用 `override` 标识 "**重写**",必须满足以下条件: > > - **基类函数必须是 `virtual` 的** > - **基类**和**派生类函数**必须满足: > - 函数名完全相同(除非是析构函数) > - 形参类型完全一致 > - 引用限定符、`const` 限定符必须完全一致 > - "**返回值**" 和 "**异常说明**" 必须兼容 > - 兼容是指,例如,基类返回基类类型,派生类返回派生类类型,这是允许的。 > > ```cpp title:override_keywords.cpp class Base { public: virtual void foo(int) { std::cout << "Base::foo(int)" << std::endl; } }; class Derived : public Base { public: // "override" 明确指出这是重写. 编译器将检查是否的确存在匹配的基类虚函数 virtual void foo(int) override { std::cout << "Derived::foo(int)" << std::endl; } }; ``` <br><br> ## final 说明符 C++中的 "`final`" 关键字有两个用途: 1. **禁止类被派生**:用于==**类声明**==时,表示**该类不能被继承**。 2. **禁止虚函数被派生类重写**:用于==**虚函数声明**==时,表示该**虚函数**在当前类的**任何派生类中不能被进一步重写**。 尝试继承一个 `final` 类,或者重写一个 `final` 虚函数会导致编译错误。 ```cpp title:final_class.cpp class Base final {}; // Base类不能被进一步派生 // class Derived : public Base {}; // error:Base是一个final类 ``` ```cpp title:final_virtual_func.cpp struct Base { virtual void SomeFunc() final; // 声明'final', 该虚函数不能在派生类中被重写 }; struct Derived : public Base { // void SomeFunc(); // error:尝试重写final函数 }; ``` <br><br> ## = default 说明符 `= default` 关键字用于指示编译器==**该成员函数使用编译器提供的默认实现**==, **只能**对具有 "**Synthesized 版本**" (即编译器提供的合成版本) 的成员函数使用,包括: - **默认构造函数** - **拷贝控制操作**相关的成员函数: - 析构函数 - 拷贝构造函数、拷贝赋值运算符 - 移动构造函数、移动赋值运算符 该修饰符可用于"**类定义内的函数声明**" 或 "**类定义外的函数声明**",用在类内时则编译器提供的实现将作为 **==内联函数==** 进行编译。 ```cpp title:default_exam.cpp struct MyClass { // 下列特殊成员函数全部使用编译器自动生成的版本 // 在类定义中使用 `=default` 声明的函数都是内联函数。 MyClass() = default; ~MyClass() = default; MyClass(const MyClass&) = default; MyClass(MyClass&&) = default; MyClass& operator=(const MyClass&); MyClass& operator=(MyClass&&); }; // 可在类外声明"=default", 得到非内联的成员函数. // (`=default`直到编译器"生成代码"时才需要, 因此可以不放在类内首次声明的位置) MyClass& MyClass::operator=(const MyClass&) = default; MyClass& MyClass::operator=(MyClass&&) = default; ``` > [!CAUTION] 在使用了 `=default` 的位置(无论是类内或类外),编译器将认为在此处 "定义" 该函数 > > 如果需要保证某些类型的信息(例如完整定义)必须出现在这些定义之前,**则务必注意声明顺序**。 > **可将 `=default` 放到 `.cpp` 文件中使用,并保证该语句前,所需的其他类型信息已可见**。参见[^3] > [!NOTE] `= default` 的常用场景 > > 常用于以下情况,显式地要求编译器提供合成版本: > > - 如果为类定义了自定义构造函数,则**编译器不会自动生成默认构造函数或拷贝构造函数**。 > - 如果显式地定义了移动构造函数或移动赋值运算符,则**编译器不会自动生成拷贝构造函数或拷贝赋值运算符**。 <br><br> ## = delete 说明符 `=delete` 关键字用于==**显式地声明"禁止"某个函数的使用**==,即表示该函数是 "**被删除**" 。 编译器会进行检查,若尝试使用被删除的成员函数会报编译错误。 例如: - **希望禁止类的实例被拷贝或赋值**时,可使用该关键字。 - 当对**析构函数**使用时,意味着该类**禁止"定义"对象变量或成员**,但**允许动态分配该类型变量**(能分配,但是不能释放) > [!tip] `= delete` 声明禁用函数时,应当放在类的 `public` 访问权限下 > > 这样,当外部代码调用该 `=delete` 函数时,编译器将**正确报告 "定义为删除" 的错误信息**。 > 如果是放在 `private` 下,则编译器的报错将只给出 "不可访问" 的错误。 > [!note] 如果一个类中 **“存在有数据成员”** 不能被默认构造、拷贝、复制或销毁,则**该类对应的成员函数自动被定义为"删除的"**。 > [!NOTE] `= delete` 可以对 "==**任何函数**==" 使用,不仅是类成员函数,也包括普通函数, > > ```cpp > // 示例: delete 用于普通函数的一个常见场景: 明确声明重载版本, 禁止实参的隐式转换. > bool isLucky(int number); > bool isLucky(char) = delete; // 拒绝 char > bool isLucky(bool) = delete; // 拒绝 bool > bool isLucky(double) = delete; // 拒绝额 float 和 double. > ``` > > [!quote] private 控制 > - C++11 之前通过将 "**成员函数声明为 `private` 且不定义**" 来实现**禁止调用**。 > - C++11 后,应当**使用 `= delete`** 来显式声明这一目的。 #### 使用示例 常见使用场景: 1. 用于 **==类中的成员函数==**,例如禁止拷贝构造或拷贝赋值; 2. 用于 ==**重载函数**==,通过明确**删除一些重载版本**,**避免 "实参的隐式类型转换"**; 3. 用于 **==模版特化==**,通过明确**删除某些模版特化**,**避免模版"被这些类型实例化"**。 示例场景一:用于**类的成员函数** ```cpp title:delete_exam.cpp struct MyClassB { // `=delete`关键字必须用在成员函数首次声明的位置 MyClassB() = default; MyClassB(const MyClassB&) = delete; // 禁止拷贝构造 MyClassB(MyClassB&&) = delete; // 禁止移动构造 MyClassB& operator=(const MyClassB&) = delete; // 禁止拷贝赋值 MyClassB& operator=(MyClassB&&) = delete; // 禁止移动赋值 // 声明禁止析构, 则该类禁止被"定义"(但允许动态分配), 但可以访问该类的静态成员. ~MyClassB() = delete; static void print() { std::cout << "This class cannot be instantiated. " << '\n' << "Because the destructor is deleted. " << '\n'; } }; int main() { MyClassB::print();     MyClassB* obj = new MyClassB();  // 正确, 可定义     // delete obj;  // error: 禁止释放 } ``` 示例场景二:用于**重载函数** ```cpp // 示例: delete 用于普通函数的一个常见场景: 明确声明重载版本, 禁止实参的隐式转换. bool isLucky(int number); bool isLucky(char) = delete; // 拒绝 char bool isLucky(bool) = delete; // 拒绝 bool bool isLucky(double) = delete; // 拒绝额 float 和 double. ``` 示例场景三:用于**模版特化** ```cpp template<typename T> void processPointer(T* ptr); template<> void processPointer<void>(void*) = delete; // 禁用void*对上述模版实例化 template<> void processPointer<const void>(const void*) = delete; // 禁用const void* 对上述模版实例化 template<> void processPointer<char*>(char*) = delete; // 禁用char*对上述模版实例化 template<> void processPointer<const char*>(const char*) = delete; // 禁用const char*对上述模版实例化 ``` <br><br> # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later # ♾️参考资料 # Footnotes [^1]: 《C++ Primer》 [^2]: 《Effective Modern C++》Item 14 [^3]: 《Effective Modern C++》Item22 [^4]: 《Effective C++》Item3 [^5]: 《C++ Primer》P483