目录
课程设计完成后用压缩软件将源代码打包,以附件形式发送邮件到 [email protected] ,标题注明课程设计四个字并写上学号、姓名,比如"课程设计-0509005208-苏晶晶"。
面向对象课程设计
1. 通讯录
1.1. 需求
数据定义:
- 一个通讯录包含若干联系人的通讯信息
- 每个联系人有姓名及其他自定义信息
- 姓名:假定没有重复
- 自定义信息:用户自己可以添加的信息字段,比如QQ,手机,生日等。自定义信息分为5类:电话、地址、日期、数字、文字。电话用于保存各种电话;日期用于保存年、月、日信息;地址包括国家、省份、城市、住址、邮编等信息;数字只能输入数字;文字可以输入任意文字。
功能说明:
- 用户可以添加、修改、删除联系人
- 用户可以显示所有联系人列表
- 用户可以输入姓名,显示这个联系人的详细信息
- 用户可以在各种自定义信息中查找联系人,并给出找到的所有联系人列表
- 可以存盘和读盘
输入输出格式:程序以命令行方式交互式运行,命令包括:
- list: 列出所有用户清单
show <name>: 列出某联系人的详细信息,name是联系人的名字,
add <name>: 添加指定的联系人,name是联系人的名字。新添加的联系人的名字与已有的联系人的名字不能重复。
remove <name>: 后面加联系人的名字,删除指定的联系人。
update <name> <action>: 修改联系人的信息,name是联系人的姓名,action可以是:
add <type> <attribute> <value>: 添加属性,attribute表示属性的名字,value表示属性的值,type表示属性的类型,可以是date、text、phone、numeric、address。
remove <attribute>: 删除属性,attribute表示属性的名字
update <attribute> <value>:更新属性的值
search <keyword>: 查找联系人,列出所有包含keyword的联系人
save <filename>: 将所有信息写入filename文件
load <filename>: 从filename中载入联系人信息
使用举例:
添加联系人,姓名叫做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类:
然后定义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函数,使它可以完成添加、列出清单的功能
运行这个程序,可以完成add和list的功能
1.4. 第三步
为程序添加删除功能。为ContactList添加如下成员:
然后修改main函数:
运行这个程序,可以完成remove功能了。
1.5. 第四步
接下来,让程序可以为联系人添加属性。首先添加Item类:
然后为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.7. 第六步
删除联系人的属性。为Contact类添加成员:
修改main函数
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.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:
修改main函数,把show中get_name改成to_string:
编译执行程序,现在可以使用show命令了。
1.10. 第八点五步
增加update属性的功能。在Item类中增加纯虚函数update
在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成员函数
在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.11. 第九步
添加搜索功能。首先为Contact类添加contain函数,判断一个联系人是否包含某个搜索的关键字,包含返回true,否则返回false。
然后修改main函数,处理search命令:
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.13. 第十一步
消除内存漏洞。为需要的类添加析构函数。 为ContactList添加析构函数
为Contact添加析构函数
为Item添加虚析构函数
1.14. 第十二步
让联系人总数和每个人的属性项数不受数组大小的限制。使用vector代替数组:
改成
1 vector<Contact *> contacts;
读取num的地方用contacts.size()代替,在数组尾部添加一个元素用contacts.push_back(...)代替,
2. 个人财务软件
2.1. 需求概述
设计一个基于字符界面的个人财务软件Finance,基本功能包括:
- 账户Account管理。包括:添加、删除、修改账户
- 账户分为4类:资产Asset、债务Debt、收入Income、支出Expense。
- 资产账户是指个人所拥有的财产,比如现金、存款、借出款、股票等。
- 债务账户是指个人所欠的债务,比如借入款,贷款等。
- 支出账户是指个人的支出,比如交通、餐饮、购物、房租等
- 收入账户是指个人的收入,比如工资、奖金、利息等
- 每个资产账户和债务账户都有初始值。
- 交易Transaction管理(添加、删除交易,设置交易属性)
- 交易用于完成在各个帐户之间的财务的转移,每笔记录有时间、转出账户、转入账户、金额、说明等属性。
- 收入账户只能作为转出账户。支出账户只能作为转入账户。
- 统计功能
- 统计各个资产账户和债务帐户的当前状况
- 统计收入和支出状况
- 统计一个账户的所有历史交易
- 存盘和读取
- 所有操作能够自动存盘,下次打开程序时能够从磁盘读取,恢复到关闭程序前一样的状态。
2.2. 使用举例
刚开始使用可以设置如下账户:
- 资产账户:现金、饭卡、招商银行卡、工商银行卡、借出
- 债务账户:借入
- 支出账户:交通支出、购物支出、吃饭支出
- 收入账户:工资
每个资产账户和债务账户设置好初始值。然后开始记录每一笔交易:
- 从家里到学校坐公交花去2元:新建交易,从现金转移到交通支出2元
- 吃早饭,饭卡上花去4元:新建交易,从饭卡转移到吃饭支出4元
- 从别人那里借了300元:新建交易,从借入账户转移300元到现金帐户
- 发工资,工商银行卡多出1080元:新建交易,从工资帐户转移到工商银行卡1080元
- 借给朋友现金200元:新建交易,从现金帐户转移到借出账户200元
- 把借别人的300元还掉:新建交易,从现金转移300元到借入账户
创建资产帐户"现金",初始金额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. 参考设计
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类:
撰写Transaction类:
撰写Finance类:
在main函数里面定义Finance对象:
2.6. 第三步
编写添加帐户的功能。在Finance类中定义添加帐户的成员函数add_account,在main函数中处理添加帐户的命令并调用Finance的成员函数实现。
2.7. 第四步
编写列出所有帐户的功能。在Finance泪中定义成员函数show_accounts,列出所有帐户
2.8. 第五步
编写添加交易的功能。在Finance类中定义添加交易的成员函数add_transaction,在main函数中处理添加交易的命令并调用Finance的成员函数实现。
2.9. 第六步
编写列出所有交易的功能。在Finance类中定义成员函数show_transactions,列出所有交易
2.10. 第七步
编写删除交易功能。在Finance类中实现remove_transaction,在main函数中处理删除交易的命令并调用Finance的成员函数函数实现。
2.11. 第八步
编写删除帐户功能。在Finance类中实现remove_account,在main函数中处理删除帐户的命令并调用Finance的成员函数函数实现。
2.12. 第九步
编写修改交易功能。
2.13. 第十步
编写修改帐户功能。
2.14. 第十一步
编写查询功能。在Finance类中定义show_account_detail,列出某帐户的详细信息(包括该帐户所有相关交易以及每次交易后的结余)。修改show_accounts,使其能够列出帐户的汇总信息(每个帐户的结余)。
2.15. 第十二步
编写存盘功能
2.16. 第十三步
编写读盘功能