一重指针
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 = #
,但是退出 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 = #
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
,并且最后的数值会同步到函数外部。
由此可以总结出
- 如果想在函数内部修改外部变量的数值,那么我们需要传入的是外部变量的地址;
- 指针变量作为函数的入口参数我们一般多次操作修改
*val_p
的数值,不会随意修改指针自身地址0x40000100
,因为在函数内部修改&val_p
毫无意义,最后函数退出val_p
会被释放。 - 指针变量作为函数的入口参数我们一般多次操作修改
*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 = #
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
数值都是允许的。