★ 了解项目的需求分析
★ 掌握C++语言模块化设计开发
★ 掌握项目的调试
★ 总结项目心得
本书的第1~10章已对C++语言基本知识进行了详细讲解。学习编程语言的目的是将其应用到项目开发中解决实际问题,在不断的应用中增强开发技能,锻炼编程思维,加深对程序设计语言的认识和理解。本章将带领大家使用C++语言实现一个简单的酒店管理系统项目开发。
开发项目前要进行充分的产品调研,了解项目应用领域的业务流程、操作规范,在明确项目需求的基础上进行详细设计,确定项目功能,才能开发出满足实际需求的软件产品。本节将针对酒店管理系统功能描述和项目设计进行介绍。
本章将实现一个简单的酒店管理系统,用以实现酒店客房的显示、添加、删除、状态设置等操作。系统分为3个功能模块,具体如图11-1所示。
下面结合图11-1介绍酒店管理系统各个模块的主要功能。
(1)客房模块:主要包括3个功能,分别是获取客房数据,如客房编号、名称、价格、面积、床位数量、状态(空闲或入住)等,将客房数据保存到外部文件,以及在显示客房信息时从文件中读取客房数据。
(2)客房管理模块:主要用于管理客房。例如,添加客房、删除客房、查找客房、设置客房状态等。
(3)界面模块:主要用于显示酒店管理系统的操作界面。例如,启动系统、显示菜单、添加客房、删除客房、设置客房状态及显示所有客房信息等操作界面。

图11-1 酒店管理系统模块划分
确定系统功能并划分功能模块之后,需要根据功能模块进行类的设计。下面分模块讲解类的设计。
1. 客房模块
客房管理模块主要负责保存、读取客房数据,如客房编号、名称、价格等,该模块需要设计一个客房类GuestRoom,在该类中定义客房的数据信息作为成员变量,并提供访问这些客房数据的成员函数。此外,GuestRoom类负责将客房数据保存到本地文件中,并在显示客房信息时负责从文件读取客房信息,因此还需要定义文件读写函数。
GuestRoom类的详细设计如图11-2所示。
2. 客房管理模块
客房管理模块主要负责管理客房,如添加客房、删除客房、查找客房,以及设置客房状态等,该模块需要设计一个客房管理类GuestR oomM anager,在该类中定义各种功能函数。由于管理客房时每次需要从文件中读取客房数据,在设计时可以将读取的客房数据保存到一个m ap容器中,以客房编号为K ey键,以GuestRoom类对象作为Value值。因此,GuestRoomM anager类中还需要定义一个m ap容器作为成员变量存储客房信息。

图11-2 GuestRoom类的详细设计
GuestRoomM anager类的详细设计如图11-3所示。
3. 界面模块
界面模块主要负责显示酒店管理系统的操作界面,包括显示菜单、添加客房、删除客房、设置客房状态、显示所有客房信息等操作界面,该模块需要设计一个界面类RoomView,定义功能函数为各种操作提供界面显示。
RoomV iew类的详细设计如图11-4所示。

图11-3 G uestRoom M anager类的详细设计

图11-4 Room View类的详细设计
上述3个类就是酒店管理系统需要的所有类,这3个类之间的关系如图11-5所示。

