【问题标题】:Exception thrown: write access violation. prev_ptr was 0x4抛出异常:写访问冲突。 prev_ptr 是 0x4
【发布时间】:2021-07-02 13:01:04
【问题描述】:

我正在学习链表,我决定尝试自己在 C++ 中实现链表。

我创建了一个带有int valNode* ptr 属性的Node 类。 然后我做了一个Linked_list类,属性为first_node,构造函数就起作用了。

append() 函数将一个节点“追加”到列表中(就像在 Python 中一样)。我首先想到的只是让ptr成为对节点指针的引用,然后在它为空时更改它,但是一旦引用,就不能更改为引用任何其他变量,所以我创建了另一个变量prev_ptr指向指向Node 的指针(使其成为Node**)。

每个循环,它检查ptr是否为NULL,如果不是,ptrprev_ptr被更新为下一个Node的指针值,以及下一个Node的指针地址值。

这种情况一直发生,直到找到一个空指针,然后将其更改为输入节点的地址。

但我收到一条错误消息:

抛出异常:写访问冲突。 prev_ptr 为 0x4。

我不知道出了什么问题。

类:

#include <iostream>
#include <string>
#include <cmath>

class Node {
    public:
        int val;
        Node* ptr = nullptr;
        Node(int Val = NULL) {
            val = Val;
        }
};

class Linked_list {
    public:
        Node first_node;
        Linked_list(int F) {
            Node f(F);
            first_node = f;
        }
        void append(Node& element) {
            Node* ptr = first_node.ptr;
            Node** prev_ptr = &first_node.ptr;
            while (true) {
                if (ptr == nullptr) {
                    *prev_ptr = &element;
                    break;
                }
                ptr = (*ptr).ptr;
                prev_ptr = &((*ptr).ptr);
            }
        }
};

main()

int main() {
    Linked_list list(5);
    Node three(3);
    list.append(three);
    Node four(4);
    list.append(four);
    return 0;
}

