链表:分段故障

Linked List: Segmentation fault

本文关键字:故障 分段 链表      更新时间:2023-10-16

我想在这个页面上尝试一下链表的例子:如何使用c++创建链表。
这是我到目前为止的代码:

#include <iostream>
using namespace s-td;
class node {
public:
    int value;
    node *next;
};
int main() {
    node *head = nullptr;
    node *temp1;
    temp1 = new node();
    temp1->value = 1;
    temp1 = head;
    while(temp1->next!=nullptr) {
        temp1 = temp1->next;
    }
    return 0;
}

但不幸的是我得到了一个Segmentation fault。我认为这部分有问题。

while(temp1->next!=nullptr) {
    temp1 = temp1->next;
}

你的代码有两个问题:

a)你正在分配temp1 = head,在你的情况下是temp1 = nullptr的同义词

b)您没有将->next初始化为nullptr,因此while条件可能满足也可能不满足(未定义的行为)

node *head = nullptr;

headNULL

temp1 = head;

temp1变为NULL

while(temp1->next!=nullptr) {

temp1NULL,因此temp1->next是对NULL指针解引用。

程序以一个空的head指针开始遍历列表。如果你在调试器中查看它,head将仍然是NULL,因为你从未设置过它。

node *temp1, *temp2, *temp3;
temp1 = new node();
temp1->value = 1;
temp1->next = nullptr;
head= temp;   // link to head
temp2 = new node();
temp2->value = 2;
temp2->next = nullptr;
temp1->next= temp2;  // link 1 -> 2
temp3 = new node();
temp3->value = 3;
temp3->next = nullptr;
temp2->next= temp3;   // link 2 -> 3
// now we have head -> temp1 -> temp2 -> temp3
// now use temp1 as iterator starting with head
temp1 = head;
while(temp1->next!=nullptr) {
    printf("%dn", temp1->value);
    temp1 = temp1->next;
}

您正在分配:

node *head = 0;
// ...
temp1 = head;
while(temp1->next!=nullptr) { // temp1 is null here
    temp1 = temp1->next;
}

另外temp1->next也不会为空。你应该初始化它:

#define NULL 0
class node {
public:
    node(int value, node *next) { 
      this.value = value;
      this.next = next;
    }
    int value;
    node *next;
};
// ..
node *n3 = new node(1, NULL);
node *n2 = new node(1, n3);
node *n1 = new node(1, n2);

那篇文章对如何用C创建链表给出了合理的解释,并且使用了c++注释,但是它对如何用c++创建链表的解释很糟糕。

让我带你离开c思维模式,开始你的封装之路。

通常链表中的每个元素称为"节点",每个节点都有一个指向列表中一个或多个其他节点的指针。在"单链表"中,它指向下一个节点,在"双链表"中,有一个指向前节点和后节点的指针,允许我们向后和向前移动。

一个重要的早期决定是如何封装这个指向。您可以选择使用一个简单的"Node"类,该类私有于List类,该类具有prev/next指针和指向实际数据本身的离散"void*"指针。这是一种非常像c的方法,因为它丢弃了类型信息。但是你可以把类型存储为enum之类的?你可以,这是一个很棒的C解决方案。

但是c++是关于封装的。作为列表的节点是一个定义良好且相当离散的角色,具有一组简单的属性和属性。作为一个简单的基类,它非常适合封装。

class ListNode
{
    ListNode* m_next;
    ListNode() : m_next(NULL) {}
    ListNode* next() { return m_next; }
    // return the last node in my current chain.
    ListNode* tail() {
        ListNode* node = this;
        while (node->next() != nullptr)
            node = node->next();
        return node;
    }
    // insert a new node at this point in the chain,
    // think about what happens if newNode->next() is not null tho.
    bool linkTo(ListNode* newNode);
};
但是,一个同样重要的封装问题是,您是否希望任何人都能够调用ListNode成员,或者您是否希望将其可见性限制为List本身的访问器。当然,在某些模式中,处理抽象的"ListNode*"可能很有帮助,而不需要知道它们属于哪个列表。但是单链表也有限制——例如,在不知道列表本身的情况下,你永远不能删除一个条目(你怎么知道谁指向你?) 然而,这允许我们执行
class Product : public ListNode {
    string m_name;
    std::vector<float> m_priceHistory;
public:
    Product(const std::string& name_)
        : ListNode()
        , m_name(name_)
        , m_priceHistory()
        {}
    void save();
};
class Book : public Product { ... };
class DVD : public Product { ... };
List productList;
productList.add(new Book("hello world"));
productList.add(new DVD("lose wait: lose a limb"));
productList.add(new Insurance("dietary related gangrene"));
...
for(ListNode* node = productList.head(); node != nullptr; node = node->next()) {
    node->save();
}

另一种方法将把我们带回到第一个ListNode的想法,更像C;问题不在于它使用了指针,而在于它丢弃了类型信息。"void"主要用于当你想说"这个指针的类型无关紧要"的时候。缺点是,"这个指针的类型消失了"。我们可以用模板来解决这个问题。

template<typename T> // T will be an alias for whatever type we have to deal with.
class List
{
    struct  Node* m_list;
public:
    struct  Node
    {
        Node* m_next;
        T* m_data;
        Node() : m_next(nullptr), m_data(nullptr) {}
        Node* next() const { return m_next; }
        bool linkTo(Node* newPredecessor);
        bool link(Node* newFollower);
    };
public:
    List() : m_list(nullptr) {}
    Node* head() { return m_next; }
    Node* tail() {
        if (m_list == nullptr)
            return nullptr;
        for (Node* node = m_list; node->m_next != nullptr; node = node->m_next)
            {}
        return node;
    }
    void insert(T* data) { // add to head of the list
        Node* node = new Node(data);
        node->m_next = m_head;
        m_head = node;
    }
    Node* append(T* data) {
        if (head() == nullptr)
            insert(data);
        else {
            Node* node = new Node(data);
            Node* tail = this->tail(); // could get expensive.
            tail->link(node);
        }
        return node;
    }
};
List<Product> productList;
productList.append(new Book("Gone with the money"));
productList.append(new DVD("that's no moon"));
productList.append(new Pet("llama"));

这种方法的一个优点是,我们不必在数据的定义中添加额外的成员/混乱。缺点是我们使用更多的内存-每个节点需要两个指针,并且节点没有一种简单的方法来告诉你它们在列表中的位置(你必须搜索所有节点并找到指向你的项目的节点)。

在内存分配方面还有一个由使用决定的成本/收益。

在最新的迭代中,仍然有一个主要的c风格的组件,Node类太明显了。理想情况下,它应该对最终用户完全保密。但是,由于数据元素无法知道它们在列表中的位置,因此不可能遍历列表。

你想要的是隐藏Node的定义,这样只有list可以看到它(不,没关系,你真的不需要改变'm_next'的能力),并创建一个二级类,提供我们需要的功能,而不让我们做任何我们不应该做的事情。

public:
    class Iterator
    {
        Node* m_node;
    public:
        Iterator(Node* node_=nullptr) : m_node(node_) {}
        bool advance() {
            if (isValid())
                m_node = m_node->m_next;
            return isValid();
        }
        T* data() {
            return isValid() ? m_node->m_data : nullptr;
        }
        bool isValid() const { return (m_node != nullptr); }
        bool isTail() const { return isValid() && (m_node->m_next != nullptr); }
        // if you feel the need to get seriously advanced,
        // overload operator "++" to allow ++it;
        // overload operator "*" to allow (*it) to get m_data
        // overload operator "=" to allow Iterator* it = list->head();
    };
    // in class list:
        Iterator head() { return Iterator(m_head); }
        Iterator tail() {
            Iterator it(m_head);
            while (!it.isTail()) {
                it.advance();
            }
            return it;
        }
        Iterator find(const T& data_) { return find(&data_); }
        Iterator find(const T* data_) {
            for (Iterator it = head(); it.isValid(); it.advance()) {
                if(it.data() == data_)
                    return it;
            }
        }

希望这足以给你很多的想法:)