C语言零碎知识点之数据结构中的 *&

本文解析了C++中使用*&在函数参数中的作用,重点讲解了如何通过引用修改链表元素影响全局,以及不同指针传递方式的对比。学习了引用在数据结构操作中的实际运用和注意事项。

在数据结构中我们经常能够看到 *& 在函数的形参中,但却有些难以明白它的含义。因为那些代码都是伪代码,不是某一类编程语言的代码,通常是 C 和 C++ 的混用。

*& 是 C++ 中才能使用的,被称之为”引用“。如果使用的是纯 C 编译器,那么很可能无法通过编译。

由于我并不会 C++ 语言,所以下面只是说一下对它使用的理解,并且在数据结构的解题中尽量避免去使用到它。

注:下面代码经过 Dev-C++ 5.11 版本验证。

举例说明,我最开始想创建一个链表,代码如下:

#include <stdio.h>
#include <malloc.h>

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

/**
 * 使用头插法创建单链表
 * @param list 单链表
 * @param nums 待插入的数据数组
 * @param n 数组长度
 */
void createByHead(LNode *list, int nums[], int n) {
    // 创建链表的头节点
    list = (LNode *) malloc(sizeof(LNode));
    list->next = NULL;
    // 循环数组 nums 中所有数据
    for (int i = 0; i < n; i++) {
        // 创建新节点并指定数据域和指针域
        LNode *newNode = (LNode *) malloc(sizeof(LNode));
        newNode->data = nums[i];
        newNode->next = NULL;
        // 将新节点插入到链表的头部,但是在头结点的后面
        LNode *temp = list->next;
        newNode->next = temp;
        list->next = newNode;
    }
}

/**
 * 打印单链表中的所有节点
 * @param list 单链表
 */
