课程设计完成后用压缩软件将源代码打包,以附件形式发送邮件到 [email protected] ,标题注明课程设计四个字并写上学号、姓名,比如"课程设计-0509005208-苏晶晶"。

面向对象课程设计

1. 通讯录

1.1. 需求

数据定义:

功能说明:

输入输出格式:程序以命令行方式交互式运行,命令包括:

使用举例:

添加联系人,姓名叫做czk
 * add czk 
为czk添加属性mobile
 * update czk add phone mobile 13587654600  
为czk添加属性work
 * update czk add phone work 614600 
为czk添加属性qq
 * update czk add numeric qq 245149 
为czk添加属性birth
 * update czk add date birth 1979 8 27 
为czk添加属性email
 * update czk add text email [email protected] 
添加联系人ymc
 * add ymc 
 * update ymc add phone mobile 13958895577
 * update ymc add phone work 665577
 * update ymc add text email [email protected]
显示所有的联系人
 * list 
显示czk的详细信息
 * show czk  
查找4600,应该可以找到czk
 * search 4600  
查找wzu.edu.cn,应该可以找到czk和ymc
 * search wzu.edu.cn 
把czk的email属性改为另一个值
 * update czk update email [email protected] 
删除czk的mobile属性
 * update czk remove mobile
将所有联系人存入contact.dat文件 
 * save contact.dat 
删除联系人ymc
 * remove ymc 
从contact.dat载入所有联系人信息(当前内存中的所有联系人被清空,替换成文件中所存储的联系人)
 * load contact.dat 

参考设计图:

1.2. 第一步

首先,做一个可以运行的主程序框架,可以接受输入各种指令,但是还不能实现任何功能。

   1 #include <iostream>
   2 #include <string>
   3 using namespace std;
   4 
   5 int main() {
   6     string command;
   7     string prompt = ">>> ";
   8     cout << prompt;
   9     while (cin >> command) {
  10         if(command == "list") {
  11 
  12         } else if(command == "show") {
  13 
  14         } else if(command == "add") {
  15 
  16         } else if(command == "remove") {
  17 
  18         } else if(command == "update") {
  19             
  20         } else if(command == "search") {
  21 
  22         } else if(command == "save") {
  23 
  24         } else if(command == "load") {
  25 
  26         } else if(command == "exit") {
  27             return 0;
  28         } else {
  29             cout << "Unknown command" << endl;
  30         }
  31         cout << prompt;
  32     }
  33 }

运行这个程序,输入正确命令不会有结果,输入错误命令提示Unknown command,输入exit退出程序。

1.3. 第二步

接下来为程序增加添加联系人和列出联系人清单的功能。先定义一个简单的Contact类:

   1 class Contact {
   2     string name; // the name of contact
   3 public:
   4     /** 初始化联系人名字为n*/
   5     Contact(string n) {
   6         /*......*/
   7     }
   8     /** 取联系人的名字 */
   9     string get_name() {
  10         /*......*/
  11     }
  12 
  13 };

然后定义ContactList类,实现添加联系人和取其中的联系人的功能

   1 class ContactList{
   2     Contact *contacts[100]; ///< 联系人数组
   3     int num; ///< 数组中的联系人总数
   4 public:
   5     /** 初始化列表为空表 */
   6     ContactList() {
   7         /*......*/
   8     }
   9     /** 在列表中添加一个名字叫做name的联系人 */
  10     void add(string name) {
  11         /*......*/
  12     }
  13     /** 取第i个联系人 */
  14     Contact* get(int i) {
  15         /*......*/
  16     }
  17     /** 列表中联系人总数 */
  18     int size() {
  19         /*......*/
  20     }
  21 };

最后修改main函数,使它可以完成添加、列出清单的功能

   1 int main() {
   2     ContactList list;
   3     //......
   4         if(command == "list") {
   5             for(int i = 0; i < list.size(); i++)
   6                 cout << list.get(i)->get_name() << endl;
   7         } else if(command == "add") {
   8             string name;
   9             cin >> name;
  10             list.add(name);
  11         }
  12     //......
  13 }

