【问题标题】:My program continously loop after reading nodes into linked list and displays only one node repeatedly我的程序在将节点读入链表后不断循环并重复只显示一个节点
【发布时间】:2012-03-24 20:53:47
【问题描述】:

我在 C++ 程序中创建了一个链接列表,该程序将节点写入二进制文件并将它们读回链接列表。然而,当返回列表时,程序只连续显示一个节点(连续循环)。

代码如下:

 #ifndef ACCOUNT_H
    #define ACCOUNT_H

    class Account{

private:
    double balance;
    unsigned int accountNumber;
    Account *next;
public:
    Account(){
        balance=0.0;
        accountNumber=0;
        next=NULL;
    }// primary constructor

    Account(unsigned int acc, double bal){
        accountNumber=acc;
        balance=bal;
        next=NULL;
    }// primary constructor

    Account::~Account(){
        //delete next;
    }// destructor


    void setBalance(double b){
        balance=b;
    }// end of setBalance


    double getBalance(){
        return balance;
    }

    void setAcc(unsigned int acc){
        accountNumber=acc;
    }

    unsigned int getAccNum(){
        return accountNumber;
    }

    //  links
    void setNextAccount(Account *nAcc){
        next=nAcc;
        //delete nAcc;
    }// Mutator for next

    Account *getNextAccount(){
        //Account *temp=next;
        return next;
    }
};
#endif

链表头文件:

#ifndef ACCOUNTLIST_H
#define ACCOUNTLIST_H

class AccountList{
private :
    Account *head;
    //Account * temp;
    //Account *current;
public:
    AccountList(){
        head=NULL;
        //temp=NULL;
        //current=NULL;
        //current=NULL;
    }// end of default constructor

    AccountList::~AccountList(){
        /*Account *temp;
        while (head!= NULL)
        {
            temp = head;
            head = head->getNextAccount();
            delete temp;
        } */
        delete head;
        //delete current;
    }// destructor for list

    void addNode(Account *h){
        //Account *temp;
        Account *current;
        //temp=h;
        //temp->setNextAccount(NULL);
        if(head==NULL){
            head=h;
        }
        else{
            current=head;
            while((current->getNextAccount())!=NULL){
                current= current->getNextAccount();
            }
            current->setNextAccount(h);
        }
        //delete current;
    }// mutator for head

    void displayAll(){
        Account *temp=head;
        while ((temp!=NULL) && (temp->getAccNum()!=0)){         
            cout << "Account number: " <<temp->getAccNum() << " has a balnce of: " << temp->getBalance() <<endl;
            temp=temp->getNextAccount();
        }

        delete temp;
    }

    void displayNode(int id){
        Account *temp= head;
        while (temp !=NULL){

            if (temp->getAccNum()==id){
                //temp->display();
                cout << "Account Number : " << temp->getAccNum() <<"has a balance of " << temp->getBalance() <<endl;
                delete temp;
                return;
            }
            temp= temp->getNextAccount();// gets next node in the list
        }
        cout << "Employer was not found" << endl;
    }// end of displayNode


    Account* getHead(){
        return head;
    }

    void removeAll(){
        Account *temp;
        while (head!=NULL){
            temp=head;
            head= head->getNextAccount();
            delete temp;
        }
    }// end of method removeAll

};

#endif

主驱动:

#include <iostream>
#include <fstream>
#include "Account.h"
#Include "AccountList"
//#include <cstdlib>
#include <stdlib.h>
using namespace std;