void print(LNode list) {
    printf("[");
    // 链表的第一个节点
    LNode *node = list.next;
    // 循环单链表所有节点,打印值
    while (node != NULL) {
        printf("%d", node->data);
        if (node->next != NULL) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

int main() {
    LNode *list;
    int nums[] = {111, 222, 333, 444, 555};
    int n = 5;

    createByHead(list, nums, n);
    print(*list);
}

但运行不会打印任何结果,尽管我传递了一个指针变量。事实上我对指针的理解还是不够深刻。

上面的代码可能有些多了,看看简化后的情况,即我想要在函数 fun 内修改传入的形参 a 的值,并且在函数外也能访问到:

#include <stdio.h>
#include <malloc.h>

void fun(int *a) {
    a = (int *) malloc(sizeof(int));
    int b = 3;
    a = &b;
    printf("fun->a: %d\n", *a);
}

int main() {
    int *a;
    fun(a);
    printf("main->a: %d\n", *a);
}

代码指向结果如下:

fun->a: 3

--------------------------------
Process exited after 3.191 seconds with return value 3221225477
请按任意键继续. . .

即在函数 fun 外并不能访问到在函数 fun 内被修改的 a,即使它是一个指针变量。

事实上再次证明了我对 C 语言指针的认知浅薄。

如果我们把形参 int *a 变成 int *&a 呢?那么代码如下:

#include <stdio.h>
#include <malloc.h>

void fun(int *&a) {
    a = (int *) malloc(sizeof(int));
    int b = 3;
    a = &b;
    printf("fun->a: %d\n", *a);
}

int main() {
    int *a;
    fun(a);
    printf("main->a: %d\n", *a);
}

代码执行结果如下:

fun->a: 3
main->a: 3

--------------------------------
Process exited after 1.911 seconds with return value 0
请按任意键继续. . .

我们仅仅在为形参的指针变量添加一个 & 符号就发生了改变。

其实在函数内形参int* aint a区别不大,不过是整型指针类型的变量int*和普通整型类型的变量int的区别,都传递的是值。在函数内对它们的值做修改,都无法影响到函数外。

int *&a 传递的是整型指针类型变量 a 的地址值,在函数内直接对地址所表示的变量进行修改,那么无论是函数内还是函数外都会被影响到。

如果仅仅是使用 C 语言,那么也可以在形参和实参中传递指针变量的地址,但修改有点多:

#include <stdio.h>
#include <malloc.h>

void fun(int **a) {
    *a = (int *) malloc(sizeof(int));
    int b = 3;
    *a = &b;
    printf("fun->a: %d\n", **a);
}

int main() {
    int *a;
    fun(&a);
    printf("main->a: %d\n", *a);
}

即在函数 fun**a 接收的是就是 main 函数内实参指针变量 *a 的地址,通过 & 操作符取得指针变量的地址;而在函数 fun*a 就表示一个指针变量,对这个指针变量的修改无论是函数内还是函数外都会被影响到。

将函数的实参改为普通变量,而形参继续是指针变量也可以实现同样的效果,但注意在函数内指针变量修改值的方式(这样 a = &b; 赋值是行不通的)。

#include <stdio.h>

void fun(int *a) {
    // 这里就不需要再为它动态分配内存空间了
    // a = (int *) malloc(sizeof(int));
    int b = 3;
    *a = b;// 修改指针变量的值
    printf("fun->a: %d\n", *a);
}

int main() {
    // 声明一个普通变量,而非指针变量
    int a;
    fun(&a);// 因为函数的形参还是指针变量,所以这里要传递普通变量的地址,使用&取址符
    printf("main->a: %d\n", a);
}

那么同样的道理,要想在函数内修改单链表成功,并且在其他函数也可以访问,就可以使用 *&。仅仅只需要改动形参,而不需要改动其他任何地方的代码。但注意这是 C++ 的语法,一般的 C 编译器是不支持的,但在解数据结构题目时为了省事可以考虑使用。

#include <stdio.h>
#include <malloc.h>

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

/**
 * 使用头插法创建单链表
 * @param list 单链表
 * @param nums 待插入的数据数组
 * @param n 数组长度
 */
void createByHead(LNode *&list, int nums[], int n) {
    // 创建链表的头节点
    list = (LNode *) malloc(sizeof(LNode));
    list->next = NULL;
    // 循环数组 nums 中所有数据
    for (int i = 0; i < n; i++) {
        // 创建新节点并指定数据域和指针域
        LNode *newNode = (LNode *) malloc(sizeof(LNode));
        newNode->data = nums[i];
        newNode->next = NULL;
        // 将新节点插入到链表的头部,但是在头结点的后面
        LNode *temp = list->next;
        newNode->next = temp;
        list->next = newNode;
    }
}

/**
 * 打印单链表中的所有节点
 * @param list 单链表
 */
void print(LNode list) {
    printf("[");
    // 链表的第一个节点
    LNode *node = list.next;
    // 循环单链表所有节点,打印值
    while (node != NULL) {
        printf("%d", node->data);
        if (node->next != NULL) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

int main() {
    LNode *list;
    int nums[] = {111, 222, 333, 444, 555};
    int n = 5;

    createByHead(list, nums, n);
    print(*list);
}

还能使用指向指针的指针,但修改涉及的代码就比较多了。实参、形参及在函数中使用了形参的代码都需要改变:

#include <stdio.h>
#include <malloc.h>

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

/**
 * 使用头插法创建单链表
 * @param list 单链表
 * @param nums 待插入的数据数组
 * @param n 数组长度
 */
void createByHead(LNode **list, int nums[], int n) {
    // 创建链表的头节点
    *list = (LNode *) malloc(sizeof(LNode));
    (*list)->next = NULL;
    // 循环数组 nums 中所有数据
    for (int i = 0; i < n; i++) {
        // 创建新节点并指定数据域和指针域
        LNode *newNode = (LNode *) malloc(sizeof(LNode));
        newNode->data = nums[i];
        newNode->next = NULL;
        // 将新节点插入到链表的头部,但是在头结点的后面
        LNode *temp = (*list)->next;
        newNode->next = temp;
        (*list)->next = newNode;
    }
}

/**
 * 打印单链表中的所有节点
 * @param list 单链表
 */
void print(LNode list) {
    printf("[");
    // 链表的第一个节点
    LNode *node = list.next;
    // 循环单链表所有节点,打印值
    while (node != NULL) {
        printf("%d", node->data);
        if (node->next != NULL) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

int main() {
    LNode *list;
    int nums[] = {111, 222, 333, 444, 555};
    int n = 5;

    createByHead(&list, nums, n);
    print(*list);
}

如果只是返回一个单链表,我们可以将创建成功的链表作为函数返回值返回,然后在主函数接收就可以,避免了它作为实参和形参的变化:

#include <stdio.h>
#include <malloc.h>

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

/**
 * 使用头插法创建单链表
 * @param list 单链表
 * @param nums 待插入的数据数组
 * @param n 数组长度
 */
LNode *createByHead(int nums[], int n) {
    // 创建链表的头节点
    LNode *list = (LNode *) malloc(sizeof(LNode));
    list->next = NULL;
    // 循环数组 nums 中所有数据
    for (int i = 0; i < n; i++) {
        // 创建新节点并指定数据域和指针域
        LNode *newNode = (LNode *) malloc(sizeof(LNode));
        newNode->data = nums[i];
        newNode->next = NULL;
        // 将新节点插入到链表的头部,但是在头结点的后面
        LNode *temp = list->next;
        newNode->next = temp;
        list->next = newNode;
    }
    // 将链表返回
    return list;
}

/**
 * 打印单链表中的所有节点
 * @param list 单链表
 */
void print(LNode list) {
    printf("[");
    // 链表的第一个节点
    LNode *node = list.next;
    // 循环单链表所有节点,打印值
    while (node != NULL) {
        printf("%d", node->data);
        if (node->next != NULL) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

int main() {
    LNode *list;
    int nums[] = {111, 222, 333, 444, 555};
    int n = 5;

    list = createByHead(nums, n);
    print(*list);
}

其实还可以考虑实参用普通变量,形参用指针变量,也能达到同样的效果:

#include <stdio.h>
#include <malloc.h>

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode;

/**
 * 使用头插法创建单链表
 * @param list 单链表
 * @param nums 待插入的数据数组
 * @param n 数组长度
 */
void createByHead(LNode *list, int nums[], int n) {
    // list就是链表的头节点,不需要再分配内存空间了,但需要把next指针指向NULL
    list->next = NULL;
    // 循环数组 nums 中所有数据
    for (int i = 0; i < n; i++) {
        // 创建新节点并指定数据域和指针域
        LNode *newNode = (LNode *) malloc(sizeof(LNode));
        newNode->data = nums[i];
        newNode->next = NULL;
        // 将新节点插入到链表的头部,但是在头结点的后面
        LNode *temp = list->next;
        newNode->next = temp;
        list->next = newNode;
    }
}

/**
 * 打印单链表中的所有节点
 * @param list 单链表
 */
void print(LNode list) {
    printf("[");
    // 链表的第一个节点
    LNode *node = list.next;
    // 循环单链表所有节点,打印值
    while (node != NULL) {
        printf("%d", node->data);
        if (node->next != NULL) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

int main() {
    LNode list;
    int nums[] = {111, 222, 333, 444, 555};
    int n = 5;

    createByHead(&list, nums, n);
    print(list);
}

总结:

  • 所谓 *& 就是传递一个引用进去,让你可以在函数内做的修改影响到函数外。
  • 通常 *& 出现在一些关于数据结构中的书籍中,作为伪代码展示。
  • 通常 *& 用来修改链表、栈、树等数据结构。
  • 除了 *& 之外,还可以考虑使用其他的方式也能实现同样的效果,不必过分纠结。
  • 变量在使用之前,普通变量可以不初始化。但指针变量必须初始化并且普通类型的指针变量可以通过 & 取址符进行赋值,而结构体指针变量则需要通过 malloc 函数动态分配空间然后再赋值。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值