为什么在这个“手工制作的列表”中使用指向指针的指针?

时间:2017-09-15 12:28:50

标签: c++ pointers

我们在学校有一个项目,尽管该项目涉及的是什么,但它涉及使用具有这种特定结构的链表:

typedef struct _node {
    int contents;
    _node *next_node;
} *node;

在项目开始之前,我们被分配了一系列功能来学习使用列表(将节点推送到列表的前面或后面,计算节点数量,搜索特定节点等)。 / p>

并不是那么困难,但是当老师发送基础项目(以便我们每个人都在同一个地方开始)时,所有功能都涉及通过引用传递*node 即可。例如:

resultType functionName(node *list, ...) { ... }

我使用 void 函数完成了项目之前的所有列表函数,因为至少我理解,我们正在使用指针,所以,只要你不丢失列表标题的内存地址,您不会丢失内容(以及列表的其余部分)。

那么......在这种情况下,将指针传递给指针有什么意义呢?有什么我想念的吗?我问我的老师,他不知道如何解释(或者,或者他和我一样迷失)。我的意思是,结构已经是一个指针,所以,为什么要传递列表地址的地址?

3 个答案:

答案 0 :(得分:2)

这取决于函数functionName中的操作。如果要分配到node*指针,或者如果想要将其值更改为超出functionName的范围,则需要能够修改它,并且指向指针的指针需要是传递到函数中。

为了举例说明,请比较下面的两个函数。第一个,allocate1能够对指针a进行堆分配,因为它接收到指针的指针。第二个,allocate2按值接收指针,能够分配数组,但在返回后,分配的空间丢失。通过比较print语句中的指针值可以看出这一点。

#include <iostream>

void allocate1(double** a, const int n)
{
    *a = new double[n];
    std::cout << "allocate1: " << *a << std::endl;
}

void allocate2(double* a, const int n)
{
    a = new double[n];
    std::cout << "allocate2: " << a << std::endl;
}

int main()
{
    double* test1 = nullptr;
    double* test2 = nullptr;
    int n = 10;

    allocate1(&test1, 10);
    std::cout << "after allocate1: " << test1 << std::endl;
    test1[3] = 16; // OK!

    allocate2(test2, 10);
    std::cout << "after allocate2: " << test2 << std::endl;
    test2[3] = 16; // NOT OK!

    return 0;
}

我机器上的输出是:

allocate1: 0x7fcdd2403160
after allocate1: 0x7fcdd2403160
allocate2: 0x7fcdd24031b0
after allocate2: 0x0
Segmentation fault: 11

答案 1 :(得分:2)

答案是:因为它很方便。它允许函数操作指针,而不管它存储在何处

关键是,使用链表,您希望在某处存储指向第一个节点的指针。该指针不是struct _node的一部分。但是您可以将其地址传递给函数,就像列表中任何节点中任何next_node指针的地址一样。也就是说,您不需要专门处理空列表的情况。

仅作为一个例子:

void pushFront(node* list, int value) {
    node newNode = malloc(sizeof(*newNode));
    newNode->contents = value;
    newNode->next_node = *list;
    *list = newNode;
}

我可以这样调用这个函数:

int main() {
    node myList = NULL;
    pushFront(&myList, 42);
    printf("The answer is %d!\n", myList->contents);
}

请注意,即使myList初始化为NULL,此代码也能正常运行!当您沿着列表行走,插入或删除节点时,会出现类似的优雅代码。

除此之外:我认为,typedef指针类型是一个坏习惯:它隐藏了事实,即事物是一个指针。我非常喜欢以下定义:

typedef struct node node;    //avoid having to write `struct` everywhere
struct node {
    int contents;
    node *next_node;
};

void pushFront(node** list, int value);

这意味着将函数实现更改为以下(仅需三个星):

void pushFront(node** list, int value) {
    node* newNode = malloc(sizeof(*newNode));
    newNode->contents = value;
    newNode->next_node = *list;
    *list = newNode;
}

int main() {
    node* myList = NULL;
    pushFront(&myList, 42);
    printf("The answer is %d!\n", myList->contents);
}

你看,每当我看到像

这样的行
node newNode = malloc(sizeof(*newNode));

我立即有一个WTF时刻:&#34;为什么newNode被视为指针,当它只是......哦等等,它真的是一个指针类型!有人应该真正清理这段代码!&#34;另一方面,当我看到

node* newNode = malloc(sizeof(*newNode));

一切都很明确:newNode是一个指针,它以标准方式分配malloc()

重点是,除非我在类型中看到*,否则我不会指望指针。 Typedefing指针类型打破了这个假设,导致代码的可读性更差。特别是C程序员喜欢他们的代码在这样的地方显式。

当然,你应该坚持老师给你的指导方针,但一定要尽快放弃指针式的习惯。

答案 2 :(得分:1)

传递给这种函数的东西通常是&#34; head&#34;,指向列表第一个元素的指针。

通常需要修改头指针:将第一个节点插入空列表(头指针从NULL更改为指向实际节点的指针)或从列表中删除第一个元素时(头部)指针需要更改为指向下一个列表元素。)

这就是为什么我们需要将指针传递给head而不是head本身。

更好的解决方案是拥有List结构

typedef struct _List {
    Node head;
} List;

然后你传递指向List的指针是有意义的:在C中你传递一个指向你需要修改的东西的指针。