指针形参的一些注意点
首先,如果你在函数中传入一个指针并修改它指向的数据,原始指针所指向的数据会被修改。不过,如果你修改的是指针本身(即让它指向其他地址),那么在函数外部指针的值不会改变。
#include <iostream>
void modifyValue(int *ptr) {
*ptr = 10; // 修改指针指向的数据
}
void modifyTarget(int *ptr) {
ptr = (int*) malloc(sizeof(int));
}
int main() {
int value = 5;
int *ptr = &value;
printf("Before modifyValue: %d\n", *ptr);
modifyValue(ptr);
printf("After modifyValue: %d\n", *ptr);
printf("Before modifyTarget: %p\n", ptr);
modifyTarget(ptr);
printf("After modifyTarget: %p\n", ptr);
return 0;
}
输出如下:
Before modifyValue: 5
After modifyValue: 10
Before modifyTarget: 0x16f7ea8c8
After modifyTarget: 0x16f7ea8c8
在这个例子中,两个函数的形参都是指针,首先修改指针指向的值,可以修改;接着尝试修改指针本身,赋给这个指针一个新生成的地址,但并没有成功。如果需要要改变指针本身,可以传递指向该指针的指针,在 C++ 中还可以传递指针的引用(注意 C 语言没有引用,只有 C++ 才有)。
传入指针的指针
#include <iostream>
void showPointer(int **ptr) {
std::cout << "ptr is: " << ptr << std::endl;
std::cout << "*ptr is: " << *ptr << std::endl;
std::cout << "**ptr is: " << **ptr << std::endl;
}
void modifyTarget(int **ptr) {
std::cout << "In modifyTarget..." << std::endl;
*ptr = (int*) malloc(sizeof(int));
std::cout << "ptr is: " << ptr << std::endl;
std::cout << "*ptr is: " << *ptr << std::endl;
std::cout << "End modifyTarget..." << std::endl;
}
int main() {
int value = 5;
int *ptr = &value;
int **pptr = &ptr;
showPointer(pptr);
modifyTarget(pptr);
showPointer(pptr);
return 0;
}
结果如下:
❯ ./a.out
ptr is: 0x16bab2dc0
*ptr is: 0x16bab2dc8
**ptr is: 5
In modifyTarget...
ptr is: 0x16bab2dc0
*ptr is: 0x11de060a0
End modifyTarget...
ptr is: 0x16bab2dc0
*ptr is: 0x11de060a0
**ptr is: 0
上面这个例子传入的是指针的指针,可以成功修改另一个指针本身的值,具体原理见下文。
根据《C和指针》这本神书,可以清楚地理解指针和二级指针(指针的指针)的一些细节,具体内容见上图。理解了这些内容,就可以明白为什么要通过传入指针的指针来修改一个指针本身,而非指向的内容。
假设传入函数的是上图中的指针 a,可以通过解引用 *a 来修改其指向的内容 x,但如果直接修改 a,修改的是传入函数的 a 的副本,a 本身并不会被修改。所以为了修改 a 的值,就需要传入指针 b,b 是指向指针 a 的指针,通过解引用 *b 就可以修改 a 本身的值了,同样也可以通过两次解引用 **b 来修改 x 的值。同样的,如果此时在函数中修改指针 b 本身,修改的也还是其副本,b 本身并没有被修改。
传入指针的引用
知道了上面的内容,在 C++ 中使用指针的引用来解决该问题也是很好理解的。在 C++中,引用(reference)是一个别名,它提供了一个别名来访问另一个变量。引用的主要目的是使代码更加简洁和易读,同时允许函数直接操作传递的变量,而不再是变量的副本,同时在一些情况下也不需要使用指针来进行间接访问。
所以下面的例子还可以怎么写:
#include <iostream>
void showPointer(int *ptr) {
std::cout << "ptr is: " << ptr << std::endl;
std::cout << "*ptr is: " << *ptr << std::endl;
}
void modifyTarget(int *&ptr) {
ptr = (int*) malloc(sizeof(int));
}
int main() {
int value = 5;
int *ptr = &value;
showPointer(ptr);
modifyTarget(ptr);
showPointer(ptr);
return 0;
}
返回结果如下:
❯ ./a.out
ptr is: 0x16f366dc8
*ptr is: 5
ptr is: 0x127e060a0
*ptr is: 0
功能相同,完成了修改,同时也使代码更加简洁。
注意语法
在 C++中,指针的引用定义为 int *&ptr,而 int &*ptr 是非法的语法,毕竟 int * 可以看作指针的数据类型,与其他声明引用变量的语法 int &a 类似,表示声明一个指针类型的引用变量。
案例:初始化栈
下面是一个使用引用来初始化链表栈的示例:
typedef struct LinkNode {
char data;
struct LinkNode *next;
} LNode, *LinkStack;
void InitStack(LinkStack &L) {
L = (LinkStack)malloc(sizeof(LNode)); // 直接操作引用,初始化L
if (L != NULL) {
L->next = NULL;
}
}
int main() {
LinkStack stack = NULL;
InitStack(stack); // 直接传递引用
// stack 现在已初始化为一个空栈
free(stack);
return 0;
}
在这个示例中,InitStack 函数使用引用参数 LinkStack &L,这样在函数内部对 L 的修改直接影响主函数中的 stack。这避免了指针的间接操作,使代码更易读和简洁。