# 纲要
> 主干纲要、Hint/线索/路标
- **类数据成员的初始化**
- 成员初始化列表
- 类内初始化项
- **类的数据成员**
- 非静态数据成员
- **静态数据成员** static
- **可变数据成员** mutable
%%
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
<br><br>
# 类的数据成员的初始化
## 初始化方式 ⭐
> 参见说明 [^1] (P65)
区分两类:
- 对于 "**非静态数据成员**",初始化方式包括两种:
- ==**构造函数初始化列表**==,也称 "**==成员初始化列表==**" (*constructor initializer list* / *member initializer list*)
- **==类内初始化项==**,也称 "**==默认成员初始化项==**" (since C++11) (*in-class initializer* / *default member initializer*)
- 对于 "**静态数据成员**",初始化方式包括:
- 对于 **`inline` 或 `constexpr` 静态数据成员**,只能采用 "**==类内初始化==**"。
- 对于 `constexpr`,其初始化将在 "**编译时完成**"
- 对于 **`const` 修饰的 "整数类型 or 枚举类型" 的静态数据成员**,"**==类内初始化==**" or "**==类外初始化==**" 均可。
- 当采用 "**类内初始化**" 时,其初始化将在 "**编译时完成**"。
- 本质上是编译器应用了 "**==常量传播==**" 优化,**省略了为该静态成员变量申请内存**,而直接**在所有使用该静态成员变量值的地方**进行**常量值替换**。
- 对于其他静态数据成员, "**==必须==在==类外==进行定义和初始化**"。
> [!NOTE] "成员初始化列表"与 "类内初始化" 同时存在时,**==前者的优先级更高==**,**前者将覆盖后者的值**
>
> ```cpp
> class MyClass {
> public:
> MyClass () = default; // 类内初始化 `data=10` 将被应用
> MyClass (int x) : data (x) {} // data 的类内初始化值被覆盖
> private:
> int data = 10;
> };
> ```
>
> [!important] 必须进行 "显式初始化" 的情况(否则编译错误) [^1] (P259)
>
> 1. 非静态数据成员是 "**==引用类型==**" 或者 **"==`const`类型==**" 时;
> 2. 非静态数据成员是 "**类类型**" 且不具有 "**默认构造函数**" 时。
>
> ```cpp title:member_init_list.cpp
> class MyClass {
> public:
> int value;
> const int cstValue;
> OtherClass &obj; // 引用数据成员, 只能用初始化列表进行初始化;
> // 使用构造函数的成员初始化列表来初始化非静态数据成员
> MyClass(int v1, int v2, OtherClass &rh)
> : value(v1), cstValue(v2), obj(rh) {}
> };
> ```
>
> [!caution] 未被 "显式初始化" 的的数据成员的默认值
>
> - 对于**非静态数据成员**,未被显式初始化时,将执行 "**默认初始化**" 或 "**零初始化**"(发生在进入构造函数体之前)
> - (取决于其所属对象本身是被 "默认初始化" 还是 "值初始化") 。
>
> >
>
> - 对于**静态数据成员**,其初始化**分为两个阶段**:
> - (1)**静态初始化**阶段,由于无显式初始化项,故执行 **==零初始化==**;
> - (2)**动态初始化**阶段,由于无显式初始化项,故执行**默认初始化**
> - 对于类类型,则是调用默认构造函数;
> - 对于基本数据类型,则什么也没做,最终表现为 "**零初始化**"。
>
> [!caution] 在构造函数体内给成员变量赋值,不属于 "初始化",而是 "赋值"。
>
> 示例:下例中的成员变量 `x` 和 `y` 由于未被显式初始化,因此实际上是先**在构造函数体之前被进行了 "==默认初始化=="**(对这两个基本类型的非静态变量而言是随机值),**然后才在函数体内被 "重新赋值"** 。
>
> ```cpp
> class MyClass {
> int x;
> double y;
> const char ch;
> int &ri;
> public:
> MyClass(int i, double d, char c) : ch(c), ri(i) { // const 与引用的成员必须显式初始化
> x = i; // 在构造函数体内赋值.
> y = d; //
> // ch = c; // 错误, const 成员不能被赋值.
> // ri = i; // 错误, 引用没有被初始化, 不能为其赋值.
> }
> };
> ```
>
>
<br><br>
### 成员初始化列表
成员初始化列表跟在**构造函数的参数列表**之后,以冒号 `:` 开始,列出一个或多个成员及其初始化值。
成员初始化列表只能用于:
- (1)**初始化 =="非静态"数据成员==**;
- (2)**调用 "其他构造函数"** 或 "**基类的构造函数**"
> [!NOTE] 成员初始化列表必须放在 "类定义" 处,而在声明处不能使用
>
> ```cpp
> class MyClass {
> public:
> MyClass(int a, int b); // 声明
> private:
> int x, int y;
> };
>
> MyClass::MyClass(int a, int b) : x(a), y(b) {} // 定义
> ```
>
>
> [!NOTE] 成员初始化列表**只声明 "用于初始化成员的值"**,**==不影响==初始化的具体执行顺序**。
>
> **成员变量的初始化顺序只取决于==类定义中声明的出现顺序==,与该顺序一致**。
>
> ```cpp
> class X {
> int i;
> int j;
> public:
> // 由于类定义中j在i之后才出现, 因此下面语句是错误的!
> // 初始化列表不影响初始化的执行顺序, 成员变量的初始化顺序只取决于类中的声明顺序. 所以下面将会先用"未被初始化的`j`"来初始化`i`, 然后采用val对`j`进行初始化.
> X(int val) : j(val), i(j) { }
> // 因此, 最好使用参数值来初始化成员变量, 而不是用一个成员变量初始化其它成员变量.
> };
> ```
>
>
#### 在成员初始化列表内调用 "其他构造函数"
这样的构造函数称之为 "**委托构造函数**",即调用类内的另一个构造函数来初始化成员。
```cpp
class MyClass {
int x;
double y;
public:
MyClass(int i) : x(i), y(0) {}
// 委托构造函数, 委托另一个构造函数来完成初始化
MyClass(int i, double d) : MyClass(i) {
y = d;
}
};
```
#### 在成员初始化列表内调用 "基类构造函数"
若一个类继承自另一个类,则可以使用基类的构造函数来**初始化继承来的成员**。
```cpp
class Base {
int x;
public:
Base(int i) : x(i) {}
};
class Derived : public Base {
public:
using Base::Base;; // 继承构造函数
};
```
<br><br>
### 类内初始化项
"**类内初始化项**" 允许**在类定义中直接为 "非静态数据成员" 提供初始值**,供**创建实例对象时进行初始化**,适用于基本类型、类类型、数组等成员。( since C++11)
类内初始化只能使用以下两种形式:
- 使用 **`=` 符号** 的 "**拷贝初始化**" 形式
- 使用 **花括号** 的 "**列表初始化**" 形式 (直接列表初始化 or 拷贝列表初始化)
```cpp
struct MyClass {
int x = 10; // 类内初始化
double y{5.5}; // 类内初始化: 使用花括号进行列表初始化
const char ch = {'G'}; // 类内初始化;
// 对类类型进行类内初始化
std::string classTypeMember = "example";
// 对容器类型类型进行类内初始化
std::vector<int> vectorMember = {1, 2, 3};
};
```
<br><br>
## 初始化时机与顺序
###### 对 "**非静态数据成员**"
- 初始化时机:**进入构造函数体之前**,"**成员列表初始化值**" 将覆盖 "**类内初始值**"。
- 初始化顺序:只取决于 **==类定义中声明的出现顺序==,与该顺序一致**。
###### 对 "**静态数据成员**"
其初始化分为 "**静态初始化**" (**==编译时==**) 与 "**动态初始化**" (**运行时**)两个阶段,
且**所有==静态初始化==(可在编译期确定的值) 会在动态初始化之前完成**,参见 [[02-开发笔记/01-cpp/cpp 基本概念/cpp-初始化声明|cpp-初始化声明]] 。
初始化时机:
- (1)满足下列条件的静态数据成员,其将在为 "**静态初始化阶段(==编译时==)**" 完成初始化(**==常量初始化==**):
- (1)使用 "**==类内初始化值==**" 的方式(即声明为 **`inline`、 `constexpr`**;或者 **`const` 修饰且是整型或枚举类型**)
- (2)为 **==字面值类型==**,如基本数据类型、指针类型、枚举类型;
- (3)初始化表达式为 "**==常量表达式==**";
- (2)其他静态数据成员,其值在:
- (1)在 "**静态初始化阶段**" 被 **==零初始化==**;
- (2)随后在 "**动态初始化阶段** 根据初始化式 "Initializer" 的形式执行对应的初始化。
初始化顺序:**遵循下列规则**
- (1)**在同一个翻译单元中,静态数据成员的初始化按照它们==在代码中定义的顺序==进行。**
- 注意区分 "**==阶段==**",如果一个静态数据成员**依赖于另一个静态成员的值,且后者是==动态初始化==的**,可能导致未定义行为。
- (2)**不同翻译单元**中定义的静态数据成员的初始化顺序是**未定义的**。
<br><br>
# 类的静态数据成员
类的静态数据成员是**属于类本身的变量**,仅**存在一份数据==实体==**,**被类的所有实例对象所共享**,具有 "**静态存储持续性**",在整个程序运行期间有效。
静态数据成员的访问:
1. 可通过**类作用域**访问 `MyClass::`(可在不创建类的任何对象的情况下访问)
2. 也可通过**类的实例对象**访问;
> [!NOTE] 静态数据成员可以是"**==不完整类型==**",甚至**可以是其==本身所属的类类型==**。
>
> [!NOTE] `static` 关键字只需出现在类内部声明中,类外部定义静态数据成员、静态成员函数时,都不需要再加该关键字
#### 静态数据成员的初始化
初始化机制参见上文总结。
```cpp title:static_member_init.cpp
class MyClass {
public:
static int stcMem; // `non-const`静态数据成员只能在类外初始化
static int stcMem2;
static const int cstStcMem = 5; // `const`修饰且为整型或枚举类型的静态数据成员可以在类内初始化, 其将在编译时被初始化
static const int cstStcMem2; // 也可以在类外初始化(但将在运行时完成初始化)
static constexpr int stcCstexprMem = 66; // `constexpr`静态成员只能在类内初始化
};
// `non-const`的静态数据成员, 必须在类外进行初始化
int MyClass::stcMem = 42;
int MyClass::stcMem2; // 类外定义 (零初始化&默认初始化), 初值为0
// `const`的静态数据成员, 可以在类外初始化,也可以在类内初始化
const int MyClass::cstStcMem2 = 33;
```
`constexpr` 只能用于"常量类型"
> [!caution] 当需要使用 "`const` 整型或枚举类型静态数据成员" 地址时,必须进行类外定义!
>
> 类的**静态成员变量若为 `const` 整型或枚举类型,可在类内直接初始化**,而**==省略== "类外定义"** 。
>
> 在此情况下,编译器会应用 "常量传播" 优化,在所有**使用到该 "静态成员变量==值=="** 的地方直接替换为常量值,从而 **==直接省略为该静态成员分配内存==**。
>
> 但是,如果程序中有需要使用 "**该静态成员变量==地址==**" 的场景,例如**定义指向该 `const` 整型静态成员的指针或引用**时,则必须为其在 "**==类外定义==**"(若类内指定了初始值,则类外定义省略初始化项),**保证编译器为其分配内存**。
>
> ```cpp
> struct MyClass {
> static const int var = 5;
> static constexpr int var2 = 15;
> };
>
> const int MyClass::var;
> // const int MyClass::var2;
>
> int main() {
> cout << MyClass::var << endl;
> cout << MyClass::var2 << endl;
> const int& ref = MyClass::var; // 如果没有类外的`const int MyClass::var`定义, 则该句报编译错误.
> const int& ref2 = MyClass::var2; // `constexpr`则只影响编译期初始化, 即使没有类外定义, 也会为其分配内存.
> return 0;
> }
> ```
>
> ^mku4i3
<br>
#### 静态数据成员的定义说明
> [!tip] 通常,**类的静态数据成员的 "类外定义" 应当放在类对应的 `.cpp` 文件中,**防止重定义错误**
声明示例:
```cpp title:MyClass.h
class MyClass {
private:
static int myVar; // 声明静态数据成员
static int myArray[5]; // 声明静态数据成员
// 类内初始化的静态成员变量将在编译时就被完成初始化
static const int sCstInt = 10; // const且为整型, 可在类内直接初始化;
static constexpr double sCstExprVar = 3.14; // constexpr, 可在类内直接初始化;
};
```
```cpp title:MyClass.cpp
#include "MyClass.h"
int MyClass::myVar; // 类外定义, 默认初始化, 初始化为0;
int MyClass::myArray[5]; // 类外定义, 默认初始化, 所有元素初始化为0;
// 可改为在定义时显式初始化
int MyClass::myVar = 5; // 类外定义,显式初始化, 初始值为5.
int MyClass::myArray[5] = {1, 2, 3, 4, 5}; // 类外定义,显式初始化
// 只有在特定情况下需要对sCstInt进行类外定义
const int MyClass::staticConstInt;
// 对于sCstExprVar通常不需要类外定义
```
示例二:静态数据成员可以是"**==不完全类型==**",**==可以是其本身所属的类类型==**。
```cpp
class Bar {
public:
// ...
private:
static Bar mem1; // 正确: 静态数据成员可以是不完全类型, 甚至是其自身所属类类型
Bar *mem2; // 正确: 指针类型可以是不完全类型
// Bar mem3; // error: 非静态数据成员必须是完整类型
};
```
%%
![[_attachment/02-开发笔记/01-cpp/类相关.assets/IMG-类相关-329AE9DB583B0EA7C91BB692EB8A283C.png|450]]
%%
<br><br>
# 类的可变数据成员(mutable)
`mutable` 说明符声明一个**可变数据成员**(mutable data member)。
其特性包括:
- **在类的 ==const 成员函数== 中可被修改**。
- **==常量对象==中的 "可变数据成员",在任何可访问该成员的地方都允许被修改**
[!example] 可变数据成员在类的 `const` 成员函数中被修改的典型场景
缓存数据、延迟计算(惰性求值)、记录类实例的使用情况或状态(如引用计数、日志记录等)。
## 使用示例
`mutable` 的典型使用场景:
1. **缓存数据**(在**提供只读数据**的 `const` 成员函数中,若**数据不存在则先计算并缓存**,否则直接返回)
2. **状态追踪**(如引用计数、日志记录等)
3. **延迟计算**(惰性求值)
示例一:缓存数据[^1] (Item 16)
```cpp
class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
std::lock_guard<std::mutex> g(m); // 使用互斥量, 保证线程安全.
if (!rootsAreValid) { // 缓存不可用, 则进行一次计算, 并用rootVals存储.
...
rootsAreValid = true;
}
return rootVals;
}
private:
mutable std::mutex m;
mutable bool rootsAreValid { false };
mutable RootsType rootsVals {};
};
```
示例二:追踪每个 Screen 的成员函数被调用了多少次:
```cpp
class Screen {
public:
void some_member() const; // 允许在const成员函数中修改可变数据成员
void reset() ; // 与其他非常量成员一样, 能被正常修改
private:
mutable size_t access_ctr; // 可变数据成员
};
void Screen::some_member() const {
// const成员函数中修改mutable数据成员
++access_str; // 保存一个计数值, 用于记录成员函数被调用的次数
};
void Screen::reset() {
// 非const成员函数中也可以修改mutable成员
access_ctr = 0;
}
```
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: 《Effective Modern C++》