我在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
您的代码存在许多问题,但我将尽量涵盖所有问题,而不是简单地转储修改后的代码清单,希望您能够根据我的描述解决问题。
首先,没有必要重新发明链表的"轮子",因为std::list会给你所有你想用AccountList类做的事情,但要做到这一点,你必须熟悉迭代器。迭代器本质上和指针是一样的,但我承认它们可能会让有C背景的人感到困惑。在任何情况下,我讨论的其余部分假设您继续使用AccountList类,而不是使用std::list。
其次,在Account和AccountList构造函数中,应该使用初始化列表来初始化成员变量,因为变量不需要任何计算或复杂逻辑。例如:
Account()
: balance(0.0), accountNumber(0), next(NULL) {}
现在来看实际的bug:
当您执行outAccount.write()时,您正在编写acc3的整个内容,包括其指针。这些指针只在那一刻有效,而且只对'list1'有效。下次运行程序时,系统可能在这些地址上分配了其他东西,这些指针将不包含"Account"对象,更不用说以前的相同对象了。当您向下移动到inAccount.read()时,理解这一点很重要,因为您正在读取Account对象的旧内容以及旧指针值。此时,这些地址不再有效,或者至少不适用于"list2"。在调用list2->addNode(acc3)时,这一点很重要。现在去看看AccountList::addNode()。传入的Account对象"h"仍然在其"next"字段中包含旧的指针值。在Account对象中读取的代码中没有任何内容将其'next'设置为NULL, addNode()也没有。如果您不选择使用std::list<>,我建议您让addNode()在执行其他操作之前先调用h->setNextAccount(NULL)。这就解决了指针过时的问题。
在我继续下一个错误之前,我想提到AccountList::addNode()是时间复杂度O(n)。随着你的AccountList规模的增长,它将花费更长的时间。扫描到列表末尾的时间更长,以便简单地将下一个Account对象添加到列表中。您可以做得更好:如果坚持将下一个Account添加到AccountList的末尾,那么在AccountList中维护一个指针,不仅指向头节点,而且指向尾节点。这样,addNode()的时间复杂度为0(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()中两个动态分配的AccountLists。如果你想让你的AccountLists在你的程序退出时被系统自动回收,这不是一个大问题,但是如果你想有多个AccountLists来&在您的程序生命周期中,您需要确保它们干净地删除它们的内容。似乎您希望您的AccountList对象拥有分配给它们的Account对象,因为在~AccountList()析构函数中,您删除了'head' Account对象。由于~Account()析构函数不删除"下一个"对象,因此列表中唯一将被删除的Account对象是头部Account。由于AccountList只有一个指向头部对象的指针,因此删除列表其余部分的有效位置可能是~Account(),正如我看到您基于注释掉的删除调用所考虑的那样。正如您所考虑的那样,您可以让每个Account对象只删除它自己的"next",但是这种方法有一个微妙的错误:每个后续的删除都会向运行时堆栈添加另一个函数调用,递归地调用列表中的每个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对象的列表或映射,因为当列表/map条目被删除时(使用erase()或当整个列表/map被删除时),包含的Account对象也随之被删除。如果它们是Account指针的列表或映射,您仍然需要手动遍历并删除它们。除非您使用auto_ptrs或smart_ptrs列表,但我离题了。
这里有几个问题,其中最严重的可能是您试图将链表写入文件的方式。
你不能写指针成员到一个文件,并期望相同的内存地址包含相同的对象,当你读回文件。你可能应该看看boost序列化库或Google的Protocol Buffers。
这是程序挂起的部分原因;当你调用list2->addNode(acc3);
时,acc3
已经有了next
的值,所以addNode
中的while
循环永远不会返回。
此外,检查!inAccount.eof()
在这里是无用的,因为你不点击eof直到尝试读第三次。第三次读取失败(好吧,它读取0字节),但您仍然调用addNode
。您可以使用inAccount.gcount()
检查读取了多少。如果您在读取后立即执行此操作,并且如果它不是预期的数量,则break
退出循环,您将避免额外的读取尝试。