%%
# 纲要
> 主干纲要、Hint/线索/路标
- 指针
- 指针类型的意义
# Q&A
#### 已明确
![[02-开发笔记/01-cpp/类型相关/cpp-指针相关#^8awn4s]]
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
> [!tip] 理解 "**指针**" 声明的含义,最好是从 "**右到左**" 的顺序阅读,**将 `*` 翻译为 `pointer to`**。
# 指针
C++中创建指针时,只会**分配用来存储地址的内存**,不会分配用来**存储指针所指向的数据的内存**(即**野指针**)
```cpp
int * fellow;
*fellow = 233333; // 段错误, 指针没有被初始化, 可能指向任何位置, 是完全未知的;
```
> [!NOTE] C++中的单目运算符 `&` 与 `*`
>
> - `&` :"取址运算符",获取数据对象的地址,将**返回一个指向数据对象的类型的指针**,
> - `*`:"解引用/间址访问运算符",应用于一个指针时,表示**访问该指针所指地址处的内存空间**
<br>
## 指针类型的意义
> ❓<font color="#c0504d">指针只存储地址,为什么对指针需要声明所指的类型?</font> ^8awn4s
「指针类型」表示的是 "**指针所指对象的类型**",指示了**编译器==如何解释==某个特定地址起始的 "==内存内容==" 及其==大小==**[^1]:
- "**地址**" 只指示了 **对象所占内存空间的==起始地址==(所占连续字节的最低地址)**;
- "**指针类型**" 则指明了 **==对象的字节大小==** 以及 **==对内存内容的解读方式==**。
> [!NOTE] 正因如此,通用指针 `void*` 仅能存储一个地址,而无法通过该指针访问内存地址上的对象
> [!info] 对指针进行 "强制类型转换",只改变指针类型,即对指针的解读方式
> [!NOTE]
>
> **指向 `int` 的指针和指向 `double` 的指针,所存储的地址都占 8 字节**,唯有明确指针类型后,才能够明确:
>
> 1. **使用==间址运算符==时 `*p` 如何解读**?(从起始地址起,读 4 字节还是 8 字节?)
> 2. **对==指针进行算术运算==时,需要==基于指针所指地址==加减多少==偏移量==**?
>
> [!NOTE] C++中能独立分配且用原生指针指向的最小对象是 `char` 类型对象[^7]
>
> 机器最小寻址单位是字节,C++标准规定 `char` 类型只占 1 字节。
<br><br><br>
## 指针运算
#### 解引用运算符
对指针使用**解引用运算符 (间址运算符) `*`** 表示 **访问指针所指的内存空间**。
#### 方括号运算符
方括号运算符用于**访问指针指向的数组元素**,等价于**对指针进行=="算术运算" 后再解引用==**。
表达式 `ptr[idx]` 等价于 `*(ptr + idx)`:
编译器会**基于指针当前指向的内存地址**,计算得到**第 `idx` 个数据对象的地址**,然后**访问该内存位置的值**。
#### 指针的算术运算
指针类型仅支持下列**基本运算**,会以 "**指针==所指向的数据类型的大小==**" 作为偏移量进行自动计算结果地址。
- **指针与整型间的==加减==运算**、**指针的自增自减**
- **两指针间的加减运算**
- **两指针间的比较**
如要对**指针所存的地址**进行复杂运算,例如**乘除、位运算**等,可先**用 `uintptr_t` 存储指针地址**,再进行操作。
> [!NOTE] `uintptr_t` 为无符号整型,用于存储指针地址,位于头文件 `<stdint.h>` 中
>
> `uintptr_t` 主要用于需要**将指针所存地址当做 "整数" 操作** 而进行复杂运算的场景,例如计算**乘除、取模、位运算**。
>
> 示例:求给定地址按 2 的整数幂上取整对齐后的地址
>
> ```cpp
> void* align_pointer(void* ptr, size_t alignment) { // 要求alignment是2的整数次幂
> uintptr_t raw = reinterpret_cast<uintptr_t>(ptr); // 将指针转为uintptr_t类型.
> uintptr_t aligned = (raw + alignment - 1) & ~(alignment - 1); // 上取整对齐2的整数次幂alignment
> return reinterpret_cast<void*>(aligned);
> }
> ```
>
###### (1)指针加减整数值 & 自增自减
对**指针变量 $+1$ (或 $-1$)** ,则增加(或减少)的**地址量**等于**所指向的类型占用的字节数**;
> [!NOTE] 对于指向 T 类型的指针 `T* p = addr`,则 `p + i` 的值等于 `addr + sizeof(T) * i`。
```cpp
int arr[] = {1, 2, 3};
int* p = arr; // p指向 arr[0]
p++; // p指向 arr[1], 以sizeof(int)作为自增的偏移量.
```
###### 两指针相减
- **两个指针相减**的结果为**它们之间的==元素数量**==,即 "**两地址之差除以该数据类型的大小**"
- (参与运算的**两指针必须指向==同一数组中的元素==**)
- 两指针相减的结果类型为 **`ptrdiff_t`,带符号类型,定义于 `<cstddef>` 头文件中**。
```cpp
#include <cstddef>
int* p1 = &arr[3];
int* p2 = &arr[0];
ptrdiff_t n = p1 - p2; // n == 3
```
###### 指针比较
指针之间可以进行比较操作,如等于(`==`)、不等于(`!=`)、小于(`<`)、大于(`>`)、小于等于(`<=`)、大于等于(`>=`)。这些操作通常用于**确定两个指针的相对位置**或**检查指针是否指向同一个内存位置**。
```cpp
if (p1 > p2) {
// p1 指向 p2 后面的内存地址
}
```
<br><br><br>
## C/C++中指针运算对应的汇编指令
> 参见[^2]
> [!example] C/C++中指针运算对应的汇编指令示例
> ![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-指针相关.assets/IMG-cpp-指针相关-1A1D68AC9F552E06E74A733A19B612CD.png|790]]
>
> ![[_attachment/02-开发笔记/01-cpp/类型相关/cpp-指针相关.assets/IMG-cpp-指针相关-D9EA2689798633E53755D145EBA30A61.png|778]]
>
> ^0aldns
<br><br><br>
# "指向常量的指针"与"常量指针"
> 参见 [^6]
#### 指向常量的指针 pointer to const
指向常量的指针: **不能通过该指针来修改对象内容**(底层 `const`)
> [!NOTE]
>
> "**指向常量的指针**",以及"**指向常量的引用**",都是仅**对"指针"或"引用"本身的操作做了限制**,即**不能通过该指针或该引用来修改所指对象的值**,但**并未限定对象本身必须是常量,其所指的对象可能是变量,对象本身的值是可修改的**,只是不能通过该指针或引用来修改。
```cpp
// 两种声明等效,都是指向int常量的指针.
const int *p; // 指向整型常量的指针
int const *p; // 指向整型常量的指针
```
#### 常量指针 const pointer
常量指针:**指针所指的"位置"不能改变**,即 **"指针"本身是个常量**(顶层 `const`)
- 常量指针所指位置不可变, 但**指针==所指向的位置中的内容可变==**;
- 常量指针**必须在声明的同时对其初始化**,不允许先声明一个指针常量随后再对其赋值,这与声明一般的常量是一样的。
```cpp
int * const p; // 指向整型的常量指针
const int * const p; // 指向整型常量的常量指针
int const * const p; // 指向整型常量的常量指针
```
<br><br><br>
# "数组指针"与"指针数组"
```cpp
// 运算符`[]`的优先级高于*, 因此`*pa[3]`表明这是一个"包含三个指针"的指针数组.
// 当使用括号时`(*pa)[3]`, 这表明这是一个指向 "包含三个元素的数组" 的指针
T *pd[3] // an array of 3 pointers 指针数组. 数组里包含3个指向T类型的指针.
T (*pd)[3] // a pointer to an array of 3 elements 指向"包含3个T类型元素的数组类型"的指针
```
#### 数组指针—指向数组的指针
指向数组的指针——Pointer to a array
```cpp
// 指向数组的指针
int *p = new int[10]; // 指向int型变量或数组的指针; 指针类型为 int(*)
int a[3] = {1, 2, 3}
int (*p)[3] = &a; // 指向"包含3个int型元素的数组"的指针; 指针类型为 int(*)[3];
```
#### 指针数组
指针数组——Array of pointers
```cpp
// 指针数组
int *p[10]; // 包含10个int*指针变量的指针数组
int b = 10;
p[0] = &b;
// 包含5个"指向长度为10的int型数组的指针"的指针数组; 5个指针, 每个指针类型都是int(*)[10].
int (*p[5])[10];
int a[10];
p[0] = &a;
// 函数指针数组
// 包含3个函数指针的数组, 每个函数指针的类型为 const double *(*)(coust double *, int);
const double * (*pa[3])(const double *, int);
```
<br>
# 指向指针的指针
```cpp
int val = 1024;
int *pi = &val; // 指向一个int型的数
int **ppi = π // 指向一个int型的指针
int ***pppi = &ppi; // 指向"指向一个int型的指针"的指针
```
<br><br><br>
# 字符指针
#### 将字符串字面量初始化或赋值给字符指针
> [!info] "**字符串字面量**" 是一个**左值**,本质上是 "**指向==常量字符数组==的指针**"
>
> 因此,只能且必须用一个 `const char*` 指针来指向 "字符串字符量"。
>
> ```cpp
> const char* ptr = "Hello"; // ptr指向的内容不可被修改
> ```
>
"**字符串字面量**" 是指向 "**常量字符数组的指针**",该 **常量字符数组** 位于内存空间中 "**==初始化数据段中的 `.rodata` 只读数据段==**",程序中**所有==相同内容的 "字符串字面量"== 都指向该区域的==同一块内存地址==**。
```cpp
const char* str1 = "Hello World";
const char* str2 = "Hello World";
assert(str1 == str2); // str1与str2指向同一内存地址!
```
<br><br>
# void* 通用指针
`void*` 表示 "**指向==未知类型==对象的指针**",其仅能用于 "**==存储地址==**",但**不能对解引用**(所指类型未知),支持以下操作[^7]:
- 除 "**函数指针**" 与 "**类成员指针**" 外,指向任意类型的指针均可被赋值给 `void*` 指针(C++支持任意指针类型到 `void*` 的隐式转换)
- **比较两个 `void*` 指针是否相等**;
- **两个 `void*` 指针之间进行赋值**;
- **将 `void*` 指针==强制转换==为任意类型指针**(C++不支持 `void*` 到其他指针类型的隐式转换,必须是 "**强制类型转换**")
不支持以下操作:
- **不能对其解引用**;
- **不能对 `void*` 指针进行算术运算**;
![[02-开发笔记/01-cpp/类型相关/cpp-类型转换#^fmg7gb]]
![[02-开发笔记/01-cpp/类型相关/cpp-类型转换#^jj8rpd]]
<br><br>
# 函数指针
函数指针通过 **函数类型** 进行声明,指向某种 **==特定类型==的函数**(可指向该类型的任意函数,而非某个特定函数)。
函数指针的声明:`返回类型 (*指针变量名)(参数类型列表);`
示例:
```cpp
int foo(double, char); // 函数类型是`int(double, char)`.
int (*ptr)(double, char) = &foo; // 声明函数指针
int (*ptr)(double, char) = foo; // 与上语句等价
```
<br>
## 函数名用作函数指针
将**函数名**作为一个值使用时,该**函数名将自动被转为指针**(指向函数地址)。
> [!caution] **成员函数**的函数名不存在自动转换,需要通过 `&` **显式取址**,例如 `&MyClass::mem_func;`
> [!NOTE] 当函数存在==重载版本==时,**函数名无法被自动转换**,可通过 `static_cast<>()` **显式指明转换的==函数指针类型==**。
>
> 如下例所示,在用于 `std::function` 或 `std::bind` 时,都需要为函数名指定显式转换。参见 [^3]
>
> ```cpp
> void func(int arg) {}
> void func(int arg1, int arg2) {}
>
> int main(int argc, char* argv[]) {
> std::function<void(int)> fc1 = static_cast<void(*)(int)>(func);
> std::function<void(int,int)> fc2 = static_cast<void(*)(int, int)>(func);
> auto fc1_b = std::bind(static_cast<void(*)(int)>(func), 5);
> auto fc2_b = std::bind(static_cast<void(*)(int, int)>(func), 6, placeholders::_1);
> return 0;
> }
> ```
>
> 对于 "**==类的成员函数==**" 也是一样,当存在多个重载版本时,需要通过 `static_cast<>()` 显式指明转换的 **==函数指针类型==**。
> 示例如下,通过 `static_cast<void (MyClass::*)(param_list)>` 显式指明函数版本。
>
> ```cpp
> struct MyClass {
> void func(int) {}
> void func(double) {}
> };
>
> MyClass obj;
> auto func_int = std::bind(static_cast<void (MyClass::*)(int)>(&MyClass::func), &obj, std::placeholders::_1);
> auto func_double = std::bind(static_cast<void (MyClass::*)(double)>(&MyClass::func), &obj, std::placeholders::_1);
> ```
>
> ^03j3v1
<br><br>
## 函数指针数组
```cpp
// 函数原型
const double *f1(const double [], int);
// 对应的函数指针
const double * (*p1)(const double *, int) = f1;
// 对应的"函数指针"数组
// 注: 运算符[]的优先级高于*, 因此*pa[3]表示这是一个包含三个指针的指针数组.
// 而声明语句的其它部分, 则指出了该数组包含的元素类型是什么样的.
const double * (*pa[3])(const double *, int) = {f1, f2, f3};
// 通过"函数指针"数组中的函数指针调用函数:
const double *px = pa[0](av, 3); // 两调用语句等价
const double *py = (*pa[1])(av, 4);
// 一个指向"包含三个函数指针元素的数组"的指针
// 注:
// - 最内层的(*pb)表明这是一个指针
// - *(*pb)[3]表明这是一个指向"包含三个指针的指针数组"的指针
// - const double *(counst double *, int) 是数组中每个函数指针对应的函数类型;
const double *(*(*pb)[3])(const double *, int)) = &pa;
// pb指向一个函数指针数组.
const double *pz = (*pb)[0](av, 3); // 两调用语句等价
const double *pm = (*(*pb)[0])(av, 4); // 外层解引用是对函数指针解引用
```
<br>
## 函数指针别名
使用 `using`(**推荐**):
```cpp
using FuncType = int(int, int); // "函数类型"的别名
using FuncPtrType = int(*)(int, int); // "函数指针类型"的别名
```
使用 `typedef`:
```cpp title:func_pointer.cpp
int MyFunc(int, int) { return 0; };
typedef int FuncType(int, int); // "函数类型"的别名
typedef int (*FuncPtrType)(int, int); // "函数指针类型"的别名
typedef decltype(MyFunc)* FuncPtrType2; // 使用'decltype' 得到函数类型.
FuncType* pFunc = MyFunc;
FuncPtrType pFuncPtr = MyFunc;
FuncPtrType2 pFuncPtr2 = MyFunc;
```
<br>
## 使用函数指针
可直接**通过函数指针调用函数**,而**无需先解引用**。
可以为函数指针**赋值为 `nullptr` 或者 `0`**,表示**该指针没有指向任何一个函数**。
函数指针使用示例:
```cpp
// 函数原型
bool lengthCompare(const string&, const string&);
bool (*pf)(const string&, const string&); // 声明函数指针, 未初始化
pf = lengthCompare; // 函数名作为值使用, 直接被转为指针
pf = &lengthCompare; // 等价的赋值语句, 取址运算符是可选的
bool b1 = pf("hello", "goodbye"); // 直接通过函数指针调用函数
bool b2 = (*pf)("hello", "goodbye"); // 等价语句, 先对函数指针解引用再调用.
// 可为函数指针赋0值或nullptr, 表示不指向任何函数
pf = 0;
pf = nullptr;
```
<br>
### 函数指针作为形参
"函数类型" 不能作为**函数形参**或者**函数的返回类型**,但可以定义 **==函数指针==** 作为**形参或返回类型**,从而**传递或返回函数**
```cpp
//下面两种声明是等价的
void Foo(int i, void(*pFoo)(int));
void Foo(int i, void pFoo(int)); // 形式上是函数类型, 但实际上会自动被转为函数指针
```
```cpp
// 声明形参为函数指针
// 写法一: (形式上是函数类型, 但实际上会自动被转为函数指针)
void useBigger(const string &s1, const string &s2,
bool pf(const string&, const string&));
// 写法二: 显式指明是函数指针
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string&, const string&));
// 可直接将函数名当作实参使用, 会自动转为函数指针
useBigger(s1, s2, lengthCompare);
```
<br>
### 函数指针作为返回类型
定义一个返回类型为"函数指针"的函数:
格式为:`函数指针对应的函数返回类型 (*函数名(函数参数)) (函数指针对应的函数参数)`:
- `int (*FuncName(int))(int*, int);`
- `auto FuncName(int) -> int (*)(int*, int);` ← 或使用尾置返回类型
```cpp
// 一个指向`int(int*, int)`函数类型的函数指针:
int (*ptr)(int*, int);
// 当该类型的函数指针作为函数返回类型, 声明一个函数原型时:
// 该函数接受一个int参数, 返回一个`int(*)(int*, int)`的函数指针
int (*func1(int))(int*, int);
// 通过尾置返回类型的方式声明函数原型:
auto func1(int) -> int (*)(int*, int);
// 为函数指针或函数类型声明别名, 简化代码
using F = int(int*, int); // 通过using声明一个函数类型的类型别名
using PF = int(*)(int*, int); // 通过using声明一个函数指针类型的类型别名
F *func1(int); // 声明函数func1, 返回类型为指向函数类型F的指针
PF func2(int); // 声明函数func2, 返回类型为PF.
```
<br>
### 函数指针使用建议
直接使用函数指针类型显得冗长烦琐,可以使用类型别名 `typedef` / `using` 和 `decltype` 简化代码。
```cpp
using Foo = void(int, int) //Foo是函数
using pFoo = void(*)(int, int) //pFoo是函数指针类型
```
```cpp
// ------------------1.为函数类型声明别名
// 为一个函数类型声明别名
typedef bool Func1(const string&, const string&);
// 为lengthCompare函数的函数类型声明别名; 等价于上述语句
typedef decltype(lengthCompare) Func2;
// ------------------1.为函数指针声明别名
// 为一个函数指针声明别名
typedef bool (*Func1P)(const string&, const string&);
// 为一个指向lengthCompare函数的函数类型的函数指针声明别名
typedef decltype(lengthCompare) *Func2P; // Func2P是函数指针
// 通过using声明一个"函数类型"的类型别名
using F = int(int*, int);
F func1(int);
// 通过using声明一个函数指针类型的类型别名
using FuncType = decltype(foo);
Functype* funcptr = &foo; // 函数指针
using PF = int(*)(int*, int);
PF funcptr2 = &f; // 函数指针
```
<br><br><br>
# 类成员指针与成员函数指针
通过**类成员指针**访问数据成员,或是通过**成员函数指针**调用成员函数,<br>都需要使用 **`.*` 或 ` ->.*` 运算符** 将该指针**绑定到特定的对象上**。
- 通过 **类成员指针** 访问数据成员:
- `obj.*dataPtr`
- `objPtr->*dataPtr`
- 通过 **类成员函数指针** 调用成员函数:
- `(obj.*funcPtr)(args)`
- `(objPtr->*funcPtr)(args)`
> [!info] 两种成员指针访问运算符:`.*` 与 `->*` => 基于一个实例对象来解引用 "成员指针 or 成员函数指针"。
> [!example] 使用示例
>
> 这两个指针需要由 "**类的非静态成员的地址**" 进行初始化或赋值.
>
> ```cpp
> class MyClass {
> public:
> int mem_data = 5;
> void* mem_func (int, char) const { return nullptr; }
> };
>
> int main() {
> MyClass obj, *ptr = &obj;
> int MyClass::*memptr_data = &MyClass::mem_data;
> void* (MyClass::*memptr_func)(int, char) const = &MyClass::mem_func;
>
> // 使用成员指针, 成员函数指针.
> obj.*memptr_data = 10; // 访问数据成员
> (obj.*memptr_func)(11, 'G'); // 函数调用
> ptr->*memptr_data = 32;
> (ptr->*memptr_func)(99, 'g');
> }
> ```
>
>
<br>
## (1)类成员指针
成员指针(Pointer to members)是指向 "**==类的非静态数据成员==**" 的指针,指向**类中成员**,而非类的实例对象[^4]。
**直到==使用成员指针==时,才需要提供==成员所属的实例对象==**。
> [!NOTE] 对 "类的非静态数据成员" 取址 `&`,得到 "**指向该类成员的成员指针**"。
> [!NOTE] 类的静态成员不属于任何对象,无需特殊类型的指针,**类的静态成员由普通指针指向**。
<br>
### 类成员指针的类型
声明类成员指针:`指向类型 类名::*指针名 = &类名::数据成员名` ,即`T C::*pmem = &C::member;`
> [!NOTE] 成员指针中既包含 "类类型" 也包含指针所指的 "成员类型"
**类成员指针的类型**仅仅与它指向的 **==数据成员本身==** 是否为**常量**有关,与其**指向的对象**是否为常量无关。
- 指向 "**常量成员**" 的成员指针, 可以用以指向一个**非常量成员**, 但是将不能通过该指针来修改该非常量成员.
- 可以为指向 "**常量成员**" 的成员指针提供 "**常量或非常量对象**",但提供非常量对象时,不能通过该指针修改这一成员的值,而只能"读"。
- 可以为指向 "**非常量成员**" 的成员指针提供 "**常量或非常量对象**"。但提供常量对象时,不能通过该指针修改这一成员的值。
类成员指针声明示例:
```cpp
class MyClass {
public:
const int constMember;
int nonConstMember;
};
// 声明一个指向类的"常量成员"的成员指针
// (使用指针时, 可提供常量对象或非常量对象, 无论哪种情况, 对该成员都"只读")
const int MyClass::*const_ptr = &MyClass::constMember; // 指针类型是`const int MyClass::*`
// 声明一个指向类的"非常量成员"的成员指针
// (使用指针时, 可提供常量对象或非常量对象, 但提供常量对象时, 不能通过指针修改该成员数据)
int MyClass::*non_const_ptr = &MyClass::nonConstMember;
// 声明一个指向"常量成员"的成员指针, 该指针可用以指向一个非常量成员,
// 其作用是不能通过该指针来修改该非常量成员.
const int MyClass::*const_ptr2 = &MyClass::nonConstMember;
```
> [!NOTE] 类的数据成员通常声明为 `private`,该情况下不能直接获得数据成员的指针。
>
> 类可以提供一个公有的静态成员函数,**返回一个成员指针**。
>
> ```cpp
> class MyClass2 {
> private:
> int nonConstMember;
> public:
> explicit MyClass2(int x) : nonConstMember(x) {}
> // 提供一个类的静态成员函数, 为私有成员返回一个const成员指针.
> static const int MyClass2::* getMemberPointer() { // 返回成员指针, 要包含类作用域
> return &MyClass2::nonConstMember; // 将类的成员的地址作为一个成员指针.
> }
> };
>
> void exam2() {
> // 通过类的static成员函数获取一个指向其私有数据成员的成员指针
> const int MyClass2::*ptr = MyClass2::getMemberPointer();
> MyClass2 obj(99);
> auto res = obj.*ptr; // ptr是指针, 通过解引用运算符访问了obj对象的私有数据成员;
> cout << res << endl;
> }
> ```
>
<br><br>
## (2)成员函数指针
成员函数指针指向 **==类的成员函数==** [^5]。
成员函数指针通过 **==类作用域下的函数类型==** 进行声明,可指向 **类中同一函数类型的成员函数**。
> [!caution]
>
> - **类的成员函数名**与指针之间不存在自动转换,因此 **必须对类的成员函数显式取地址** 再赋给指针。
> - 若成员函数是 **const 成员** 或 **引用成员**,则声明成员函数指针时必须包含 **const 限定符** 或 **引用限定符**。
#### 使用方式
- **声明**成员函数指针:`返回类型 (类::*ptr)(参数列表) [const限定符或引用限定符] = &类::成员函数 `
- 通过成员函数指针**调用函数**:`(obj.*ptr)(args)`
```cpp
class MyClass {
public:
void memberFunc(int x) {
std::cout << "Value: " << x << std::endl;
}
int constFunc(int x) const {
std::cout << "Value: " << x + 5 << std::endl;
}
}
int main() {
// 声明成员函数指针
void (MyClass::*ptr)(int) = &MyClass::memberFunc; // 对成员函数必须显式取址
MyClass obj;
(obj.*ptr)(5); // 通过成员函数指针来调用obj对象的成员函数
MyClass *pObj = &obj;
(pObj->*ptr)(10); // 通过成员函数指针来调用指针pObj所指对象的成员函数
// 声明指向const成员函数的成员函数指针
int (MyClass::*ptr2)(int) const = &MyClass::constFunc;
const MyClass obj2;
(obj2.*ptr2)(5);
const MyClass *pObj2 = &obj2;
(pObj2->*ptr2)(10);
}
```
> [!example] 一个优雅的成员函数指针使用示例
>
> ```cpp
> class Screen {
> public:
> using Action = Screen& (Screen::*)(void); // 成员函数指针类型
> enum Directions { HOME = 0, FORWARD, BACK, UP, DOWN };
> Screen& move(Directions); // 其会根据传入参数调用下列成员函数.
>
> Screen& home() { cout << "move home" << endl; return *this; }
> Screen& forward() { cout << "move forward" << endl; return *this; }
> Screen& back() { cout << "move back" << endl; return *this; }
> Screen& up() { cout << "move up" << endl; return *this; }
> Screen& down() { cout << "move down" << endl; return *this; }
>
> private:
> static Action Menu[]; // 函数表: 指向成员函数指针的指针.
> };
>
> Screen::Action Screen::Menu[] = {
> &Screen::home,
> &Screen::forward,
> &Screen::back,
> &Screen::up,
> &Screen::down
> };
>
> Screen& Screen::move(Screen::Directions d) {
> return (this->*Menu[d])(); // `Menu[d]`获取成员函数指针, 通过`this->*`调用.
> }
>
> int main() {
> Screen myscreen;
> myscreen.move(Screen::HOME);
> myscreen.move(Screen::FORWARD);
> myscreen.move(Screen::BACK);
> myscreen.move(Screen::UP);
> myscreen.move(Screen::DOWN);
> }
> ```
>
<br>
#### 成员函数指针作为形参
示例: 形参为成员函数指针,可以指向**类中同一函数类型的任意成员函数**。
```cpp
#include <iostream>
class MyClass {
public:
void functionA() const {
std::cout << "Function A called!" << std::endl;
}
void functionB() const {
std::cout << "Function B called!" << std::endl;
}
};
// 定义一个参数为 "MyClass对象引用" 以及 "指向MyClass的const成员函数的指针" 的函数
void invoke(const MyClass& obj, void (MyClass::*func)() const) {
(obj.*func)(); // 使用成员函数指针调用成员函数
}
int main() {
MyClass myObj;
invoke(myObj, &MyClass::functionA); // 输出:Function A called!
invoke(myObj, &MyClass::functionB); // 输出:Function B called!
}
```
#### 为成员函数指针生成可调用对象
为 "**成员函数指针**" 生成 "**可调用对象**" 的四种方式:
- 使用 `function<>` 对象;
- 使用 `bind`
- 使用 `lambda`
- 使用 `mem_fn`
说明示例:
```cpp
int main() {
vector<string> vec {
"hello world",
"ZZZZZZ",
"",
"gdgsdf"
};
// 方式一: 使用function<>对象
function<bool(const string&)> fcn = &string::empty;
auto it1 = find_if(vec.begin(), vec.end(), fcn);
// 方式二: 使用bind绑定
auto fcn_b = bind(&string::empty, ::placeholders::_1);
auto it2 = find_if(vec.begin(), vec.end(), fcn_b);
// 方式三: 使用lambda
auto fcn_lam = [](const string& str) -> bool {
return (str.*(&string::empty))();
};
auto it3 = find_if(vec.begin(), vec.end(), fcn_lam);
// 方式四: 使用mem_fn
auto it4 = find_if(vec.begin(), vec.end(), mem_fn(&string::empty));
}
```
##### 方式一:使用function为成员函数指针直接生成一个可调用对象
> [!caution] `function<>` 包装成员函数指针时,其**首项必须额外添加一个参数**,用于**隐式的接收==执行成员函数的对象=="**。
需要对 function 中的函数类型做修改, 在参数列表中**额外添加一个==对实例对象的指针或引用==**,作为**首项参数**。
- 可以将**成员函数的地址**直接赋给function对象
- 可以将**成员函数指针**直接赋给function对象
在这一方式下, 在**调用function对象时可以传入不同的实例对象**。
示例一:
```cpp
class MyClass {
public:
void memberFunc(int x) {
std::cout << x << std::endl;
}
void constFunc(int x, int y) const { // const成员函数
std::cout << x << ", " << y << std::endl;
}
};
void exam1() {
// 1. 将成员函数的地址直接赋给function对象
std::function<void(MyClass&, int)> fcn = &MyClass::memberFunc;
std::function<void(const MyClass&, int, int)> fcn_const = &MyClass::constFunc;
MyClass obj;
const MyClass const_obj;
fcn(obj, 1);
fcn_const(obj, 2, 3);
fcn_const(const_obj, 4, 5);
// 2. 将成员函数指针直接赋给function对象,
void (MyClass::*ptr)(int) = &MyClass::memberFunc;
void (MyClass::*constPtr)(int, int) const = &MyClass::constFunc;
std::function<void(MyClass &, int)> fcn2 = ptr;
std::function<void(const MyClass &, int, int)> fcn_const2 = constPtr;
fcn2(obj, 11);
fcn_const2(obj, 22, 33);
fcn_const2(const_obj, 44, 55);
}
```
示例二:
```cpp
vector<string> vec;
function<bool(const string&)> fcn = &string::empty; // 使用function对象封装"函数指针"
auto it = find_if(vec.bgein(), vec.end(), fcn); // 找出第一个为空的字符串.
vector<string*> pvec;
function<bool(const string*)> fcn = &string::empty;
auto it = find_if(vec.begin(), vec.end(), fcn);
```
##### 方式二:使用bind为成员函数指针生成一个可调用对象
有两种方式:
1. 使用bind绑定成员函数指针时**不提供实例对象**,而**在调用bind生成的可调用对象时才传入实例对象**
参数列表中需要额外增加一项第一参数,**调用时需要传入一个对实例对象的指针或引用。**
(类似于方式一中function的用法;)
2. 使用bind绑定成员函数指针时**提供具体实例对象**,而后可以直接调用。
```cpp
void exam2() {
MyClass obj;
const MyClass const_obj;
void (MyClass::*ptr)(int) = &MyClass::memberFunc;
void (MyClass::*const_ptr)(int, int) const = &MyClass::constFunc;
// 2.1 使用bind绑定时 "不提供实例对象"
// std::bind(, args)的参数列表args中需要额外添加一项第一参数,
// 调用时需要传入一个对实例对象的指针或引用.
auto bind_func11 =
std::bind(&MyClass::memberFunc,
std::placeholders::_1, std::placeholders::_2);
auto bind_const_func11 =
std::bind(&MyClass::constFunc, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3);
bind_func11(&obj, 10);
bind_const_func11(&obj, 20, 30);
bind_const_func11(&const_obj, 40, 50);
auto bind_func12 =
std::bind(ptr, std::placeholders::_1, std::placeholders::_2);
auto bind_const_func12 =
std::bind(const_ptr, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3);
bind_func12(&obj, 10);
bind_const_func12(&obj, 20, 30);
bind_const_func12(&const_obj, 40, 50);
// 2.2 使用bind绑定时 "提供具体的实例对象"
// std::bind()的第一项参数接受成员函数指针,
// 第二项参数接受一个实例对象的引用或指针, 剩余参数为成员函数的参数列表.
auto bind_func21 =
std::bind(&MyClass::memberFunc, &obj, std::placeholders::_1);
auto bind_const_func21 =
std::bind(&MyClass::constFunc, &const_obj,
std::placeholders::_1, std::placeholders::_2);
auto bind_const_func211 =
std::bind(&MyClass::constFunc, &obj,
std::placeholders::_1, std::placeholders::_2);
bind_func21(1);
bind_const_func21(11, 22);
bind_const_func211(31, 32);
auto bind_func22 = std::bind(ptr, &obj, std::placeholders::_1);
auto bind_const_func22 =
std::bind(const_ptr, &const_obj,
std::placeholders::_1, std::placeholders::_2);
auto bind_const_func222 =
std::bind(const_ptr, &obj, std::placeholders::_1, std::placeholders::_2);
bind_func22(3);
bind_const_func22(44, 55);
bind_const_func222(66, 77);
}
```
##### 方式三:使用lambda为成员函数指针生成一个可调用对象
```cpp
// 方式三: 通过lambda生成可调用对象
void exam3() {
MyClass obj;
const MyClass const_obj;
void (MyClass::*ptr)(int) = &MyClass::memberFunc;
void (MyClass::*constPtr)(int, int) const = &MyClass::constFunc;
// lambda捕获外部的实例对象和成员函数指针, 并在函数体里通过实例对象和成员函数指针完成调用.
auto lambda_func = [&obj, &ptr](int x) { (obj.*ptr)(x); };
auto lambda_const_func =
[&const_obj, &constPtr](int x, int y) { (const_obj.*constPtr)(x, y); };
lambda_func(1);
lambda_const_func(11, 22);
}
```
##### 方式四:使用mem_fn生成一个可调用对象
```cpp
// 方式四: 通过mem_fn生成可调用对象
// mem_fn为成员函数指针生成的函数, 含有一对重载的函数调用运算符, 一个接受实例对象的引用, 一个接受实例对象指针
void exam4() {
MyClass obj;
const MyClass const_obj;
MyClass *obj_ptr = &obj;
const MyClass *const_obj_ptr = &const_obj;
auto wrapped_mem_fun = std::mem_fn(&MyClass::memberFunc);
auto wrapped_const_mem_fun = std::mem_fn(&MyClass::constFunc);
wrapped_mem_fun(obj, 10);
wrapped_mem_fun(obj_ptr, 10);
wrapped_const_mem_fun(const_obj, 20, 30);
wrapped_const_mem_fun(const_obj_ptr, 20, 30);
wrapped_const_mem_fun(obj, 40, 50);
wrapped_const_mem_fun(obj_ptr, 40, 50);
}
```
<br><br><br>
# 指针的状态
## 空指针
> 空指针:**未指向任何有效地址的指针**。
**将 `0` 或 `NULL` 或 `nullptr` 赋给一个指针时**,该指针即为 "**==空指针==**"。
```cpp
// 定义空指针
int *p1 = nullptr; // C++11关键字, 等价于下一行语句;
int *p2 = 0;
// `NULL`是值为0的常量宏, 在<cstddef> C++标准库头文件中定义
int *p3 = NULL;
```
> [!info] 空指针中存储的 "值" 通常为 0
>
> 当一个指针是 "**空指针**" 时,**指针==内部存储的 "地址值"==** 由编译器的实现决定。
> 在绝大多数平台上实现为,**空指针内部存储的值为`0`**。
> 在 OS 层面上,**==地址 0== 通常保留作为 "无效地址" 而禁止访问**,故**访问空指针**将触发 "**段错误**"(非法内存访问)。
>
> ```cpp
> int main() {
> int *p = nullptr;
> cout << p << endl; // 输出0
> }
> ```
>
> [!caution] C++中 `ostream` 对**字符指针** `char*` 重载了 `<<` 运算符,将**输出指针所指的以空字符(`'\0'`)结尾的 C 风格字符串**。
>
> 若**一个字符指针为空指针**,则 `cout <<` 该字符指针将会触发 "**段错误**"(非法内存访问)!
>
> ```cpp
> int main() {
> int *ip = nullptr;
> char *cp = nullptr;
> printf("%p\n", ip); // 输出0000000000000000
> printf("%p\n", cp); // 输出0000000000000000
> cout << ip << endl; // 输出0
> cout << cp << endl; // 将触发段错误!
> }
> ```
>
<br><br>
#### 关于 NULL
`NULL` 是一个**宏**,在 C& C++11 之前用于**表示空指针**,**在 C 和 C++中该宏的定义不相同**。
| | `NULL` 的宏定义 | 说明 |
| --- | -------------------- | --------------------------- |
| C | `#define ((void*)0)` | **==值为 0 的 `void*` 类型指针==** |
| C++ | `#define NULL 0` | **==值为 0 的宏常量==** |
> [!caution] 在 C++中 **`NULL` 不能定义为 `(void(*)0)`**,因为 C++**不支持从 `void*` 到任何其它指针类型**的隐式转换,如果这样定义**则无法将 `NULL` 赋值给指针**。
> [!example] 在 `MinGW` 实现中对`NULL` 宏的定义
>
> ```cpp
> #if defined(__need_NULL)
> #undef NULL
> #ifdef __cplusplus
> # if !defined(__MINGW32__) && !defined(_MSC_VER)
> # define NULL __null
> # else
> # define NULL 0
> # endif
> #else
> # define NULL ((void*)0)
> #endif
> ```
>
>
#### 关于 `nullptr`
> [!info] `nullptr` 是一个特殊的**字面量值**,其类型是 `std::nullptr_t`,可被隐式转换为指向任何类型的指针 ^u7d9yw
`nullptr` 可以被自动转换为**任何指针类型**,但**不会被转换为任何整数类型**。
使用 `nullptr` 来取代 `0` 或者 `NULL`,能够帮助在“null pointer” 被解释为一个整数值时避免误解。例如:
```cpp
void f(int);
void f(void*);
f(0); // call f(int)
f(NULL); // call f(int) if NULL is 0, ambiguous otherwise
f(nullptr); // call f(void*)
```
<br><br>
## 野指针
> A wild pointer is a pointer that has **not been correctly initialized** and therefore p**oints to some random piece of memory**. It is a serious error to have wild pointers.
**==野指针==**(wild pointer):**尚未初始化**为已知地址的指针。
野指针不是空指针,野指针**指向的内存地址是未知的** (随机的,不正确的,无明确意义的)。
成因:**指针未初始化**: 指针变量刚被创建时不会自动成为空指针,它的缺省值是**随机**的;
```cpp
int *ptr; // 未初始化,指向任意内存地址
// 使用ptr是非常危险的,因为不知道它指向哪里
```
<br><br><br>
## 悬空指针
> A dangling pointer is a pointer that **used to point to a valid address but now no longer does**. This is usually due to that **memory location being freed up and no longer available**. There is nothing wrong with having a dangling pointer unless you try to access the memory location pointed at by that pointer. It is always best practice not to have or leave dangling pointers.
==**悬空指针**==(dangling pointer):当一个指针被分配给内存,而**这块内存又被释放或删除后**,如果没有将该指针置为 null(或其他有效地址),该指针就成为了悬空指针。
悬空指针的成因:**指针释放后未置空**——指针在 `free` 或 `delete` 后未赋值 NULL,此时**指向无效内存**。
```cpp
int *ptr = new int(7);
delete ptr; // 内存被释放
// 此时,ptr是一个悬空指针,因为它不再指向已分配的内存
```
### 其它情况
- **指针越界**:指针指向的范围超出了有效范围
- **指针超出变量的作用域**:
- 调用函数时**返回指向局部变量 (栈内存)的指针或引用**,函数结束时,栈区上局部变量的内存空间被释放;
- 指针指向代码块中的局部变量,代码块执行结束后,变成野指针;
<br><br><br>
# 参考资料
# Footnotes
[^1]: 《深度探索 C++对象模型》P28
[^2]: 《深入理解计算机系统》
[^3]: 《C++ Primer》P513
[^4]: 《C++ Primer》P739-P741
[^5]: 《C++ Primer》P741-P746
[^6]: [如何理解常量指针与指针常量?](https://www.zhihu.com/question/19829354)
[^7]: 《C++程序设计语言》P149