C++ 为什么在这个';手工制作的列表';?

C++ 为什么在这个';手工制作的列表';?,c++,pointers,C++,Pointers,我们在学校有一个项目,尽管项目是关于什么的,但它涉及使用具有这种特定结构的链表: typedef struct _node { int contents; _node *next_node; } *node; 在项目开始之前,我们被分配了一系列功能来学习如何使用列表(将节点推到列表的前面或后面,计算节点数量,搜索特定节点等) 这并不难,但是当老师发送基本项目时(这样我们每个人都在同一个地方开始),所有的功能都涉及通过引用传递一个*节点。例如: resultType functi

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

typedef struct _node {
    int contents;
    _node *next_node;
} *node;
在项目开始之前,我们被分配了一系列功能来学习如何使用列表(将节点推到列表的前面或后面,计算节点数量,搜索特定节点等)

这并不难,但是当老师发送基本项目时(这样我们每个人都在同一个地方开始),所有的功能都涉及通过引用传递一个
*节点。例如:

resultType functionName(node *list, ...) { ... }
在项目之前,我使用void函数完成了所有列表函数,因为至少据我所知,我们使用的是指针,因此,只要不丢失列表标题的内存地址,就不会丢失内容(以及列表的其余部分)


所以。。。在这种情况下,将指针传递给指针有什么意义?有什么我遗漏的吗?我问了我的老师,他不知道该怎么解释(要么就是这样,要么他和我一样迷路了)。我的意思是,这个结构已经是一个指针了,那么,为什么要传递列表地址的地址呢?

这取决于函数
functionName
中的操作。如果要分配到
节点*
指针上,或者要将其值更改到
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;
}

传递给此类函数的对象通常是“head”,即指向列表第一个元素的指针

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

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

更好的解决方案是使用
列表
结构

typedef struct _List {
    Node head;
} List;

然后,您将传递一个指向
列表的指针,这很有意义:在C中,您传递一个指向需要修改的内容的指针。

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

关键是,对于链表,您希望将指向第一个节点的指针存储在某个位置。该指针不是结构节点的一部分。但是您可以将其地址传递给函数,就像列表中任何节点中的任何
下一个节点的地址一样。也就是说,您不需要特别处理空列表的情况

举个例子:

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);
这意味着将函数实现更改为以下内容(只需再更改三颗星):

你看,每当我看到像

node newNode = malloc(sizeof(*newNode));
我立刻有了一个WTF时刻:“为什么
newNode
被当作一个指针,而它只是…哦,等等,它真的是一个指针类型!有人应该真正清理这个代码!”另一方面,当我看到

node* newNode = malloc(sizeof(*newNode));
一切都很清楚:
newNode
是一个指针,它以标准方式分配给
malloc()

关键是,除非我在类型中看到
*
,否则我不希望看到指针。Typedefing指针类型打破了这一假设,导致代码的可读性较差。尤其是C程序员喜欢在这样的地方显式地编写代码


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

这不能是C,因为
\u节点没有
struct
。你只显示一个指针参数。通过C++中的引用传递指针(这不是C)看起来像“代码>函数名(节点*&List,…)< /Cord>),并且根本不显示指针(这是节点**/COD>),所以我不能完全确定这个问题。is@Useless; <代码>节点
的类型为
struct\u node*
,因此
列表
的类型为
struct\u node**
@nwp;他说的是
\u节点*下一个节点。我理解你对头和其他东西所说的,因为我们这样做了,但是
*节点
仍然是一个指针结构,所以列表是指向
\u节点
结构的指针。既然列表已经是一个指针,为什么还要将它作为参考传递呢?你真的需要让你的老师解释一下——我怀疑你是唯一一个有这种困惑的人。同时,提示:最初,当列表为空时,头指针为空。如果只是传递指针,如何使列表非空?现在,试着写addNode(
\u Node*
),你就会明白为什么需要
addNode(\u Node**)
很抱歉花时间回答。。。工作,学习等等。。。当我们创建列表时,我们不使用指向第一个节点的指针,我个人并不完全同意这一点。我们实际上创建了一个
\u节点
,我们称该节点为
。它指向t
node newNode = malloc(sizeof(*newNode));
node* newNode = malloc(sizeof(*newNode));