我一般对C++和指针很陌生,但我似乎无法解释为什么我在两个几乎相同的代码之一中出现分段错误。我正在做一个遍历LinkedList
的琐碎任务,这是我做的第一种方法:
Node * curNode = head; //Head is the pointer to the head of the LinkedList
while(curNode) {
curNode = curNode->next;
}
这将产生所需的结果并成功遍历整个链表。但是,这引发了一个Segmentation fault
错误:
Node * curNode = head;
while(curNode->next != NULL) {
curNode = curNode->next;
}
将条件更改为强制转换的布尔值或!= nullptr
不会执行任何操作,因此条件不会在空指针上引发true
。然而,我似乎无法弄清楚为什么我会得到Segmentation fault
,似乎适当的循环条件确保我不会访问任何空指针。
我可以看到这抛出段错误的唯一情况是head
指针本身是否NULL
。
为了确保head
不NULL
,以下方法应该有效并防止段错误:
Node * curNode = head;
if (head) {
while(curNode->next != NULL) {
curNode = curNode->next;
}
}
你也应该检查curNode
是否NULL
:
Node * curNode = head;
while(curNode && curNode->next != NULL) {
curNode = curNode->next;
}
好吧,它们并不相同:
while(curNode->next != NULL) {
curNode = curNode->next;
}
在第二个版本中没有对curNode
进行初始检查。
因此,您还需要检查curNode
:
while(curNode && curNode->next) {
curNode = curNode->next;
}
或者从循环外的exta检查开始:
if(curNode) // This will also check for the case where `head` would be `NULL`
while(curNode->next) {
curNode = curNode->next;
}
}
但这会跳过列表中的最后一项。因此,首选您的第一个版本:
while(curNode) {
curNode = curNode->next;
}
:
Node * curNode = head;
// presumably, head is initialized somehow in omitted code
while(curNode->next != NULL) {
curNode = curNode->next;
}
有两件严重的问题。一个是相对琐碎的问题,即当head
为空时,它不考虑这种情况。这会导致崩溃。
第二个问题是,即使解决了第一个问题,它在算法上仍然是不正确的。它忽略了遍历列表的最后一个节点。
如果 curNode
是非空指针,但curNode->next
是空的,这意味着"我们有一个有效的节点,它没有后继节点:curNode
是最后一个节点"。
因此,循环保护条件while (curNode->next != NULL)
表示"而curNode
不是最后一个节点"。 它不等效于遍历整个列表(包括最后一个节点)的第一个循环。
如果遍历除最后一个节点之外的所有节点是你想要的,那么它是正确的,我们可以理解为什么需要head
测试:如果head
为 null,则列表为空,因此它没有最后一个节点。由于它没有最后一个节点,因此空列表不能分区为"最后一个节点"和"其他所有节点"。这就是为什么它是一个特殊情况,以及为什么额外的空检查只是为了特殊情况而存在。我们可以这样写:
Node * curNode = head;
if (head) { // list is not empty: visit all but the last node
while (curNode->next != NULL) {
curNode = curNode->next;
}
} else { // special case: list has no last node
// do nothing? Or maybe some special behavior.
}
如果我们在特殊情况下不需要任何特殊行为(什么都不做是可以的),那么我们可以这样写:
while (head && curNode->next != NULL) { // fold the if test into the while
curNode = curNode->next;
}
我们在循环的每次迭代中浪费地测试head
;但是,编译器可以轻松地对其进行优化,因为head
在循环体中没有修改。这也是可能的:
while (curNode && curNode->next != NULL) {
curNode = curNode->next;
}
但是,它会浪费地在循环的每次迭代中测试curNode
。我们知道curNode
除了在第一次迭代之前之外,它永远不会为空。在随后的迭代中,curNode
的值是从 curNode->next
获得的,我们知道curNode->next
不为 null,因为这是进入迭代的条件。 也许您的编译器也可以解决这个问题,尽管它需要比优化对head
的访问进行更深入的分析。编译器必须遵循相同的数据流推理,即curNode
是从 curNode->next
获得的,这不是空的,但这仅在第二次和后续迭代中是正确的。
您的第二个代码片段很好,因此仅当 head NULL
时Segmentation fault
才可能发生错误。