【问题讨论】:

  • 使用调试器单步调试程序。请留意错误地址的来源。这几乎总是比提出 SO 问题要快。
  • 提示:当你更新prev_ptr时,记住你已经更新了ptr
  • 节点应该对用户不可见。我想将ints 添加到列表中。不要在堆栈上创建节点,然后将节点添加到我的列表中。
  • 注意:您可以使用ptr-&gt;/*something*/,而不是(*ptr)./*something*/
  • @HaydenSoares 链表节点通常是*动态分配的(例如new。这将解决您在本地创建Node 变量的问题。您只需delete 它们当您使用它们完成,即在列表类的析构函数和赋值运算符中。

标签: c++ class linked-list


【解决方案1】:
            ptr = (*ptr).ptr;
            prev_ptr = &((*ptr).ptr);

首先将ptr 推进到下一个节点。然后,您再次使用ptr,忘记了它已经提前了:(*ptr).ptr 现在指向两个节点,我们不知道我们是否可以走那么远。

也许你需要交换作业。

            prev_ptr = &((*ptr).ptr);
            ptr = (*ptr).ptr;

(此外,为什么不ptr-&gt;ptr?)

【讨论】:

    【解决方案2】:

    代码及说明

    我认为这段代码也应该可以工作,所以不需要两个指针。它基于 Linus Torvalds 在一次采访中给出的example of “good taste”

    void append(Node &element)
    {
        Node** cursor = &first_node.ptr;
        while ((*cursor) != nullptr)
            cursor = &(*cursor)->ptr;
        *cursor = &element;
    }
    

    它消除了对多个指针的需求,消除了边缘情况,它允许我们评估 while 循环的条件,而不必放开指向下一个元素的指针。这使我们可以修改指向NULL 的指针,并使用单个迭代器而不是ptrprev_ptr

    命名约定

    另外规范是调用链表中的第一个节点head,并调用指向下一个节点的指针next而不是ptr,所以我将在下面的代码中重命名它们。

    void append(Node &new)
    {
        Node** cursor = &head.next;
        while ((*cursor) != nullptr)
            cursor = &(*cursor)->next;
        *cursor = &new;
    }
    

    【讨论】:

    • 完全同意。更好的方法,但作为一个 SO 答案,它需要一些肉来解释你在做什么以及为什么。否则,人们会在没有真正了解它的作用和工作原理的情况下出现并复制它。世界已经够了Cargo Cult Programmers
    • @user4581301 你完全正确!我现在已经解释了我的代码并修复了一些问题。我也对其进行了测试,它似乎可以工作。
    • 关于“为什么起作用”的一些额外解释:将指针指向 ptr 允许您将插入点(以前的 prev_ptr 与当前指针结合起来。这也抽象了用于相同目的但具有不同名称的指针之间的所有差异,例如 headptr,并且可以消除大量处理不同名称的冗余代码。
    【解决方案3】:

    好的,你正在以一种奇怪的方式做一些事情。首先,您的 Linked_List 可能不应该有 firstNode 的节点。它应该有一个 Node *.毕竟,空列表是可能的。 (通常)删除第一个节点也是如此。此外,还有一个非正式的命名约定,称为head。还有一个标准约定是在您的节点中调用链接next 而不是ptr

    但是您的 append() 方法有两个更简单的方法。首先,您还可以在 Linked_List 中保留一个 Node * 尾。这很常见。它指向列表中的最后一个节点。如果你这样做,那么追加看起来像:

    void append(Node &nodeToAppend) {
        if (head == nullptr) {
            head = &nodeToAppend;
            tail = &nodeToAppend;
        }
        else {
            tail->next = nodeToAppend;
            tail = &nodeToAppend;
        }
    }
    

    但是,能够在任何地方插入或在没有尾部的情况下追加也是值得的:

     void append(Node &nodeToAppend) {
         if (head == nullptr) {
             head = &nodeToAppend;
         }
         else {
             Node *ptr = head;
             while (ptr->next != nullptr) {
                  ptr = ptr->next;
             }
             ptr->next = &nodeToAppend;
         }
     }
    

    以某种排序顺序插入几乎是相同的,尽管略有不同。 while 循环看起来像:

             while (ptr->next != nullptr && ptr->value < nodeToAppend.value) ...
    

    但否则会是相同的。

    【讨论】:

    • 关于非常规的名称和实现,我只知道链表包含的节点包含一个值和一个指向下一个节点的指针,然后再执行此操作。在了解实际实施方式之前尝试实施某事是否被认为是不好的做法?
    • @HaydenSoares 不。我只是想帮助你改进和遵循在你的水平上你不应该知道的约定。
    【解决方案4】:

    此代码不能解决您的直接问题,但会回答 cmets 中提出的问题。

    链接列表通常是(我教过的)300-400 级的作业。编写一个像样的链表必须具备许多原则。首先,我将展示main.cpp 及其输出。

    ma​​in.cpp

    #include "list.hpp"
    #include <iostream>
    
    template <typename Container>
    void print(Container& container, std::ostream& sout = std::cout)
    {
        for (auto i : container) {
            sout << i << ' ';
        }
        sout << '\n';
    }
    
    int main()
    {
        List<int> list;
        for (int i = 1; i <= 10; ++i) {
            list.push_back(i);
        }
        print(list);
    
        list.erase(list.find(4));
        print(list);
        list.erase(list.find(1));
        print(list);
        list.erase(list.find(10));
        print(list);
    }
    

    输出:

    1 2 3 4 5 6 7 8 9 10 
    1 2 3 5 6 7 8 9 10 
    2 3 5 6 7 8 9 10 
    2 3 5 6 7 8 9 
    

    它不会测试链表的每个方面,但它可以用来展示用户应该使用什么。用户将希望直接与 C++ 中的列表及其迭代器进行交互。您创建一个Node,然后将Node 添加到您的列表中。这是用户不想被打扰的 DIY 水平。在下面的代码中,您会看到仍然使用 Node,但它只存在于 List 类中。用户永远不会看到Node

    您可以在push_back()(类似于您的附加)之类的函数中查看与您的问题相关的具体答案。

    为了解释更多,指针是关键。是的,我声明了一个本地 Node* 将超出范围,但创建的对象继续存在于堆上。由于链表的工作方式,该列表能够跟踪这些Nodes,即Nodes 知道他们的邻居住在哪里(保存他们的地址)。

    还有一个List&lt;T&gt;::iterator 类。在声明中,如果要在基于范围的 for 循环中使用链表,则需要标记为 // minimum 的函数。其他功能确实可以满足LegacyBidirectionalIterator 的要求;这是 C++ 标准库中std::list 使用的迭代器级别。

    下面的代码应该只是一个不错的例子(希望我不要太冒昧)。它缺少std::list 中的一些功能,并且可能以非最佳方式做一些事情。需要调整的一件大事是删除成员函数 find() 并使类与 std::find() 一起工作。

    list.hpp

    #ifndef MY_LIST_HPP
    #define MY_LIST_HPP
    
    #include <algorithm>  // std::swap
    #include <cstddef>  // std::size_t
    
    /*
     * Pre-declare template class and friends
     */
    
    template <typename T>
    class List;
    
    template <typename T>
    void swap(List<T>& lhs, List<T>& rhs);
    
    /*
     * List Class Declaration
     */
    template <typename T>
    class List {
     public:
      List() = default;
      List(T val);
      List(const List& other);
      List(List&& other);
      ~List();
    
      void push_front(T val);
      void push_back(T val);
    
      class iterator;
      iterator begin();
      iterator end();
      iterator find(T val);
    
      std::size_t size() const;
    
      iterator erase(iterator toErase);  // Implement
      void clear();
      bool operator=(List other);
      friend void swap<T>(List& lhs, List& rhs);
    
     private:
      struct Node {
        T data;
        Node* prev = nullptr;
        Node* next = nullptr;
    
        Node(T val) : data(val) {}
      };
    
      Node* m_head = nullptr;
      Node* m_tail = nullptr;
      std::size_t m_size = 0;
    
      // Helper functions
      void make_first_node(T val);
      Node* find_node(T val);
    };
    
    /*
     * List Iterator Declaration
     */
    template <typename T>
    class List<T>::iterator {
     public:
      iterator() = default;
      iterator(List<T>::Node* node);  // minimum
      T& operator*();                 // minimum
      iterator& operator++();         // minimum
      iterator operator++(int);
      iterator& operator--();
      iterator operator--(int);
      bool operator==(const iterator& other);  // minimum
      bool operator!=(const iterator& other);  // minimum
     private:
      Node* m_pos = nullptr;
    };
    
    /*
     * List Implementation
     */
    template <typename T>
    List<T>::List(T val) : m_head(new Node(val)), m_tail(m_head), m_size(1) {}
    
    template <typename T>
    List<T>::List(const List<T>& other) {
      m_head = new Node((other.m_head)->data);
      m_tail = m_head;
      m_size = 1;
    
      Node* walker = (other.m_head)->next;
      while (walker) {
        push_back(walker->data);
        ++m_size;
        walker = walker->next;
      }
    }
    
    template <typename T>
    List<T>::List(List&& other) : List() {
      swap(*this, other);
    }
    
    template <typename T>
    List<T>::~List() {
      clear();
    }
    
    template <typename T>
    void List<T>::push_front(T val)
    {
      if (!m_head) {
        make_first_node(val);
        return;
      }
    
      Node* tmp = new Node(val);
      tmp->next = m_head;
      m_head->prev = tmp;
      m_head = tmp;
      ++m_size;
    }
    
    template <typename T>
    void List<T>::push_back(T val) {
      if (!m_head) {
        make_first_node(val);
        return;
      }
    
      Node* tmp = new Node(val);
      tmp->prev = m_tail;
      m_tail->next = tmp;
      m_tail = tmp;
      ++m_size;
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::begin() {
      return iterator(m_head);
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::end() {
      return iterator(nullptr);
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::find(T val) {
      return iterator(find_node(val));
    }
    
    template <typename T>
    std::size_t List<T>::size() const {
      return m_size;
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::erase(typename List<T>::iterator toErase)
    {
      Node* node = find_node(*toErase);
    
      if (node->prev) {
        node->prev->next = node->next;
      } else {
        m_head = node->next;
      }
    
      if (node->next) {
        node->next->prev = node->prev;
      } else {
        m_tail = node->prev;
      }
    
      Node* toReturn = node->next;
      delete node;
    
      return toReturn;
    }
    
    template <typename T>
    void List<T>::clear() {
      Node* tmp = m_head;
      while (m_head) {
        m_head = m_head->next;
        delete tmp;
        tmp = m_head;
      }
      m_tail = nullptr;
      m_size = 0;
    }
    
    template <typename T>
    bool List<T>::operator=(List other) {
      swap(*this, other);
    
      return *this;
    }
    
    template <typename T>
    void List<T>::make_first_node(T val) {
      m_head = new Node(val);
      m_tail = m_head;
      m_size = 1;
    }
    
    template <typename T>
    typename List<T>::Node* List<T>::find_node(T val) {
      if (!m_head) {
        return nullptr;
      }
    
      Node* walker = m_head;
      while (walker != nullptr && walker->data != val) {
        walker = walker->next;
      }
    
      return walker;
    }
    
    template <typename T>
    void swap(List<T>& lhs, List<T>& rhs) {
      using std::swap;
    
      swap(lhs.m_head, rhs.m_head);
      swap(lhs.m_tail, rhs.m_tail);
      swap(lhs.m_size, rhs.m_size);
    }
    
    /*
     * List Iterator Implementation
     */
    template <typename T>
    List<T>::iterator::iterator(Node* node) : m_pos(node) {}
    
    template <typename T>
    T& List<T>::iterator::operator*() {
      return m_pos->data;
    }
    
    template <typename T>
    typename List<T>::iterator& List<T>::iterator::operator++() {
      m_pos = m_pos->next;
    
      return *this;
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::iterator::operator++(int) {
      iterator tmp(m_pos);
      ++(*this);
    
      return tmp;
    }
    
    template <typename T>
    typename List<T>::iterator& List<T>::iterator::operator--() {
      m_pos = m_pos->prev;
    
      return *this;
    }
    
    template <typename T>
    typename List<T>::iterator List<T>::iterator::operator--(int) {
      iterator tmp(m_pos);
      --(*this);
    
      return tmp;
    }
    
    template <typename T>
    bool List<T>::iterator::operator==(const iterator& other) {
      return m_pos == other.m_pos;
    }
    
    template <typename T>
    bool List<T>::iterator::operator!=(const iterator& other) {
      return !(*this == other);
    }
    
    #endif
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-12
      • 2017-01-27
      • 2022-01-08
      • 2020-04-15
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      相关资源
      最近更新 更多