Skip to content

指针

又一次栽在指针上,这次涉及到多态和类型转化等略微复杂一点的,所以略作总结下.

首先计算机本质上并不知道什么继承与多态,本质上是地址,数值;地址,数值;所以再次深度理解指针,还是蛮有意义的.

简介指针

指针的本质是一个无符号整数,它存储的是另一个变量在内存中的绝对地址。在 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* 指针先抹去数据类型,接着做一次类型转化,

或者就是一个函数表也很常见

Released under the MIT License.