这里怎么了?而 (temp->ptr_Next != NULL) 失败,如果 (temp->ptr_Next == NULL) 有效

what's wrong here ? while (temp->ptr_Next != NULL) fails, if (temp->ptr_Next == NULL) works

提问人:JamesM 提问时间:4/20/2022 更新时间:6/3/2023 访问量:85

问:

我一直在制作一个双链表,而 while 语句有点不稳定。我确定有一个简单的解释,但我没有看到它。谁能帮忙?

这正在工作(Embarcadero RAD studio 11.1 - C++ Builder - 经典编译器(不是Clang))

TDoubleLinkedNode* __fastcall TDoubleLinkedList::CreateNode ( int                ID
                                                            , UnicodeString      Name
                                                            )

{
    // use "malloc" (or "new") to create the node in the heap
    // it won't be deleted automatically when the function goes out of scope
    // return the pointer to the structure
    // let the calling function set PriorNode & NextNode
    struct TDoubleLinkedNode* ReturnNode = (struct TDoubleLinkedNode*)malloc(sizeof(struct TDoubleLinkedNode));
    ReturnNode->ID            = ID;
    ReturnNode->Name          = Name;
    ReturnNode->ptr_PriorNode = NULL;
    ReturnNode->ptr_NextNode  = NULL;
    return ReturnNode;
}
void __fastcall TDoubleLinkedList::Add_ToEnd ( int                ID
                                                 , UnicodeString      Name
                                                 )
    {
       struct TDoubleLinkedNode* newNode     = CreateNode(ID,Name);
       if(this->IsEmpty)
       {
          // Head Pointer has not been initialised. Set as newNode
          this->FHeadNode = newNode;
          // This is the first Record. 
          newNode->ptr_PriorNode = NULL;
          newNode->ptr_NextNode  = NULL;
          return;
       }
       else
       {
          struct TDoubleLinkedNode* oldHeadNode = this->FHeadNode;
          struct TDoubleLinkedNode* tempNode    = this->FHeadNode;
          do   // keep iterating until a break statement is triggered
          {
             if (tempNode->ptr_NextNode == NULL)   // terminate the do while statement
             {
                break;
             }
             tempNode = tempNode->ptr_NextNode;    // Move to the "Next" record
          }
          while (true);   // always repeat...
          tempNode->ptr_NextNode = newNode;
          newNode->ptr_PriorNode = tempNode;
          newNode->ptr_NextNode  = NULL;
       }
    }

但是,如果我将

do 
{
   if (tempNode->ptr_NextNode == NULL)break;
}
while (true);

while (tempNode->ptr_NextNode != NULL)
{
    tempNode = tempNode->ptr_NextNode ;
}

当导致 tempNode 设置为 NULL 时,while 语句不会中断,从而使下面的语句失败(因为您无法将数据分配给不存在的对象)。tempNode->ptr_NextNode == NULLtempNode->ptr_NextNode = newNode

我一直在逐步执行,并且当tempNode->ptr_NextNode == NULL 时肯定在运行,当我的理解是它不应该??

我敢肯定,这并不是唯一一团糟的领域(有很多时间声明)。 我正在添加 6 条测试记录,但只能检索 5 条!所以很明显有些事情发生了。如果你能阐明我不理解的地方,我将不胜感激

谢谢,J

C++ 指针 null

评论

3赞 Remy Lebeau 4/20/2022
好吧,对于初学者来说,不要使用 /,而是使用 /,特别是因为包含一个属于类类型 () 的成员。更好的解决方案是使用并让它为您处理链接列表。malloc()free()newdeleteTDoubleLinkedNodeNamestd::list
2赞 user4581301 4/20/2022
仍在消化,但快速说明:在 C++ 中没有函数。使用并制作更智能的构造函数。CreateNodenewTDoubleLinkedNode
2赞 John Bollinger 4/20/2022
我看不出你提议的循环有什么特别的问题,尽管它比与 .如果你的代码没有像你预期的那样运行,那么我们希望看到一个最小的可重现示例来演示这个问题。whilenullptrNULL
1赞 PaulMcKenzie 4/20/2022
// use "malloc"-- 当包含或类似的非 POD 时,您的程序就会蓬勃发展。TDoubleLinkedNodestd::string
1赞 Remy Lebeau 4/20/2022
@PaulMcKenzie该成员显然是非 POD 类类型(正在为它分配一个对象)TDoubleLinkedNode::NameUnicodeString

答:

1赞 AQTS 4/20/2022 #1

我已经有20年没有使用CBuilder了,但是如果你所说的正是发生的事情,如果将“do while”循环替换为“while”会改变行为,并且你可以清楚地看到当你逐步完成它时发生的疯狂事情,这对我来说也是不合逻辑的。

我不知道现在怎么样了,但是在本世纪初,由于 C++ Builder 与 Object Pascal 库有很多复杂的链接,因此遇到非常奇怪的情况并不少见,在调试中发生了疯狂的事情。过去有帮助的是做一个“重建所有”,可能删除我在项目中找到的所有临时文件。

也许它会有所帮助。

我也支持我们的同事关于更新代码以使用更合适的 C++ 替代方案的评论,如果可能的话,更简单、更高效的替代方案(我知道有时您可能正在使用可能不容易更新的遗留软件)。

实际上,由于内容损坏,您也很可能会看到未定义的行为,正如人们在评论中所说的那样。只有你能确定。