运行这个程序,可以完成add和list的功能

1.4. 第三步

为程序添加删除功能。为ContactList添加如下成员:

   1 //......
   2     /** 删除位置在第i个的联系人 */
   3     void remove(int i) {
   4         /*......*/
   5     }
   6     /** 查找名字是name的联系人,返回联系人的位置。找不到返回-1 */
   7     int find(string name) {
   8         /*......*/
   9     }
  10 
  11     /** 删除名字是name的联系人 */
  12     void remove(string name) {
  13         int i = find(name);
  14         if(i != -1)
  15             remove(i);                
  16     }
  17 //......
  18 

然后修改main函数:

   1 //......
   2         if(command == "remove") {
   3             string name;
   4             cin >> name;
   5             list.remove(name);
   6         }
   7 //......
   8 

运行这个程序,可以完成remove功能了。

1.5. 第四步

接下来,让程序可以为联系人添加属性。首先添加Item类:

   1 class Item {
   2     string name; ///<属性项的名字
   3 public:
   4     /// 初始化属性的名字是n
   5     Item(string n) {
   6         /*......*/
   7     }
   8     /// 获取属性的名字
   9     string get_name() {
  10         /*......*/
  11     }
  12 };

然后为Contact类添加成员:

   1 //......
   2     Item* items[100];
   3     int num;
   4 //......
   5     /// 添加属性项
   6     void add(Item *item) {
   7         items[num++] = item;
   8     }
   9     
  10     /// 添加类型是type,名字是name,值是value的属性项
  11     void add(string type, string name, string value) {
  12         add(new Item(name)); //这里暂时不区分类型,只是创建简单的Item类型对象添加进去
  13     }
  14     
  15     /// 属性项的总数
  16     int size() {
  17         /*......*/
  18     }
  19 
  20     /// 取某一项属性
  21     Item *get(int i) {
  22         /*......*/
  23     }
  24 //......
  25 

然后修改main函数,处理输入的update命令

   1         if(command == "update") {
   2             string name, subcommand, item_name, item_type, item_value;
   3             cin >> name;
   4             int i = list.find(name);
   5             if(i != -1) {
   6                 Contact *c =  list.get(i);
   7                 cin >> subcommand;
   8                 if(subcommand == "add") {
   9                     cin >> item_type >> item_name;
  10                     getline(cin, item_value);
  11                     c->add(item_type, item_name, item_value);
  12                 } else if (subcommand == "remove") {
  13                     
  14                 }
  15             }
  16         }

现在就可以运行程序,为联系人添加属性了。但是还不能看到添加进去的属性。

1.6. 第五步

为程序添加查看联系人详细信息的功能。

   1 //......
   2         if(command == "show") {
   3             string name;
   4             cin >> name;
   5             int i = list.find(name);
   6             if(i!=-1) {
   7                 Contact *c = list.get(i);
   8                 for(int j = 0; j < c->size(); j++) {
   9                     cout << c->get(j)->get_name() << endl;
  10                 }
  11             }            
  12         } 
  13 //......
  14 

运行程序,可以看到添加进去的属性的名字,但是还没有属性的值。

1.7. 第六步

删除联系人的属性。为Contact类添加成员:

   1     /// 删除联系人的第i个属性项
   2     void remove(int i) {
   3         /*......*/
   4     }
   5     /// 查找属性名字是n的编号,找不到返回-1
   6     int find(string n) {
   7         /*......*/
   8     }
   9     /// 删除属性名称是n的属性
  10     void remove(string n) {
  11         int i = find(n);
  12         if(i!=-1)
  13             remove(i);
  14     }

