指针
又一次栽在指针上,这次涉及到多态和类型转化等略微复杂一点的,所以略作总结下.
首先计算机本质上并不知道什么继承与多态,本质上是地址,数值;地址,数值;所以再次深度理解指针,还是蛮有意义的.
简介指针
指针的本质是一个无符号整数,它存储的是另一个变量在内存中的绝对地址。在 64 位系统上,它永远占用 8 个字节,不管它指向的是一个 char 还是一个几百兆大小的复杂类。
如果开辟了一段连续数组,实际上就是一段地址连续的空间,所以可以通过逐步位移指针来进行数值的访问.
const
之前也略说过, const 实际上是一种「契约」关系——我是否要改动这个“数据”,可能是1, 0.2, ”hello world“等字面量(变量存储的数值),也有可能是指针所存储的地址.
C++
void process_data(const char* data);比如上面代码,就可以显然看出来是一个 ReadOnly 的代码——我在方法中不会对数据进行修改,我只读取这个 data
双重指针
可以通过指针修改指针变量所指向的数据;那么通过双重指针就可以修改双重指针所指向的指针的数据.
C++
void AllocateMemory(int* ptr) {
ptr = new int(42); // 错误!这只是修改了函数内部的局部拷贝,外面的 p 依然是 nullptr
}
int* p = nullptr;
AllocateMemory(p);
void AllocateMemory(int** ptr_to_ptr) {
*ptr_to_ptr = new int(42);
}
int* p = nullptr;
AllocateMemory(&p);第一段代码只修改了指针的拷贝,而做不到修改指针; 第二段代码通过二级指针解引用一次,拿到了外面真实的那个 p,最后让它指向新内存,非常正确
提供一个 Linus 玩法,利用二级指针删除单链表节点
C++
// curr_ptr_addr 是一个双重指针,它存的是“指向当前节点的那个 next 指针”的地址。
// 初始状态下,它是 head 指针的地址。
Node** curr_ptr_addr = &head;
// 如果这个地址里存的指针不是我们要找的 target,就继续往下走
while (*curr_ptr_addr != target) {
// 移动双重指针:让它指向下一个节点的 next 指针的地址
curr_ptr_addr = &((*curr_ptr_addr)->next);
}
// 现在 curr_ptr_addr 可能是 &head,也可能是 &(Prev->next)。
// 我们不管它是啥,直接改掉这个指针的内容,让它跳过 target。
*curr_ptr_addr = target->next;借助双重指针来规避了一次 head 判断 (内存释放部分省略)
- 如果
curr_ptr_addr是 head 的话,head=head->next,确实是删除了 head - 如果不是head,那么就是普通的把target删除 非常的优雅, 普通做法因为维护了一个前驱, 把前驱的下一个指向 target 的下一个,所以需要特判
函数指针
要调用一个方法,在运行时也是通过一个函数指针来找到函数再进行调用.所以也可以定义一个指针指向函数的地址,这样就可以方便的通过指针变量来对函数进行调用.
C++
int Add(int a, int b) { return a + b; }
int Sub(int a, int b) { return a - b; }
// 定义一个函数指针变量 'operation'
int (*operation)(int, int);
operation = &Add; // 亦可直接写 operation = Add;
int result1 = operation(5, 3); // 相当于调用 Add(5,3),结果是 8
operation = Sub; // 动态改变行为
int result2 = operation(5, 3); // 相当于调用 Sub(5,3),结果是 2再比如 stl 下的 sort, 可以支持任意类型数据,但是要提供一个 compare 方法
C++
// 提供一个函数指针类型:比较两个 void* 数据
typedef bool (*CompareFunc)(const void* a, const void* b);
// 底层引擎代码:完全不知道具体数据类型
void MySort(void* array, int count, int element_size, CompareFunc comp) {
// ... 排序逻辑 ...
// 当需要比较两个元素时:
if ( comp(element_A, element_B) ) { // 调用业务层传进来的函数
// 交换 ...
}
}
// 业务层代码:
bool ComparePlayers(const void* a, const void* b) {
// 强制转换回具体的业务类型
const Player* p1 = static_cast<const Player*>(a);
const Player* p2 = static_cast<const Player*>(b);
return p1->level < p2->level; // 按等级排序
}
MySort(playerArray, 100, sizeof(Player), ComparePlayers);这边就使用 void* 指针先抹去数据类型,接着做一次类型转化,
或者就是一个函数表也很常见