单向链表 C++ 构造函数、析构函数和打印输出

3

我是一个初学者,正在学习C++,目前在制作单向链表。我遇到了一些问题,思考了很长时间,搜索了很多资料,但仍然没有找到这段代码的答案,所以我请求一些帮助。

这是我的linked.h文件:

template <class T>
class Node {
    public:
        T data;
        Node<T>* next;
};

template <class T>
class List {
    private:
        Node<T> *head;
    public:
        List() : head(NULL) {};
        ~List() {
            Node<T>* ptr, tmp;
            for(ptr = head->next; ptr == NULL; ptr = head->next) {
                delete ptr;
            }     
        }
        List(T* arr, int n_nodes) {
            head = NULL; 
            Node<T> *tmp = head;
            for(int i = 0; i < n_nodes; i++) {
                Node<T>* node = new Node<T>;
                node->data = arr[i];
                if(head == NULL) {
                    head->next = node;
                    tmp = node;
                }
                else {
                    tmp->next = node;
                    node->next = NULL;
                    tmp = node;
                }
            }
        }
        friend std::ostream& operator<<(std::ostream& out, List<T>& rhs) {                                                                                                                                 
            Node<T>* cur = rhs.head;
            out << cur;
            while(cur != NULL) {       
                if(cur->next != NULL) { 
                    out << cur->data << ", ";
                    cur = cur->next;
                }
                else
                    out << cur->data << " ";
            }
            return out;
        }
}; 

这是我的main.cc文件。

#include <iostream>
#include "linked.h"

int main() {
    int array[5] = {12, 7, 9, 21, 13};
    List<int> li(array, 5);
    std::cout << li;

    return 0;
}

我在运行构造函数时不断收到分段错误,但我不知道原因。我犯了什么错误?任何帮助都将不胜感激!


3
我看到的第一个明显的错误是:“如果(head == NULL){head->next = node; ...}” - Paul R
想一想:如果 headNULL,那么当你尝试给 head->next 赋值时会发生什么? - Paul R
Erica,head->next 对你意味着什么? - YSC
如果头节点为空,那么意味着你需要为头节点创建一个新的节点。 - stark
那么,我现在是不是可以开始像这样输入值:Node<T> *tmp = head; node->data = arr[i]; - Erica
显示剩余3条评论
1个回答

3
你可以用指向指针的指针来解决这个问题:
List(T* arr, int n_nodes)
{
    Node<T>** tmp = &head; // tmp *pointing* to uninitialized(!) head pointer
    for(int i = 0; i < n_nodes; i++)
    {
        Node<T>* node = new Node<T>();
        node->data = arr[i];
        // now the trick:
        *tmp = node; // !!!
        // you now have assigned the new node to whatever pointer
        // the tmp pointer points to - which initially is - guess - head...

        // but we now need to advance!
        tmp = &node->next;
    }
    // tmp now points to latestly created node's next pointer
    // (or still head, if no nodes where created because of n_nodes == 0)
    // be aware that this one still is not initialized! so:
    *tmp = nullptr;
}

你的析构函数也必然会失败:
Node<T>* ptr, tmp;
for(ptr = head->next; ptr == NULL; ptr = head->next)
{
    delete ptr; // you delete ptr, but advancing (ptr = head->next)
                // is done AFTERWARDS, so you'd access already deleted memory
                // undefined behaviour
}

此外,您不应删除头节点!如果头节点是nullptr,则可能会出现未定义的行为。

请尝试以下方法:

while(head)
{
    Node<T>* tmp = head; // need a copy of pointer
    head = head->next;   // need to advance BEFORE deleting
    delete tmp;          // now can delete safely
}

哇,这真的很有帮助!如果我理解正确的话,那么此 for 循环完成后,最后一个节点将指向 null?我是否正确理解了您的代码? - Erica
1
@Erica 不是的,在循环之后,最后一个节点的 next 指针仍未初始化。这就是为什么我们仍然需要显式赋值为空指针。 - Aconcagua
1
假设我们有 n_nodes == 0。那么循环根本不会进入,而 tmp 仍然指向 head(即 *tmp == head)。然后循环之后的赋值将通过 tmp 指针间接地将 head 初始化为 nullptr。当 n_nodes != 0 时,将进入循环。然后在第一次循环运行中,head 将直接被分配给新节点(没有被分配 nullptr,这是无关紧要的,因为这个赋值会被忽略掉...)。 - Aconcagua
1
假设头指针一开始指向一个我们称之为“垃圾”的值。我们现在可以在开始时用0覆盖这个“垃圾”。如果循环进入,那么这个0将被覆盖为例如0x1012,假设节点是在那里创建的。如果循环没有进入,那么由于*tmp == head,我们将再次将头部分配为0。因此,对0的初始赋值是徒劳的。这就是我想表达的...构造函数退出后,tmp指针丢失了,但是通过该指针进行的更改仍然存在! - Aconcagua
1
与以下代码进行比较:int n, m; int* p = &n; *p = 10; p = &m; *p = 12; - 现在 nm 的值将是什么?我的代码没有任何不同,只是 nm 的类型本身就是指针,并且我不分配整数字面量,而是地址。这就是全部。 - Aconcagua
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接