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

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 函数动态分配空间然后再赋值。

以上是关于C语言零碎知识点之数据结构中的 *&的主要内容,如果未能解决你的问题,请参考以下文章

C语言零碎知识点之字符串数组

C/C++ 语言 零碎知识点的总结干货

C语言零碎知识点之定义指针时星号靠近类型名还是变量名

C/C++ 语言 零碎知识点的总结

C/C++ 语言 零碎知识点的总结

C/C++ 语言 零碎知识点的总结