void main(){
    Account *acc1=new Account(1, 546.34); // create object and initialize attributes 
    Account *acc2=new Account(2,7896.34);
    Account *acc3=new Account();
    AccountList *list1= new AccountList();
    AccountList *list2= new AccountList();

    // add nodes to linked list
    list1->addNode(acc1);
    list1->addNode(acc2);
    //cout <<"Hello"<<endl;

    // file operation
    ofstream outAccount("account.dat", ios::out|ios::binary);

    // checks if ofstream could open file
    if(!outAccount){
        cerr<<"File could not be open" << endl;
        exit(1);
    }   
    acc3=list1->getHead();
    while( acc3!=NULL){
        outAccount.write(reinterpret_cast < const char*>(&acc3), sizeof(Account));
        acc3=acc3->getNextAccount();
        //outAccount.write(reinterpret_cast < const char*>(&acc2), `sizeof(Account));`
    }
    //cout <<"Hello"<<endl;
        outAccount.close();


    // read and display contents of file
    ifstream inAccount("account.dat", ios::in|ios::binary);
    if(!inAccount){
        cerr <<"File could not be openned for reading from file" << endl;
        exit(1);
    }
    //Account *accTemp=new Account();
    while(inAccount && !inAccount.eof()){

        inAccount.read(reinterpret_cast < char* >(&acc3), sizeof(Account));
        list2->addNode(acc3);

        //cout <<"Account Number : " << acc3->getAccNum()<< "has a balance of: " `<< acc3->getBalance() <<endl;`
    }

    inAccount.close();
    cout <<"Hello"<<endl;

    list2->displayAll();
    system("PAUSE");

    system("PAUSE");
}// end of main

【问题讨论】:

  • 到目前为止你尝试过什么?例如,您是否在调试器中运行过它?如果您表明自己确实陷入困境,人们将更有可能提供帮助,因为您已经付出了自己的努力。
  • 只看写逻辑这是行不通的:写构成对象的字节,特别是如果包含指针,如果有什么好处的话,对你没有多大帮助。通过快速浏览代码,很明显您可以删除许多不相关的垃圾:将程序简化为显示问题的最小示例,典型的ky 导致我看到问题 mysrlf。

标签: c++ linked-list binaryfiles


【解决方案1】:

您的代码存在许多问题,但我会尽量涵盖所有问题,而不是简单地转储修改后的代码清单,希望您能根据我的描述解决这些问题。

首先,没有必要重新发明链表“轮子”,因为 std::list 将为您提供您尝试使用 AccountList 类做的一切,但要这样做,您必须熟悉与迭代器。迭代器与指针本质上是一样的,但我承认它们可能会让有 C 背景的人感到困惑。无论如何,我讨论的其余部分假设您继续使用 AccountList 类,而不是使用 std::list。

其次,在您的 Account 和 AccountList 构造函数中,您应该使用初始化列表来初始化您的成员变量,因为变量不需要任何计算或复杂的逻辑。例如。这样做:

Account()
 : balance(0.0), accountNumber(0), next(NULL) {}

现在讨论实际的错误:

当您执行 outAccount.write() 时,您正在编写 acc3 的全部内容,包括它的指针。这些指针仅在那个时刻有效,并且仅对“list1”有效。下次运行程序时,系统可能在这些地址分配了其他东西,这些指针将不包含“帐户”对象,更不用说与以前相同的对象了。当您向下移动到 inAccount.read() 时,理解这一点很重要,您正在读取 Account 对象的旧内容以及旧指针值。此时这些地址不再有效,或者至少不适用于“list2”。当您调用 list2->addNode(acc3) 时,这一点很重要。现在去看看 AccountList::addNode()。您传入的 Account 对象“h”仍然在其“next”字段中包含旧指针值。读取 Account 对象的代码中没有任何内容将其“下一个”设置为 NULL,addNode() 也没有。如果您不选择使用 std::list,我建议您在 addNode() 执行任何其他操作之前通过调用 h->setNextAccount(NULL) 来启动它。这可以解决过时的指针错误。

在继续讨论下一个错误之前,我想提一下 AccountList::addNode() 的时间复杂度为 O(n)。随着您的 AccountList 大小的增长,扫描到列表末尾的时间会越来越长,以便将下一个 Account 对象简单地添加到列表中。你可以做得更好:如果你坚持将下一个Account添加到AccountList的末尾,那么在AccountList中维护一个指针,不仅指向head节点,还指向尾节点。这样 addNode() 将是时间复杂度 O(1)。或者,将新的 Account 对象添加到列表的头部:将新对象的 'next' 设置为当前头部,然后将 'head' 更改为新添加的 Account (这种方式显着简化了添加新 Account 对象的逻辑到列表)。请注意,尽管根据其帐号查找帐户仍然需要 O(n) 的顺序,并且如果您的软件正在处理数千个帐户,那么查找帐户的性能非常糟糕。如果您使用 std::map,您可以获得 O(log n) 帐户查找时间。在这种情况下,您的“addNode”时间也将是 O(log n),这比 O(1) 更糟糕,但是您会做更多的事情,添加帐户或查找现有帐户?可能正在查找现有帐户。