修改main函数

   1 //......
   2                 if(subcommand == "add") {
   3                     cin >> item_type >> item_name;
   4                     getline(cin, item_value);
   5                     c->add(item_type, item_name, item_value);
   6                 } else if (subcommand == "remove") {
   7                     cin >> item_name;
   8                     c->remove(item_name);
   9                 }
  10 //......
  11 

1.8. 第七步

添加各种不同类型的Item。定义Item的五个派生类

   1 #include <sstream>
   2 using namespace std;
   3 
   4 class DateItem : public Item{
   5     int year, month, day;
   6 public:
   7     ///构造一个名字是n,日期是value的日期项。value以字符串形式表示,比如"1979 8 27"。
   8     DateItem(string n, string value) :Item(n) {
   9         istringstream is(value);
  10         is >> year >> month >> day;
  11     }
  12 };
  13 
  14 class AddressItem : public Item {
  15 /*......*/
  16 };
  17 
  18 class PhoneItem : public Item {
  19 /*......*/
  20 };
  21 
  22 class NumericItem: public Item {
  23 /*......*/
  24 };
  25 
  26 class TextItem: public Item {
  27 /*......*/
  28 };

修改Contact的add函数:

   1 //......
   2     /// 添加类型是type,名称是name,值是value的属性
   3     void add(string type, string name, string value) {
   4         if(type == "date") {
   5             add(new DateItem(name, value);
   6         } else if(type == "phone")
   7             /*......*/
   8     }
   9 //......
  10 

现在,添加的各种属性的值都能保存下来了。但是在查看详细信息的时候,还只有属性的名字,而没有属性的值。

1.9. 第八步

显示联系人所有详细信息。为Item各种派生类添加to_string函数,把属性值转换成字符串,比如:

   1 class DateItem : public Item{
   2     int year, month, day;
   3 public:
   4     DateItem(string n, string value) :Item(n) {
   5         istringstream is(value);
   6         is >> year >> month >> day;
   7     }
   8     ///把属性值转换成字符串
   9     string to_string() {
  10         ostringstream os;
  11         os << get_name()<< " " << year << " " << month << " " << day;
  12         return os.str(); 
  13     }
  14 };
  15 
  16 class TextItem :public Item {
  17 //......
  18     string to_string() {
  19         /*......*/
  20     }
  21 };
  22 
  23 class AddressItem :public Item {
  24 //......
  25     string to_string() {
  26         /*......*/
  27     }
  28 };
  29 
  30 class PhoneItem :public Item {
  31 //......
  32     string to_string() {
  33         /*......*/
  34     }
  35 };
  36 
  37 class NumericItem :public Item {
  38 //......
  39     string to_string() {
  40         /*......*/
  41     }
  42 };

为Item类添加虚函数to_string:

   1 class Item {
   2 //......
   3     virtual string to_string() {
   4         return name;
   5     }
   6 };

修改main函数,把show中get_name改成to_string:

   1 //......
   2         if(command == "show") {
   3             string name;
   4             cin >> name;
   5             int i = list.find(name);
   6             if(i!=-1) {
   7                 Contact *c = list.get(i);
   8                 for(int j = 0; j < c->size(); j++) {
   9                     cout << c->get(j)->to_string() << endl;
  10                 }
  11             }            
  12         }
  13 //......
  14 

编译执行程序,现在可以使用show命令了。

1.10. 第八点五步

增加update属性的功能。在Item类中增加纯虚函数update

   1     /// 显示更新Item的值为value
   2     virtual void update(string value) = 0; 

在Item的各个派生类中,实现update的功能

   1 class DateItem: public Item {
   2 //......
   3     void update(string value) {
   4         istringstream is(value);
   5         is >> year >> month >> day;    
   6     }
   7 };
   8 
   9 class PhoneItem: public Item {
  10 //......
  11     void update(string value) {
  12         /*......*/   
  13     }
  14 };
  15 
  16 class TextItem :public Item {
  17 //......
  18     void update(string value) {
  19         /*......*/
  20     }
  21 };
  22 
  23 class AddressItem :public Item {
  24 //......
  25     void update(string value) {
  26         /*......*/
  27     }
  28 };
  29 
  30 class NumericItem :public Item {
  31 //......
  32     void update(string value) {
  33         /*......*/
  34     }
  35 };

在Contact类中,增加update_item成员函数

   1     //更新属性名字是item_name的属性,把它的值改为value
   2     void update_item(string item_name, string value) {
   3         /*......*/
   4     }

在main函数中增加处理update的部分:

   1 //......
   2                 if(subcommand == "add") {
   3                     string type, item_name, value;
   4                     cin >>type >> item_name;
   5                     getline(cin, value);
   6                     c->add_item(type, item_name, value);
   7                 } else if(subcommand == "remove") {
   8                     string item_name;
   9                     cin >> item_name;
  10                     c->remove_item(item_name);
  11                 } else if(subcommand == "update") {
  12                     string item_name, value;
  13                     cin >> item_name;
  14                     getline(cin, value);
  15                     c->update_item(item_name, value);
  16                 }
  17 //......
  18 

编译运行这个程序,这个时候已经可以更新已有属性的值了。

注意,在每个Item的派生类中,构造函数的实现和update函数的实现类似,可以将构造函数改成调用update函数,比如:

   1 class DateItem: public Item {
   2     int year, month, day;
   3 public:
   4     DateItem(string n, string v)
   5     :Item(n) {
   6         update(v);
   7     }
   8     virtual void update(string value) {
   9         istringstream is(value);
  10         is >> year >> month >> day;    
  11     }
  12 //......
  13 };

1.11. 第九步

添加搜索功能。首先为Contact类添加contain函数,判断一个联系人是否包含某个搜索的关键字,包含返回true,否则返回false。

   1 //.....
   2     ///判断某个联系人是否包含key这个字符串,包含返回true,否则返回false。
   3     bool contain(string key) {
   4         /*......*/
   5     }
   6 //.....
   7 

然后修改main函数,处理search命令:

   1         if(command == "search") {
   2             string key;
   3             cin >> key;
   4             for(int i = 0; i < list.size(); i++)
   5                 if(list.get(i)->contain(key))
   6                     cout << list.get(i)->get_name() << endl;
   7         }

1.12. 第十步

存盘与读盘。要进行文件操作,可以使用ifstream和ofstream类,它们在头文件fstream中定义。修改main函数实现存盘功能:

   1         if(command == "save") {
   2             string filename;
   3             cin >> filename;
   4             ofstream f(filename.c_str());
   5             f << list.size() << endl;
   6             for(int i = 0; i < list.size(); i++) {
   7                 Contact *c = list.get(i);
   8                 f << c->get_name() << endl;
   9                 f << c->size() << endl;
  10                 for(int j = 0; j < c->size(); j++) {
  11                     f << typeid(*c->get(j)).name() << endl;
  12                     f << c->get(j)->to_string() << endl;
  13                 }
  14             }
  15         }

这时可以编译运行程序。用save命令把数据存到文件中。打开文件可以查看到文件的内容。

修改main函数的实现读盘功能:

   1         if(command == "load") {
   2             string filename;
   3             cin >> filename;
   4             ifstream f(filename.c_str());
   5             int size;
   6             f >> size;
   7             list.clear();
   8             for(int i = 0; i < size; i++) {
   9                 string name;
  10                 int item_size;
  11                 f >> name >> item_size;
  12                 list.add(name);
  13                 Contact *c=list.get(list.find(name));
  14                 for(int j = 0; j < item_size; j++) {
  15                     string type, item_name, value;
  16                     f >> type;
  17                     f >> item_name;
  18                     getline(f, value);
  19                     if(type == typeid(DateItem).name()) {
  20                         c->add(new DateItem(item_name, value));
  21                     } else if(type == typeid(AddressItem).name()) {
  22                         /*....*/   
  23                     }
  24                     /*....*/
  25                 }
  26             }
  27         }

ContactList添加clear()函数:

   1     /**清空列表里面的所有联系人*/
   2     void clear() {
   3 
   4     }

编译运行,这时可以存盘与读盘了。

1.13. 第十一步

消除内存漏洞。为需要的类添加析构函数。 ContactList添加析构函数

   1 //......
   2     ~ContactList() {
   3         /*......*/
   4     }
   5 //......
   6 

为Contact添加析构函数

   1 //......
   2     ~Contact() {
   3         /*......*/
   4     }
   5 //......
   6 

为Item添加虚析构函数

   1 //......
   2     virtual ~Item() {
   3         /*......*/
   4     }
   5 //......
   6 

1.14. 第十二步

让联系人总数和每个人的属性项数不受数组大小的限制。使用vector代替数组:

   1     Contact *contacts[100];
   2     int num;

改成

   1     vector<Contact *> contacts;

读取num的地方用contacts.size()代替,在数组尾部添加一个元素用contacts.push_back(...)代替,

2. 个人财务软件

2.1. 需求概述

设计一个基于字符界面的个人财务软件Finance,基本功能包括:

  1. 账户Account管理。包括:添加、删除、修改账户
    • 账户分为4类:资产Asset、债务Debt、收入Income、支出Expense。
    • 资产账户是指个人所拥有的财产,比如现金、存款、借出款、股票等。
    • 债务账户是指个人所欠的债务,比如借入款,贷款等。
    • 支出账户是指个人的支出,比如交通、餐饮、购物、房租等
    • 收入账户是指个人的收入,比如工资、奖金、利息等
    • 每个资产账户和债务账户都有初始值。
  2. 交易Transaction管理(添加、删除交易,设置交易属性)
    • 交易用于完成在各个帐户之间的财务的转移,每笔记录有时间、转出账户、转入账户、金额、说明等属性。
    • 收入账户只能作为转出账户。支出账户只能作为转入账户。
  3. 统计功能
    • 统计各个资产账户和债务帐户的当前状况
    • 统计收入和支出状况
    • 统计一个账户的所有历史交易
  4. 存盘和读取
    • 所有操作能够自动存盘,下次打开程序时能够从磁盘读取,恢复到关闭程序前一样的状态。

2.2. 使用举例

刚开始使用可以设置如下账户:

每个资产账户和债务账户设置好初始值。然后开始记录每一笔交易:

创建资产帐户"现金",初始金额300
 * create account asset 现金 300
 * create account asset 饭卡 100
 * create account asset 招行卡 1000
 * create account asset 工行卡 500
 * create account asset 借出 0
创建债务帐户"借入",初始金额0
 * create account debt 借入 0
创建支出帐户"交通支出"
 * create account expense 交通支出
 * create account expense 购物支出
 * create account expense 吃饭支出
创建收入帐户"工资"
 * create account income 工资
创建交易,从现金帐户转移2元到交通支出帐户
 * create transaction 现金 交通支出 2.0
 * create transaction 饭卡 吃饭支出 4.0
 * create transaction 借入 现金 300.0
 * create transaction 工资 工行卡 1080.0 
 * create transaction 现金 借出 200.0 
 * create transaction 现金 借入 300.0 
显示所有帐户的结余和总的收支状况
 * show balance
显示某帐户的交易清单
 * show account 现金
显示所有交易的清单
 * show transaction
删除第1条交易
 * remove transaction 1
删除吃饭支出帐户
 * remove account 吃饭支出

2.3. 参考设计

OOPDesign2.png

2.4. 第一步

首先建好主程序框架,接受各种输入指令,但是不做任何处理:

   1 #include <iostream>
   2 using namespace std;
   3 
   4 int main() {
   5     while(true) {
   6         string command, type, prompt = "finance>>";
   7         cout << prompt;
   8         cin >> command >> type;
   9         if(command == "create") {
  10             if(type == "account") {
  11                 
  12             } else if(type == "transaction") {
  13                 
  14             } else {
  15                 cout << "Parameter error: "<< type << endl; 
  16             }
  17         } else if (command == "remove" ) {
  18             if(type == "account") {
  19                 
  20             } else if(type == "transaction") {
  21                 
  22             } else {
  23                 cout << "Parameter error: "<< type << endl; 
  24             }            
  25         } else if (command == "show" ) {
  26             if(type == "balance") {
  27                 
  28             } else if(type == "account") {
  29                 
  30             } else if(type == "transaction") {
  31                 
  32             } else {
  33                 cout << "Parameter error: "<< type << endl; 
  34             } 
  35         } else if (command == "modify") {
  36             if(type == "account") {
  37                 
  38             } else if(type == "transaction") {
  39                 
  40             } else {
  41                 cout << "Parameter error: "<< type << endl; 
  42             }             
  43         } else if(command == "save"){
  44 
  45         } else if(command == "load"){
  46 
  47         } else if(command == "exit") {
  48             break;
  49         } else {
  50             cout << "Command error: " << command << endl;
  51         }
  52     }    
  53 }

编译运行这个程序

2.5. 第二步

编写必要的数据结构。撰写Account类:

   1 class Account {
   2     string name_;   // 帐户名称
   3     string type_;   // 帐户类型(4种类型之一)
   4     double equity_; // 帐户金额初始值
   5 };

撰写Transaction类:

   1 class Transaction {
   2     Account * from_; //从哪个帐户转出
   3     Account * to_;   //转到哪个帐户中去
   4     double amount_;  //转了多少金额
   5 };

撰写Finance类:

   1 class Finance{
   2     vector<Transaction *> transactions_; //所有交易的列表
   3     vector<Account *> accounts_;         //所有帐户的列表
   4 };

在main函数里面定义Finance对象:

   1 //...
   2 int main() {
   3     Finance finance;        //所有财务信息存在finance对象中
   4 //...
   5 

2.6. 第三步

编写添加帐户的功能。在Finance类中定义添加帐户的成员函数add_account,在main函数中处理添加帐户的命令并调用Finance的成员函数实现。

   1     void add_account(string type, string name, double value) {
   2         /*...*/
   3     }

2.7. 第四步

编写列出所有帐户的功能。在Finance泪中定义成员函数show_accounts,列出所有帐户

   1     void show_accounts {
   2         /*...*/
   3     }

2.8. 第五步

编写添加交易的功能。在Finance类中定义添加交易的成员函数add_transaction,在main函数中处理添加交易的命令并调用Finance的成员函数实现。

   1     void add_transaction(string from, string to, double value) {
   2         /*...*/
   3     }

2.9. 第六步

编写列出所有交易的功能。在Finance类中定义成员函数show_transactions,列出所有交易

   1     void show_transactions() {
   2         /*...*/
   3     }

2.10. 第七步

编写删除交易功能。在Finance类中实现remove_transaction,在main函数中处理删除交易的命令并调用Finance的成员函数函数实现。

   1     void remove_transaction(int i) {
   2         /*...*/
   3     }

2.11. 第八步

编写删除帐户功能。在Finance类中实现remove_account,在main函数中处理删除帐户的命令并调用Finance的成员函数函数实现。

   1     void remove_account(string name) {
   2         /*...*/
   3     }

2.12. 第九步

编写修改交易功能。

2.13. 第十步

编写修改帐户功能。

2.14. 第十一步

编写查询功能。在Finance类中定义show_account_detail,列出某帐户的详细信息(包括该帐户所有相关交易以及每次交易后的结余)。修改show_accounts,使其能够列出帐户的汇总信息(每个帐户的结余)。

   1     void show_account_detail(string name) {
   2         /*...*/
   3     }

2.15. 第十二步

编写存盘功能

2.16. 第十三步

编写读盘功能

C++课程设计 (2008-02-23 15:36:45由localhost编辑)

ch3n2k.com | Copyright (c) 2004-2020 czk.