深入浅出带你搞懂二重指针

一重指针

int *val_p 表示定义一个指针变量,其中,val_p 表示指针指向的地址,*val_p 表示指向地址中存储的数据。假设有一个变量 num=100; val_p = #,那么 *val_p = 100;val_p 就是 num 的地址。

一重指针在函数入口参数中应用

#include <stdio.h>

void modifyValue(int *val_p) {
    *val_p = 50; // 不可以操作 val_p = xxx;
}

int main() {
    int num = 100;
    modifyValue(&num);
    printf("num = %d\n", num); // 输出结果 num = 50
    return 0;
}

定义函数 void function(int* val_p); 当外部变量 num = 100 当作入口参数时 function(&num);,在进入 function 函数内部,函数会自动在栈中申请一块地址,用来创建一个指针变量 val_p 指向 num 的地址 int* val_p = &num;,但是退出 function 函数的时候,val_p 指针变量会被释放,也就是出栈。由于函数入口参数传入的是 num 的地址,所以即使 val_p 被释放,在函数内部改变 num 的数值 num = *val_p = 50; 会同步到函数外部,最终 num=50

#include <stdio.h>
 
void modifyValue(int *val_p) {
    *val_p = 50;  // 修改 num_p 指向的地址中的值(*num_p)
}
 
int main() {
    int num = 100;
    int *num_p = &num;
 
    printf("初始值:*num_p = %d\n", *num_p);  // 初始值:*num_p = 100
    modifyValue(num_p); // 传入 num_p 指向的地址 
    printf("修改后:*num_p = %d\n", *num_p);  // 修改后:*num_p = 50
 
    return 0;
}

同理,我们将上面的 num 换成一个指针变量 *num_p 传入该函数 function(num_p);,在进入 function 函数内部,函数会自动在栈中申请一块地址,用来创建一个指针变量 val_p 指向 num_p 也就是 *num_p 的地址 int* val_p = num_p;。那么,在该函数内部所有对 *val_p 的操作都等价于直接操作 *num_p,并且最后的数值会同步到函数外部。

由此可以总结出

  1. 如果想在函数内部修改外部变量的数值,那么我们需要传入的是外部变量的地址;
  2. 指针变量作为函数的入口参数我们一般多次操作修改 *val_p 的数值,不会随意修改指针自身地址 0x40000100,因为在函数内部修改 &val_p 毫无意义,最后函数退出 val_p 会被释放。
  3. 指针变量作为函数的入口参数我们一般多次操作修改 *val_p 的数值,不会去修改 val_p 的数值,因为 val_p 表示指针指向的地址,可以理解成定位的作用,在函数传入入口参数的时候,就已经固定好了 val_p 指向的地址。

同理,如果外部参数*num_p 传入函数时,只能修改地址内数值*num_p,不可以修改地址 num_p那么,如果我想在函数内部多次修改 num_p 的数据,也就是 *num_p 的地址要怎么办呢?这就用到了二重指针。

二重指针

由上面一重指针在函数入口参数中的应用,我们可以总结出规律:函数的入口参数是指针时,传入指针指向的地址,只可修改指向地址内的数值,也就是说函数内部只能操作 *val_p = xxx,不可以操作 val_p = xxx

对于二重指针作为入口参数时,同样有这个规律,在函数内部只能修改 **val_pp = xxx*val_pp = xxx,而不可以操作 val_pp = xxx,总而言之,就是不可以去掉 * 号直接操作指针指向的地址(val_p = xxx、val_pp = xxx)。

#include <stdio.h>
 
void modifyAddrAndValue(int **val_p) {
    *val_p = 0x70000000;  // 修改指针 num_p 指向的地址
    **val_p = 50; // 修改 num_p 指向的地址中的值(*num_p)
}
 
int main() {
    int num_p = 100;
    int *num_p = &num;
 
    printf("初始指针指向地址:num_p = %p\n", num_p);  // 初始指针指向地址:num_p = 0x60000000
    printf("初始指针指向地址内的值:num_p = %d\n", *num_p);// 初始指针指向地址内的值:*num_p = 100
    modifyAddrAndValue(&num_p); // 传入保存 num_p 指针的地址
    printf("修改后指针指向地址:num_p = %p\n", num_p);  // 修改后指针指向地址:num_p = 0x70000000
    printf("修改后指针指向地址内的值:num_p = %d\n", *num_p);  // 修改后指针指向地址内的值:*num_p = 50
 
    return 0;
}

二重指针具体操作如下图所示,可以看出,二重指针的主要作用是通过 *val_pp = 0x70000000 修改传入指针 num_p 指向的地址,也可以修改指向地址内的数值 **val_pp = 50

所以,如果我们想修改外部指针变量 *num_p 的地址 num_p,那么,我们要想办法在函数内操作 *val = num_p,这样,就可以修改 num_p 的数值了。下面就来介绍一下二重指针在函数入口参数中的应用。

二重指针在函数入口参数中应用

二重指针其中一个典型的应用就是在链表中插入头节点,例如在插入或删除节点时,如果可能影响头指针的位置,就需要用二重指针,下面就以这个应用为例展开介绍。

// 双向链表节点结构
typedef struct Node {
    int data;            // 数据域
    struct Node* prev;   // 指向前一个节点
    struct Node* next;   // 指向后一个节点
} Node;

// 创建节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (!newNode) {
        printf("内存分配失败!\n");
        return NULL;
    }
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// 链表头插入节点
void insertAtHead(Node** head_pp, int data) {
    Node* newNode = createNode(data);
    if (!newNode) return;

    newNode->next = *head_pp;
    if (*head_pp) {
        (*head_pp)->prev = newNode;
    }
    *head_pp = newNode;
}

int main() {
    Node* head = NULL;

    // 表头插入(假设已经存在链表 1 -> 2 -> 3, 表头插入 0 后链表为 0 -> 1 -> 2 -> 3)
    insertAtHead(&head, 0);
}

上面代码的 void insertAtHead(Node** head_pp, int data) 函数用到了二重指针,下面我们结合链表来分析一下,二重指针具体的作用。insertAtHead(&head, 0) 函数执行拆解如下图所示。入口参数是一个取地址操作的指针变量 &head,链表头插入的节点的数值是 0

从图中的过程可以看出,在链表头部插入新节点的关键是要先创建一个新节点,然后将头部节点的地址 head 修改为新节点的地址 newNode。在函数外部参数是指针变量 *head,我们要修改 head 数值显然用一重指针是不可以操作的,那么我们可以借助二重指针,传入 &head 参数,既 &head = head_pp,这样在函数内部可以操作 *head_pp,相当于操作 head 来修改节点。在这个操作中对比一重指针,传入函数的时候,固定好的是 &head 地址,而非 head 指向的地址,所以,我们操作*head_pp相当于修改head数值,**head_pp 相当于修改 *head 数值都是允许的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值