好的,现在是下一个错误:在 main() 中,当你第一次检查 inAccount.read() 循环的退出条件时,你甚至没有读入第一条记录。我想这没什么大不了的,但是当您最终阅读最后一个时, inAccount.eof() 还不是真的,所以您再次进入循环体,但这次 inAccount.read() 没有读取任何内容。所以 acc3 与上一次迭代相比没有变化。然后你仍然调用 list2->addNode(acc3) 即使你什么都没读。您将最后一条记录添加两次!我会让你弄清楚如何解决这个问题。

最后,您正在进行一些非常可怕的内存/对象管理。请注意,您永远不会在 main() 中删除两个动态分配的 AccountList。如果您打算让您的 AccountLists 在程序退出时由系统自动回收,这并不是什么大问题,但是如果您打算在程序的生命周期中不时出现多个 AccountLists,那么您需要确保他们干净地删除了他们的内容。看来您希望您的 AccountList 对象拥有分配给它们的 Account 对象,因为在您的 ~AccountList() 析构函数中,您删除了“头”Account 对象。由于 ~Account() 析构函数不会删除“下一个”对象,因此列表中唯一将被删除的 Account 对象是头 Account。由于 AccountList 只有一个指向 head 对象的指针,所以删除列表其余部分的有效位置可能在 ~Account() 中,正如我看到您考虑基于注释掉的删除调用所做的那样。正如您所考虑的那样,您可以让每个 Account 对象只删除它自己的“下一个”,但这种方法有一个微妙的错误:每个后续删除都会向运行时堆栈添加另一个函数调用,递归地在每个 Account 上调用 delete直到它到达列表的末尾。如果您有数千个 Account 对象,那么您肯定会多次破坏您的运行时堆栈。更好的方法如下:

~Account(){
    Account *victim, *itsNext = next;
    while (itsNext){
        victim = itsNext;
        itsNext = victim->next;
        victim->next = NULL; // prevent recursion & blown stack
        delete victim;
    }
    next = NULL;
}

嗯...我看到您有一个 AccountList::removeAll() 方法,该方法的结构似乎与我上面的 ~Account() 非常相似。也许你可以从你的 ~AccountList() 析构函数中调用它。

现在,如果您决定使用 std::list 或 std::map(我推荐使用 map),您不必担心内存泄漏或如何/何时删除,只要您的 std:: list 或 std::map 是实际 Account 对象的列表或映射,因为当列表/映射条目被删除(使用 erase() 或整个列表/映射被删除时),包含的 Account 对象被删除随之而来。如果它们是帐户指针的列表或映射,您仍然需要手动循环并删除它们。除非您使用 auto_ptrs 或 smart_ptrs 列表,但我离题了。

【讨论】:

  • 我已经考虑将 remove all 放在析构函数中,因为它做同样的事情
  • 还有关于在头部添加新节点的事情,这将大 O 减少到 O(1),从而使列表在添加节点时更快
【解决方案2】:

这里有一些问题,其中最严重的可能是您尝试将链表写入文件的方式。

当您重新读入文件时,您不能将指针成员写入文件并期望相同的内存地址包含相同的对象。您可能应该看看boost serialization library 或@987654322 之类的东西@。

这部分归咎于您的程序挂起;当你调用list2-&gt;addNode(acc3); 时,acc3 已经有一个next 的值,所以addNode 中的while 循环永远不会返回。

此外,在这里检查!inAccount.eof() 是没有用的,因为您在尝试第三次阅读之前不会点击eof。第三次读取失败(嗯,它读取 0 个字节),但您仍然调用 addNode。您可以使用inAccount.gcount() 查看阅读了多少内容。如果您在阅读后立即执行此操作,并且如果它不是预期的数量,那么break 将退出循环,您将避免额外的读取尝试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    相关资源
    最近更新 更多