C++ 传入函数的指针

317 阅读4分钟

指针形参的一些注意点

首先,如果你在函数中传入一个指针并修改它指向的数据,原始指针所指向的数据会被修改。不过,如果你修改的是指针本身(即让它指向其他地址),那么在函数外部指针的值不会改变。

#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

上面这个例子传入的是指针的指针,可以成功修改另一个指针本身的值,具体原理见下文。

二级指针.jpg

根据《C和指针》这本神书,可以清楚地理解指针和二级指针(指针的指针)的一些细节,具体内容见上图。理解了这些内容,就可以明白为什么要通过传入指针的指针来修改一个指针本身,而非指向的内容。

假设传入函数的是上图中的指针 a,可以通过解引用 *a 来修改其指向的内容 x,但如果直接修改 a,修改的是传入函数的 a 的副本,a 本身并不会被修改。所以为了修改 a 的值,就需要传入指针 bb 是指向指针 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。这避免了指针的间接操作,使代码更易读和简洁。