帶著學生做課程設計。程序一大,課程中做過了小項目,練過了分解動作,一到合起來了,難免還是要亂了分寸。其實,實戰的功夫,就是這樣出來的。(課程設計指導視頻[鏈接](http://edu.csdn.net/course/detail/474)(第36課時,3.18 銀行系統開發),課程主頁在[鏈接](http://blog.csdn.net/sxhelijian/article/details/44117039),指導文檔見[鏈接](http://blog.csdn.net/sxhelijian/article/details/45046901),示例程序見[鏈接](http://blog.csdn.net/sxhelijian/article/details/45046497))。
話說,已經有兩位做銀行系統的同學和我說,“文件中寫不進去數據。程序一退出,明明寫進去了,結果卻是空文件。”這不是一個小打擊。
做軟件,找Bug,有些像打空氣,使半天勁,人家就不理你。學計算機的人,練的就是這樣的功夫,要學會自己創建線索,找出問題所在。
話說,出問題的兩位同學的程序,框架大體如下:
~~~
int main()
{
Bank b; //創建一個銀行對象
if (pass()) //用pass校驗用戶
{
Bank b;
b.work(); //完成各種業務
}
return 0;
}
class Bank
{
……
}
Bank::Bank()
{
ifstream infile("account.dat",ios::in);
if(!infile)
{
cerr<<"open error!"<<endl;
exit(1);
}
//下面的代碼,將之前發生過的業務數據從文件讀入銀行對象
infile.close();
}
Bank::~Bank()
{
ofstream outfile("account.dat",ios::out);
if(!outfile) //測試文件打開操作是否成功,不成功則提示后退出。
{
cerr<<"open error!"<<endl;
exit(1);
}
//下面的代碼,將銀行對象中的業務數據寫入文件
outfile.close();
delete p;
}
~~~
因為數據要在文件里存儲,所以,可選的方案是,在構造函數中讀文件,在析構函數中寫文件。上面的程序就是照這種思路設計的。
然而,程序退出后,文件就是空的。
老賀看了也納悶,寫文件的語句中規中矩,然而就是不對。
仔細審查析構函數中文件的打開方式ios::out,似乎有嫌疑,但排除了。在實際運行的系統中,ios::out的方式不常用,因為這樣一打開,也就意味著存在的文件也要重建,用ios::app的更多。
可是,在這個由大一學生實施的設計中,簡化的方案是,將所有的數據讀入內存,操作針對內存中的數據,而最后,就是要重建文件,將內存中的全部數據重寫一遍。
幾百行的程序,就不可以用眼睛盯著找問題了。單步跟蹤,對這樣的程序,如果問題具體在哪兒都不清楚,也不是一個好辦法。
析構函數中寫文件的部分最可疑。我在析構函數~Bank中加了一句`“cout<<"in destructor."<<endl;”`。結果發現,最后的,析構函數執行了兩次。
然后,在main函數的return 0;前加了一句`“cout<<"end of main"<<endl;”`,發現輸出的信息是:
> in destructor.
end of main
in destructor.
再看main函數,真相大白了。問題出在main函數中:Bank b出現了兩次:一個是屬于main函數的局部對象b(前者,第3行),另一個的作用范圍,只在if語句的一對花括號內的對象b(后者,第6行)。
程序初次執行,文件為空,前者執行構造函數,b中保存的是空業務。當用戶密碼驗證成功,會創建后者,自然業務信息也空。當執行完b.work();,會執行后者的析構函數,將這次業務后的業務信息保存在了文件中。文件內容不會是空。
然而,當程序的執行離開main函數時,其局部的變量b(前者)也要析構,這時就是問題之所在,這個b中的業務信息是空的,文件打開重建后,沒有要寫入的信息,最后就是空文件了。
所以,解決的辦法,將兩個Bank b;,無論前者或后者,去掉一個即可。
問題解決了,再反思。前述的問題自然不該發生,但這里設計的缺陷也存在。在程序中直接將文件名寫定,并且寫在構造函數和析構函數中,也就意味著該類的所有對象都用同一個文件(如同Person類中的每個對象都用同一個碗吃飯,多家銀行將數據存在一個文件中,太可怕了)。合理的做法是,采取某種機制,不同對象,使用不同的文件。
當然,對于本文中的問題,就是不該定義兩個 Bank b。