更新:我刚刚看到了 Remy Lebeau 刚刚发布的另一个答案,我想补充一点,他对 malloc 的不当使用是正确的。搜索谷歌,我看到UnicodeString似乎是Object Pascal中的一个对象,是吗?真的似乎是一个非 POD,无论如何你都会遇到麻烦,不要将 malloc 用于 C++ 对象。

评论

0赞 JamesM 4/20/2022
感谢大家的回复。通过反馈: * 使用 new 而不是(我的第一个也是唯一一个)malloc 没有修复它 * 清理和构建没有修复它 * 我会看看 std::List ,谢谢你大喊大叫 * 我将开始使用 nullptr * 我想我最近尝试过一些 JSON 东西unique_ptr,我似乎记得它没有编译。我想我得出的结论是,它可能需要 clang 编译器(我现在仍在使用经典)。* 仅使用 “do (if (NULL)break ;}while(true);“ 现在列出了所有 6 个项目(所以看起来它正在工作)
0赞 AQTS 4/20/2022
非常疯狂的事情。是的,它可能总是你的编译器中的一个错误,但我认为你更有可能因为指针丢失或损坏而面临未定义的行为。请注意!
0赞 JamesM 4/20/2022
更多的反馈是“nullptr”。这是 C++11 的东西,所以 Embarcadero 经典编译器不支持它。您必须运行 Clang 编译器才能使用它,因此我将将它们更改回 NULL,直到我可以完成切换。
1赞 Remy Lebeau 4/20/2022 #2

你的循环代码很好(尽管编码很奇怪,而且效率低下)。while

很有可能,您正在代码中的其他位置调用未定义/非法的行为,这可能会影响循环作为副作用while

例如,不要在 C++ 中使用 /,而是使用 /。结构包含一个成员,该成员显然是非 POD 类类型,因为它被分配了一个值。当用于分配实例时,该成员的构造函数将不会运行,因此在将参数分配给成员时会调用未定义的行为malloc()free()newdeleteTDoubleLinkedListNameUnicodeStringmalloc()TDoubleLinkedListNameReturnNode->Name

话虽如此,您应该完全摆脱它,而是为自己添加一个适当的构造函数,例如:CreateNode()TDoubleLinkedNode

struct TDoubleLinkedNode
{
    int ID;
    UnicodeString Name;
    TDoubleLinkedNode *ptr_PriorNode;
    TDoubleLinkedNode *ptr_NextNode;

    TDoubleLinkedNode(int id, UnicodeString name, TDoubleLinkedNode *priorNode = NULL, TDoubleLinkedNode *nextNode = NULL) :
        ID(id),
        Name(name),
        ptr_PriorNode(priorNode),
        ptr_NextNode(nextNode)
    {
    }
};

然后可以简化:Add_ToEnd()

void __fastcall TDoubleLinkedList::Add_ToEnd ( int ID,
                                               UnicodeString Name
                                             )
{
    TDoubleLinkedNode* newNode = new TDoubleLinkedNode(ID, Name);
    if (!FHeadNode)
    {
        // Head Pointer has not been initialised. Set as newNode
        FHeadNode = newNode;
        return;
    }

    TDoubleLinkedNode* tempNode = FHeadNode;
    while (tempNode->ptr_NextNode) {
        tempNode = tempNode->ptr_NextNode;    // Move to the "Next" record
    }

    tempNode->ptr_NextNode = newNode;
    newNode->ptr_PriorNode = tempNode;
}

这实际上可以进一步简化:

void __fastcall TDoubleLinkedList::Add_ToEnd ( int ID,
                                               UnicodeString Name
                                             )
{
    TDoubleLinkedNode** tempNode = &FHeadNode;
    while (*tempNode) {
        tempNode = &((*tempNode)->ptr_NextNode);
    }
    *tempNode = new TDoubleLinkedNode(ID, Name, *tempNode);
}

如果您向班级添加成员,则更是如此:FTailNode

void __fastcall TDoubleLinkedList::Add_ToEnd ( int ID,
                                               UnicodeString Name
                                             )
{
    TDoubleLinkedNode **tempNode = (FTailNode) ? &(FTailNode->ptr_NextNode) : &FHeadNode;
    FTailNode = new TDoubleLinkedNode(ID, Name, FTailNode);
    *tempNode = FTailNode;
}

话虽如此,更好的解决方案是根本不手动创建链表。在标头中使用标准的 std::list 容器,例如:<list>

#include <list>

struct TNodeData
{
    int ID;
    UnicodeString Name;
};

class TDoubleLinkedList
{
private:
    std::list<TNodeData> FData;
public:
    ...
    void __fastcall Add_ToEnd(int ID, UnicodeString Name);
    ...
};

void __fastcall TDoubleLinkedList::Add_ToEnd ( int ID,
                                               UnicodeString Name
                                             )
{
    FData.push_back(TNodeData{ID, Name});
}

评论

0赞 JamesM 4/20/2022
虽然我感谢所有做出回应的人,但我想借此机会感谢你们为其他人提供的数千个答案。这可能不合适(把你挑出来)..但我数不清你多年来提供了我需要的答案(提供给其他人)的次数。- 我会看看 std::list - 你刚刚用指针让我大吃一惊,所以我也会看看。- 这话说不完。谢谢 ->大家<-
0赞 JamesM 4/20/2022
我刚刚注意到您对 TDoubleLinkedNode 构造函数上的变量默认值(指针)做了什么。马上就用上了!我将坚持使用手动链表作为“学习体验”,并在完成后切换到 std::list。谢谢