链表:分段故障
Linked List: Segmentation fault
我想在这个页面上尝试一下链表的例子:如何使用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;
head
是NULL
temp1 = head;
temp1
变为NULL
while(temp1->next!=nullptr) {
temp1
是NULL
,因此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;
}
}
希望这足以给你很多的想法:)
- 分段故障(堆芯转储)矢量
- 数组的指针从不分段故障
- Windows 10-使用gtkmm-3.0库和g++[包括再现]的分段故障
- 分段故障 运行C++代码时出现 SIGSEGV
- 分段故障背包问题
- 分段故障 11,从类函数显示动态 C 字符串
- 面临分段故障 使用 ffmpeg 读取视频时,因为"pFormatCtx-> streams [i]-> codecpar"的地址0x00
- 在C++中,当指向删除和指向不同对象时,分段故障指针
- 对程序故障进行分段
- 分段故障说明
- 分段故障(核心转储)-不知道为什么
- 分段故障线程
- hiredis SET遇到分段故障
- 分段故障,合并排序算法
- 多线程程序中的分段故障和gdb回溯上的不完整信息
- 到达主C++之前分段故障
- 分段故障核心使用 IF流转储
- 使用向量的移动键盘排列(分段故障)
- 在二进制树插入和遍历期间,我得到了分段故障
- 分段故障在类之间返回整数