图11-5 酒店管理系统类之间的关系
11.1节对酒店管理系统的功能进行了描述与模块划分,并且针对每个模块都进行了类的设计。本节就带领读者实现各个模块。
在实际开发中,一个模块通常分两个文件实现,类的定义与数据的设计在.h文件中,而类的实现在对应的.cpp文件中。下面分两个文件实现客房模块:guest_room.h和guest_room.cpp,在guest_room.h文件中定义GuestRoom类,在guest_room.cpp文件中实现GuestRoom类的设计,具体如例11-1和例11-2所示。
例11-1 guest_room.h
#pragma once
#include <string>
#include<map>
using namespace std;
//定义enum ROOM_STATE枚举类型,表示客房状态:空闲、入住
enum ROOM_STATE { FREE = 0, CHECK_IN };
//定义客房类
class GuestRoom
{
public:
GuestRoom()=default; //提供默认构造函数
GuestRoom(string, int, int, int,ROOM_STATE); //有参构造函数
string generate_number(); //生成客房编号
string show_state(); //显示客房状态
bool save_data(map<string, GuestRoom>&); //保存数据
map<string, GuestRoom> read_data(); //读取数据
public:
string get_num(); //获取客房编号
string get_name(); //获取客房名称
int get_price(); //获取客房价格
int get_area(); //获取客房面积
int get_bed_num(); //获取客房床位数量
void set_state(); //设置客房状态
private:
string m_number; //客房编号
int m_price; //客房价格
int m_area; //客房面积
int m_bed_number; //客房床位数量
string m_name; //客房名称
enum ROOM_STATE m_state; //客房状态
};
例11-2 guest_room.cpp
1 #define _CRT_SECURE_NO_WARNINGS
2 #include"guest_room.h"
3 #include<time.h>
4 #include<iostream>
5 #include<fstream>
6 using namespace std;
7 //定义字符指针,指向一个文件
8 const char* const room_data_file = "room.dat";
9 //GuestRoom构造函数实现
10 GuestRoom::GuestRoom(string name, int price , int bed_num,
11 int area,ROOM_STATE state=FREE)
12 {
13 this->m_name = name;
14 this->m_price = price;
15 this->m_bed_number = bed_num;
16 this->m_area = area;
17 this->m_number = generate_number();
18 this->m_state = state;
19 }
20 //生成客房编号
21 string GuestRoom::generate_number()
22 {
23 //本地时间转字符串
24 time_t my_time = time(NULL);
25 struct tm* my_tm = localtime(&my_time);
26 char tim_buff[128] = { 0 };
27 sprintf(tim_buff, "%d%d", my_tm->tm_yday, my_tm->tm_sec);
28 //生成随机数
29 int rand_num = rand() % 50;
30 char buf[128] = { 0 };
31 sprintf(buf, "%d", rand_num);
32 //拼接字符串作为房间编号
33 return string(tim_buff) + string(buf);
34 }
35 //显示客房状态
36 string GuestRoom::show_state()
37 {
38 if (m_state == FREE)
39 return "空闲";
40 if (m_state == CHECK_IN)
41 return "入住";
42 }
43 //保存数据(map容器,key:客房编号,value:客房对象)
44 bool GuestRoom::save_data(map<string, GuestRoom>& room_list)
45 {
46 //1. 打开文件
47 ofstream ofs(room_data_file,ios::out);
48 if(!ofs)
49 {
50 return false;
51 }
52 //2. 写入对象数据
53 for(auto& room : room_list)
54 {
55 //写入m_name成员变量所占内存大小及其值
56 size_t name_len = room.second.m_name.size();
57 ofs.write(reinterpret_cast<const char*>(&name_
58 sizeof(size_t));
59 ofs.write(room.second.m_name.c_str(), name_len);
60 //写入m_price
61 ofs.write(reinterpret_cast<const char*>(&room.second.m_price),
62 sizeof(int));
63 //写入m_area
64 ofs.write(reinterpret_cast<const char*>(&room.second.m_area),
65 sizeof(int));
66 //写入m_bed_number
67 ofs.write(reinterpret_cast<const char*>
68 (&room.second.m_bed_number),sizeof(int));
69 //写入m_state
70 ofs.write(reinterpret_cast<const char*>(&room.second.m_state),
71 sizeof(enum ROOM_STATE));
72 //写入m_number成员变量所占内存大小及其值
73 size_t number_len = room.second.m_number.size();
74 ofs.write(reinterpret_cast<const
75 char*>(&number_len), sizeof(size_t));
76 ofs.write(room.second.m_number.c_str(), number_len);
77 }
78 //3. 关闭文件
79 ofs.close();
80 return true;
81 }
82 //读取数据
83 map<string, GuestRoom> GuestRoom::read_data()
84 {
85 //1. 打开文件
86 map<string, GuestRoom> room_list;
87 ifstream ifs(room_data_file, ios::in);
88 if(!ifs)
89 {
90 return room_list;
91 }
92 //2. 写入对象数据
93 while (ifs.peek() != EOF)
94 {
95 GuestRoom room;
96 //读入m_name成员变量所占内存大小及其值
97 size_t name_len = 0;
98 ifs.read(reinterpret_cast<char*>(&name_len), sizeof(size_t));
99 char name_buffer[128] = { 0 };
100 ifs.read(name_buffer, name_len);
101 room.m_name = name_buffer;
102 //写入m_price
103 ifs.read(reinterpret_cast<char*>(&room.m_price),sizeof(int));
104 //写入m_area
105 ifs.read(reinterpret_cast<char*>(&room.m_area),sizeof(int));
106 //写入 bed_number
107 ifs.read(reinterpret_cast<char*>(&room.m_bed_number),
108 sizeof(int));
109 //写入 state
110 ifs.read(reinterpret_cast<char*>(&room.m_state),
111 sizeof(enum ROOM_STATE));
112 //写入m_number成员变量所占内存大小及其值
113 size_t number_len = 0;
114 ifs.read(reinterpret_cast<char*>(&number_len),sizeof(size_t));
115 char number_buffer[128] = { 0 };
116 ifs.read(number_buffer, number_len);
117 room.m_number = number_buffer;
118 //将数据存储到map容器中
119 room_list.insert(make_pair(room.m_number, room));
120 }
121 //3. 关闭文件
122 ifs.close();
123 return room_list;
124 }
125 //获取客房编号
126 string GuestRoom::get_num()
127 {
128 return m_number;
129 }
130 //设置客房状态
131 void GuestRoom::set_state()
132 {
133 m_state = CHECK_IN;
134 }
135 //获取客房名称
136 string GuestRoom::get_name()
137 {
138 return m_name;
139 }
140 //获取客房价格
141 int GuestRoom::get_price()
142 {
143 return m_price;
144 }
145 //获取客房面积
146 int GuestRoom::get_area()
147 {
148 return m_area;
149 }
150 //获取客房床位数量
151 int GuestRoom::get_bed_num()
152 {
153 return m_bed_number;
154 }
guest_room.cpp文件一共实现了11个函数,下面分别对这些函数进行介绍。
(1)GuestRoom()函数
第10~19行代码实现了GuestRoom()函数,GuestRoom()函数是类的构造函数,用于对类的成员变量进行初始化,实现比较简单。
(2)generate_number()函数
第21~34行代码实现了generate_number()函数,generate_number()函数用于生成客房编号。第24~27行代码调用localtim e()函数获取本地时间,并通过sprintf()函数将获取的本地时间转换成字符串存储到tim_buff字符数组中。第29~31行代码调用random()函数生成一个小于50的随机数,并通过sprintf()函数将随机数转换成字符串存储到buf字符数组中。第33行代码将字符数组tim_buff与字符数组buf中的数据拼接后作为房间编号并返回。
(3)show_state()函数
第36~42行代码实现了show_state()函数,show_state()函数用于显示客房状态。在函数内部,通过if语句判断成员变量m_state的值返回客房状态。客房状态一共有两种:空闲和入住。
(4)save_data()函数
第44~81行代码实现了save_data()函数,save_data()函数用于将客房数据保存到外部文件中,它有一个m ap<string,GuestRoom>类型的容器作为参数。在函数内部,第47~51行代码以追加方式打开文件,并判断文件打开是否成功。第53~77行代码通过for循环依次将客房的名称、价格、面积、床位数量、状态和编号写入文件。第79行代码关闭文件。
(5)read_data()函数
第83~124行代码实现read_data()函数,read_data()函数从文件中读取客房数据,并存储到m ap容器中,将m ap容器返回。第86行代码定义m ap容器room_list。第87~91行代码打开文件,并判断文件是否打开成功。第93~120行代码通过while循环从文件中依次读取客房的编号、状态、名称、价格、面积及床位数量,并存储到容器room_list中。第122行代码关闭文件。
(6)get_num()函数
第126~129行代码实现get_num()函数,用于获取客房编号。
(7)set_state()函数
第131~134行代码实现set_state()函数,用于设置客房状态。
(8)get_nam e()函数
第136~139行代码实现get_nam e()函数,用于获取客房名称。
(9)get_price()函数
第141~144行代码实现get_price()函数,用于获取客房价格。
(10)get_area()函数
第146~149行代码实现get_area()函数,用于获取客房面积。
(11)get_bed_num()函数
第151~154行代码实现get_bed_num()函数,用于获取客房床位数量。
客房管理模块分两个文件实现:room_m anager.h和room_m anager.cpp,room_m anager.h文件用于定义GuestRoomM anager类,room_m anager.cpp文件用于实现GuestRoomM anager类的成员函数,具体如例11-3和例11-4所示。
例11-3 room_manager.h
#pragma once
#include "guest_room.h"
#include "room_manager.h"
#include<map>
#include<string>
class GuestRoomManager //定义客房管理类
{
public:
GuestRoomManager(); //构造函数
bool check_in(const string); //查找客房
void add_room(const GuestRoom room); //添加客房
bool remove_data(const string); //删除客房数据
void set_room_state(string number); //设置客房状态
private:
map<string, GuestRoom> m_room_list; //map容器
};
例11-4 room_manager.cpp
1 #include "room_manager.h"
2 #include "guest_room.h"
3 #include<iostream>
4 using namespace std;
5 //构造函数实现
6 GuestRoomManager::GuestRoomManager()
7 {
8 GuestRoom().read_data();//读取客房数据
9 }
10 //查找客房
11 bool GuestRoomManager::check_in(const string number)
12 {
13 //读取文件中的数据到容器中
14 GuestRoom grm;
15 m_room_list = grm.read_data();
16 //在容器中查找客房
17 if(m_room_list.find(number) == m_room_list.end())
18 {
19 return false;
20 }
21 return true;
22 }
23 //添加客房
24 void GuestRoomManager::add_room( GuestRoom room)
25 {
26 //判断客房编号是否存在
27 if (check_in(room.get_num()))
28 {
29 cout << "房间编号已存在" << endl;
30 return;
31 }
32 //将客房添加到容器中
33 m_room_list.insert(make_pair(room.get_num(), room));
34 //将容器中的数据存放到文件中
35 GuestRoom().save_data(m_room_list);
36 }
37 //删除客房数据
38 bool GuestRoomManager::remove_data(const string number)
39 {
40 //判断客房编号是否存在
41 if (!check_in(number))
42 {
43 cout << "房间编号不存在" << endl;
44 return false;
45 }
46 //删除
47 m_room_list = GuestRoom().read_data();
48 m_room_list.erase(number);
49 //更新文件
50 GuestRoom().save_data(m_room_list);
51 return true;
52 }
53 //设置客房状态
54 void GuestRoomManager::set_room_state(string number)
55 {
56 if (!check_in(number))
57 {
58 cout << "房间编号不存在" << endl;
59 return;
60 }
61 else
62 {
63 //将文件中的客房数据读取到文件中
64 m_room_list = GuestRoom().read_data();
65 //设置为入住状态
66 m_room_list[number].set_state();
67 //将数据保存到文件中
68 GuestRoom().save_data(m_room_list);
69 }
70 }
room_m anager.cpp文件一共实现了5个函数,下面分别进行介绍。
(1)GuestRoomM anager()函数
第6~9行代码实现GuestRoomM anager()函数,GuestRoomM anager()函数是类的构造函数。在构造函数内部,通过GuestRoom类的匿名对象调用read_data()函数读取文件,即每次通过GuestRoomM anager类对象管理客房时,都会先读取文件获取客房数据。
(2)check_in()函数
第11~22行代码实现check_in()函数,check_in()函数用于查找客房,参数为客房编号。第14~15行代码创建GuestRoom类对象grm,并通过对象grm调用read_data()函数,将客房数据读取到容器m_room_list中。第17~20行代码,通过容器m_room_list调用find()函数查找客房编号是否存在,如果不存在,则返回false,否则返回true。
(3)add_room()函数
第24~36行代码实现add_room()函数。add_room()函数用于添加客房,参数为GuestRoom类对象,即要添加的客房。第27~31行代码判断客房编号是否已经存在,如果客房编号已经存在,则退出函数调用。第33行代码通过容器m_room_list调用insert()函数将客房添加到容器中。第35行代码通过GuestRoom类匿名对象调用save_data()函数将容器m_room_list的数据保存到文件中。
(4)rem ove_data()函数
第38~52行代码实现rem ove_data()函数,rem ove_data()函数用于删除客房数据,参数为要删除客房的编号。第41~45行代码通过check_in()函数判断客房编号是否存在,如果不存在,就返回false。第47~48行代码通过GuestRoom类匿名对象调用read_data()函数,将文件中的数据读取到容器m_room_list当中,并调用容器m_room_list的成员函数erase()函数删除编号为number的客房。第50行代码通过GuestRoom类匿名对象调用save_data()函数将容器m_room_list中更改后的客房数据保存到文件中。
(5)set_room_state()函数
第54~70行代码实现set_room_state()函数,set_room_state()函数用于设置客房状态,参数为客房编号。第56~69行代码判断客房编号是否存在,如果不存在,则显示提示信息后返回;如果存在,则通过GuestRoom类匿名对象调用read_data()函数,将文件中的客房数据读取到容器m_room_list中。第66行代码在容器m_room_list中设置客房状态,第68行代码通过GuestRoom类匿名对象调用save_data()函数,将更改后的客房数据重新保存到文件中。
界面模块分两个文件实现:room_view.h和room_view.cpp。room_view.h文件用于定义GuestView类,room_view.cpp文件用于实现GuestV iew类的成员函数,具体如例11-5和例11-6所示。
例11-5 room_view.h
#pragma once
#include "room_view.h"
#include "room_manager.h"
class RoomView //定义界面类
{
public:
void show_menu(); //显示菜单
void add_room(); //添加客房
void remove_data(); //删除客房
void set_room(); //设置客房状态
void show_all(); //显示所有客房信息
void run(); //启动酒店管理系统
private:
GuestRoomManager m_grm; //客房管理对象
};
例11-6 room_view.cpp
1 #include<iostream>
2 #include "room_view.h"
3 #include<iomanip>
4 using namespace std;
5 //显示菜单
6 void RoomView::show_menu()
7 {
8 cout << "1. 显示客房信息" << endl;
9 cout << "2. 设置客房状态" << endl;
10 cout << "3. 删除客房信息" << endl;
11 cout << "4. 添加客房信息" << endl;
12 cout << "5. 退出管理系统" << endl;
13 }
14 // 添加客房
15 void RoomView::add_room()
16 {
17 string name;
18 int price;
19 int bed_num;
20 int area;
21 cout << "请输入客房名称:"; cin >> name;
22 cout << "请输入客房价格:"; cin >> price;
23 cout << "请输入客房床数量:"; cin >> bed_num;
24 cout << "请输入客房面积:"; cin >> area;
25 //创建客房对象
26 GuestRoom room(name, price, bed_num, area,FREE);
27 //调用客房管理模块的add_room()函数添加客房
28 m_grm.add_room(room);
29 }
30 //删除客房
31 void RoomView::remove_data()
32 {
33 cout << "请输入您要删除的客房编号:";
34 string room_number;
35 cin >> room_number;
36 // 调用客房管理模块的remove_data()函数
37 m_grm.remove_data(room_number);
38 }
39 //设置客房状态
40 void RoomView::set_room()
41 {
42 string number;
43 cout << "请输入要设置的客房编号:"; cin >> number;
44 m_grm.set_room_state(number);
45 }
46 //显示所有客房信息
47 void RoomView::show_all()
48 {
49
50 cout << "------------------------------------------------" << endl;
51 cout << "编号" << "\t " << "名称" << "\t\t" << "面积" << "\t"
52 << "价格" << "\t" << "床位数量" << "\t" << "状态" << endl;
53 cout << "-------------------------------------------------" << endl;
54 GuestRoom grm;
55 map<string, GuestRoom> rooms;
56 rooms=grm.read_data();
57 if(rooms.empty())
58 {
59 cout<<"请添加客房信息后再进行操作!"<<endl;
60 }
61 for (auto& room : rooms)
62 {
63 cout << room.second.get_num() << "\t" << setw(10)
64 << room.second.get_name() << "\t"<< room.second.get_area()
65 << "平方\t" << room.second.get_price() << "元\t "
66 << room.second.get_bed_num() << "个\t\t"
67 << room.second.show_state() << endl;
68 }
69 cout << "-------------------------------------------------" << endl;
70 }
71 //启动酒店管理系统
72 void RoomView::run()
73 {
74 //定义state变量标识系统是否启动
75 bool state = true;
76 while (state)
77 {
78 //显示菜单
79 show_menu();
80 //获得输入的命令
81 int flag = -1;
82 cout << "请输入您的操作:";
83 cin >> flag;
84 system("cls");
85 //根据输入执行相应的操作
86 switch (flag)
87 {
88 case 1:
89 show_all();
90 break;
91 case 2:
92 set_room();
93 break;
94 case 3:
95 remove_data();
96 break;
97 case 4:
98 add_room();
99 break;
100 case 5:
101 exit(0);
102 default:
103 break;
104 }
105 }
106 }
room_view.cpp文件一共实现了6个函数,下面分别进行介绍。
(1)show_m enu()函数
第6~13行代码实现show_m enu()函数,show_m enu()函数用于显示酒店管理系统菜单,实现比较简单,没有业务逻辑,这里不再赘述。
(2)add_room()函数
第15~29行代码实现add_room()函数,add_room()函数用于添加客房。第17~24行代码定义变量并输出添加客房数据的提示。第26行代码创建GuestRoom类对象room。第28行代码通过对象m_grm调用函数add_room()添加客房对象room。
(3)rem ove_data()函数
第31~38行代码实现rem ove_data()函数,rem ove_data()函数用于删除客房。在函数内部,首先输入要删除的客房编号,然后通过对象m_grm调用成员函数rem ove_data()删除客房数据。
(4)set_room()函数
第40~45行代码实现set_room()函数,set_room()函数用于设置客房状态。在函数内部,首先输入客房编号,然后通过对象m_grm调用成员函数set_room_state()设置客房状态。
(5)show_all()函数
第47~70行代码实现show_all()函数,show_all()函数用于显示所有客房信息。第50~53行代码显示客房信息的输出格式。第54~56行代码创建GuestRoom类对象grm,m ap<string,Guest>对象room s,并通过对象grm调用read_data()函数,将文件中的客房数据读取到容器room s中。第57~60行代码判断room s是否为空,如果为空,则提示先添加客房再进行其他操作。第61~68行代码通过for循环遍历容器room s,依次输出所有客房信息。
(6)run()函数
第72~106行代码实现run()函数,run()函数用于启动酒店管理系统。在函数内部,通过while循环不断执行操作。第79行代码调用show_m enu()函数显示菜单。第81~83行代码定义int类型变量flag,并从键盘输入flag的值。第86~104行代码通过输入判断要执行的操作,如果输入1,就调用show_all()函数显示所有客房信息;如果输入2,就调用set_room()函数设置客房状态;如果输入3,就调用rem ove_data()函数删除客房;如果输入4,就调用add_room()函数添加客房;如果输入5,就调用exit()函数退出系统。
前面已经完成了酒店管理系统中所有功能模块的编写,但是功能模块是无法独立运行的,需要一个程序将这些功能模块按照项目的逻辑思路整合起来,这样才能完成一个完整的项目。此时就需要创建一个m ain.cpp文件来整合这些功能模块,m ain.cpp文件中包含m ain()函数,是程序的入口。m ain.cpp文件的实现如例11-7所示。
例11-7 main.cpp
1 #include "guest_room.h"
2 #include<iostream>
3 #include<time.h>
4 #include<string>
5 #include "room_manager.h"
6 #include "room_view.h"
7 using namespace std;
8 int main()
9 {
10 RoomView grv; //创建RoomView类对象
11 grv.run(); //启动酒店管理系统
12 return 0;
13 }
m ain.cpp文件实现比较简单,在m ain()函数中,首先创建了RoomView类对象grv,然后通过对象grv调用run()函数启动酒店管理系统。
至此,酒店管理系统代码已经全部完成。
11.2节实现了整个项目,为了让读者更加直观地看到酒店管理系统最终的效果,并对程序的执行过程有个整体的认识,下面分别展示酒店管理系统的界面效果。
1. 显示系统主菜单
系统运行后,显示系统主菜单,如图11-6所示。

图11-6 系统主菜单
2. 显示客房信息
输入数字1,显示客房信息,第一次运行程序时,由于还没有客房,因此显示客房信息时,没有任何信息,系统会提示先添加客房,如图11-7所示。
3. 添加客房信息
输入数字4,添加客房信息,如图11-8所示。
4. 显示客房信息
添加客房之后,再次输入数字1,显示客房信息,如图11-9所示。

图11-7 第一次运行程序显示客房信息

图11-8 添加客房信息

图11-9 显示客房信息
5. 设置客房状态
输入数字2,可以设置客房状态。在设置客房状态时,需要输入客房编号,如图11-10所示。

图11-10 设置客房状态
输入客房编号之后,系统会更改该编号的客房状态。回到主菜单再次输入数字1,显示客房信息,可以看到92317客房状态改变,如图11-11所示。
6. 删除客房信息
输入数字3,可以删除客房信息。在删除客房信息时,输入客房编号,如图11-12所示。
输入客房编号后,按Enter键回到主菜单,系统会删除对应编号的客房信息。此时,再次输入数字1查看客房信息,编号为922134的客房不存在,如图11-13所示。

图11-11 查看客房状态

图11-12 删除客房信息

图11-13 查看客房信息
7. 退出管理系统
输入数字5,可以退出酒店管理系统,如图11-14所示。

图11-14 退出酒店管理系统
在程序开发过程中难免会出现各种各样的错误。为了快速发现和解决程序中的错误,可以使用Visual Studio 2019自带的调试功能,通过程序调试快速定位错误。本节就以酒店管理系统为例对调试功能进行详细的讲解。
在程序的调试过程中,为了分析出程序出错的原因,往往需要观察程序中某些数据的变化情况,这时就需要为程序设置断点,从而让正在运行的程序在断点处暂停,方便观察程序中的数据。
在V isual Studio 2019中,为程序设置断点的方式有两种,下面分别介绍。
1. 单击鼠标右键
在程序中,将鼠标放置在要插入断点的代码行,单击鼠标右键,依次选择【断点】→【插入断点】,如图11-15所示。
在图11-15中单击【插入断点】选项后,选中的代码行左边会有一个红色的圆点,如图11-16所示。

图11-15 单击鼠标右键插入断点

图11-16 插入断点
从图11-16得知,第11行代码处添加了一个断点。为程序设置断点以后,就可以对程序进行调试了。调试完毕要删除断点。删除断点的操作也非常简单,将鼠标放置在断点代码行,单击鼠标右键,选择【删除断点】,如图11-17所示。

图11-17 删除断点
2. 单击鼠标左键
除了上述方式,读者还可以在代码左边的灰色区域通过单击鼠标左键插入断点,断点插入成功后,代码行左侧也会有红色圆点出现。同样,删除断点时,只需再次在代码行左侧已插入的红色圆点处单击鼠标左键,即可删除断点。相比于上一种断点插入方式,这种方式更简单便捷。
当程序出现Bug时,为了找出错误原因,通常会一步一步跟踪程序的执行流程,这种调试方式称为单步调试。单步调试分为逐语句调试和逐过程调试。逐语句调试会进入函数内部调试,单步执行函数体的每条语句;逐过程调试不会进入函数体内部,而是把函数当作一步来执行。下面分别对这两种调试方法进行介绍。
1. 逐语句调试
以图11-16中的断点为例对项目进行逐语句调试,设置断点之后,单击工具栏中的运行按钮
,程序运行之后,遇到断点就会停止执行,如图11-18所示。

图11-18 程序遇到断点停止执行
在图11-18中,程序遇到断点会暂停运行,等待用户进行操作。程序暂停运行之后,Visual Studio 2019工具栏按钮会发生变化,如图11-19所示。

图11-19 Visual Studio 2019为调试按钮
图11-19中的调试按钮相关介绍具体如下。
如果在调试时想逐语句调试,则使用快捷键【F11】或工具栏中的逐语句调试按钮,程序会进入run()函数内部一条一条地执行语句。逐语句调试过程如图11-20所示。

图11-20 逐语句调试
在图11-20中,使用快捷键【F11】或工具栏中的逐语句调试按钮,程序就会逐条语句往下执行,当执行第78行代码,就会接着进入show_m enu()函数执行。
2. 逐过程调试
逐过程调试在每次调试时执行一个函数,当调试开始时,使用快捷键【F10】或单击工具栏中的逐过程调试按钮,可以一次执行一个函数,程序会逐个函数地往下执行,直到程序执行完毕。
调试程序一般是为了查找错误,当查找到错误之后就会结束调试,并不会全程调试。结束调试后,可单击工具栏中的运行按钮继续往下执行程序,也可以单击工具栏中的停止调试按钮结束程序调试。
在程序调试过程中,主要的就是观察当前变量的值以尽快找到程序出错的原因,Visual Studio 2019工具支持多种方式查看变量,下面介绍几个常用的查看变量的方法。
1. 使用鼠标悬停法查看变量的值
Visual Studio 2019可以通过鼠标悬停的方式查看变量的值,即鼠标指向变量,变量就会显示出其值。例如,run()函数中定义了变量flag,下面以查看变量flag的值为例演示Visual Studio 2019查看变量的方法。在图11-20中,程序还未执行第82行代码,此时变量flag的值为-1,将鼠标悬停在变量flag上面,Visual Studio 2019会显示出flag的值,如图11-21所示。
在图11-21中,通过鼠标悬停方式查看到变量flag的值为-1。继续逐语句往下执行,当执行完第82行代码时,通过键盘输入flag的值,此时再查看flag的值,flag就变成了输入的数值,如图11-22所示。
在图11-22中,程序执行到了第83行,cin>>flag语句已经执行完毕,此时将鼠标悬停在变量flag上,V isual Studio 2019会显示flag的值为2。
2. 使用局部变量窗口查看变量的值
除了鼠标悬停法,还可以通过V isual Studio 2019的“局部变量”窗口查看变量的值。在菜单栏中选择【调试】→【窗口】→【局部变量】,打开“局部变量”窗口查看变量的值,在“局部变量”窗口中可以看到当前运行代码之前所有变量的名称、当前值和类型,如图11-23所示。

图11-21 鼠标悬停查看变量flag的值(1)

图11-22 鼠标悬停查看变量flag的值(2)
3. 使用“快速监视”窗口查看变量的值
程序调试过程中,在代码区单击鼠标右键选择【快速监视】,弹出“快速监视”窗口,在该窗口的“表达式”文本框中输入要监视的变量,单击【重新计算】按钮,就可以查看变量的名称、值与数据类型,如图11-24所示。

图11-23 “局部变量”窗口
4. 使用“即时”窗口查看变量的值
在代码调试的过程中,在菜单栏中选择【调试】→【窗口】→【即时】打开“即时”窗口,在“即时”窗口中直接输入程序中的变量名,按Enter键即可查看变量的值,也可以在变量名前加上“&”(取地址符)查看变量的地址,如图11-25所示。

图11-24 “快速监视”窗口

图11-25 “即时窗口”
在前面几节中讲解了程序调试的相关知识,为了让读者真实体验实际开发中进行程序调试的全过程,接下来以酒店管理系统为例演示程序调试过程。
在酒店管理系统的主菜单中,要求输入1~5之间的整数,如果输入不符合要求的数据,如输入一个字符,则系统会一直闪屏无响应。对此进行分析,主菜单的输入操作由界面模块的run()函数实现,在例11-6所示的room_view.cpp文件中,第81~105行代码定义int类型变量flag,利用对象cin从键盘输入flag的值,然后判断flag的值,根据flag的值调用不同的函数以实现不同的操作。
当输入字符时,系统闪屏无响应,表明第81~105行代码在输入flag值时或switch…case语句对flag的处理不够严谨,需要对此段代码进行调试。
在例11-6第81行代码设置断点,如图11-26所示。
在图11-26中,设置断点之后开始调试,通过逐语句调试跟踪每一步操作。当程序运行至第83行代码时,发现调用cin输入flag的值时,没有对cin作错误处理。利用cin为int类型变量输入数据时,如果输入的是char类型的数据,则cin的一个错误标记会被设置,cin就不能再使用,再次调用cin>>flag会直接返回false(即0)。switch…case语句无法匹配到0,因此,while(state)无限循环,导致系统主菜单界面闪屏无响应。

图11-26 在room_view.cpp第81行代码设置断点
经过上面的调试和分析,已确定了程序中的问题所在。此时,可以首先通过调用clear()函数清除cin的错误标志,恢复cin的默认状态;然后再调用ignore()函数将cin中不符合输入选项的数据忽略。
在图11-26中第83行代码后添加如下代码,解决输入错误导致程序运行出错的问题。
cin.clear(); //清除cin的错误标志 cin.ignore(); //忽略cin中残留数据
在实际生活中,开发一个项目总会遇到各种各样的问题,每开发完一个项目都需要进行简单的总结,酒店管理系统也不例外,下面就总结一下酒店管理系统项目的开发心得。
1. 项目整体规划
每一个项目,在实现之前都要分析设计本项目要实现哪些功能。将这些功能划分成不同的模块,如果模块较大,还可以在内部划分成更小的功能模块。这样逐个实现每个模块,条理清晰。在分别实现各个模块后,再将这些模块整合,使各个功能协调、有序地进行。
2. 类的设计
在项目开发中,类的设计要简洁、独立。在本项目中,每个模块设计了一个类,每个类根据模块的功能封装成员变量与成员函数。GuestRoom类负责保存客房数据,GuestRoomM anager类用于管理客房数据,GuestV iew类用于显示系统界面,三个类相互独立又相互联系。
GuestRoom类是底层数据,GuestRoomM anager类是中间管理层数据,GuestView类是显示层数据,若需要增加新的功能只需要修改GuestRoomM anager类,即增加新的操作,上层显示模块GuestV iew类只需要调用GuestRoomM anager类中的成员函数即可。
在设计这三个类时,GuestRoom类将客房数据保存到文件或者从文件中读取数据时,以m ap容器作为载体,实现客房数据的存储和读取;GuestRoomM anager类以m ap容器作为成员变量,实现对客房数据的管理;GuestV iew类以GuestRoomM anager类对象作为成员变量,以实现各个操作的界面管理。
3. 清屏
酒店管理系统是一个多操作场景项目,每一次切换操作都会把上一个操作的内容清屏,如果不清屏,多次操作后会造成屏幕内容显示过多。本项目中多处使用清屏语句system("cls"),做到了良好的场景切换体验。
本章综合运用前面所讲的知识,设计了一个综合项目——酒店管理系统,目的是帮助大家了解如何开发一个多模块、多文件的C++程序。在开发这个程序时,首先将一个项目拆分成若干个小的模块,然后分别设计每个模块所需要的类。在实现时,将每个模块的声明和定义分开,放置在头文件和源文件中,最后在一个含有m ain()函数的源文件中,将它们的头文件包含进来,并利用m ain()函数将所有的模块联系起来。通过酒店管理系统项目的学习,读者会对C++程序开发流程有个整体的认识,这对实际工作大有裨益。