引言
這學期最后的資料結構課程設計需要我們完成一個簡單的小程式,我選擇了一個機票售賣系統,實作了一些基本的功能;因為時間給的比較短,又趕在復習周補課,所以并沒有什么突出的地方,我就在這里聊聊我的代碼實作和可以進一步改進的地方;
注:該程式并沒有使用C++的面向物件部分內容 而是使用面向程序編程,主要使用了C++的一些容器和函式;
實作程序
基本功能
功能很簡單,就是以下五種:
1,購票
2,退票
3,顯示用戶資訊
4,查詢用戶資訊
5,查看航班資訊
這里因為是售票系統,所以對航班的增加洗掉等操作并沒有添加,當然如果你想加也很容易實作;
雖然功能很簡單,但是對于輸入資訊的判斷需要保持嚴謹,所以如何制定判斷規則也是要考慮的;
資料結構
首先考慮了用什么資料結構存盤用戶資訊和航班資訊,因為需要快速檢索資訊,并且在這里并沒有使用資料庫而使用最基本的檔案操作,所以就考慮使用哈希表來存盤用戶資訊和航班資訊,并將key值分別設定為身份證后六位 和 航班號;(這里通過map容器來實作哈希表的操作)
結構體
然后就考慮用戶和航班的結構體,代碼如下:
// 客戶資訊結構體
struct Person {
string name; // 姓名
string age; // 年齡
string gender; // 性別
string idNum; // 身份證號
string myAirNum; // 用戶航班號
};
// 航班資訊結構體
struct Airplane {
string airNum; // 航班號
string startPoint; // 起點
string destination; // 目的地
string tickets; // 票數
string ticketPrice; // 票價
string departureTime; // 起飛時間
};
很簡單,也沒什么可以說的,可能有人會發現:為什么全部變數都用的是string型別?主要考慮到初始化時要把檔案內容讀取到記憶體中,而從檔案種讀取的都是字串,所以就直接全部定義成了字串;
當然這樣不一定是最好的辦法,我也想到了定義一套轉換體系,但是時間比較緊所以就沒有過多在這里糾結;如果你有更好的方法也歡迎交流一下!
檔案
因為在這我并沒有用到資料庫而使用檔案,所以并不需要考慮表結構的設計,但是還是要設計一下基本的檔案格式的;
首先檔案選用兩個.txt檔案,一個是userInfo存放用戶資訊,一個是airplane存放航班資訊;
接下來需要考慮如何存放,因為初始化要把檔案內容讀入記憶體中,所以我定義每個用戶資訊(航班資訊)占一行,每個屬性間以一個空格劃分,這樣讀取檔案時一次就只讀一行,讀入后以空格為基礎劃分字串;
檔案內容如圖:


函式
下面就可以想想需要什么函式了,我把函式分為兩大類:基本功能函式 和 工具函式;
函式注釋已經很詳細了,除非特殊的我會詳細說一下,其他就不多說了;
一、基本功能函式
函式宣告:
void initInfo(); // 初始化資訊
void UI(); // UI界面
void showUserInfo(); // 顯示輸出用戶資訊
void showAirplaneInfo(); // 顯示輸出航班資訊
void sellTickets(); // 售票
void selectUserInfo(string idNum); // 查詢用戶資訊
void refundTickets(string idNum); // 退票
UI界面
列印出來操作界面以及操作的匯總;
/// <summary>
/// UI界面
/// </summary>
void UI() {
while (true) {
// 列印界面
cout << "************歡迎使用xxxx航空購票系統**********" << endl;
cout << "************** 1, 購 票 **************" << endl;
cout << "************** 2, 退 票 **************" << endl;
cout << "************** 3,查看購票資訊 **************" << endl;
cout << "************** 4,查找用戶資訊 **************" << endl;
cout << "************** 5,查看航班資訊 **************" << endl;
cout << "************** 6, 退 出 **************" << endl;
int choose; // 選項
cin >> choose; // 輸入選擇
switch (choose) {
case 1 :
sellTickets(); // 售票
break;
case 2: {
string id;
while (true) {
cout << "請輸入退票人的身份證號碼:";
cin >> id;
// 判斷輸入身份證號是否合法
if (idNumIsLegal(id)) {
break;
}
cout << "身份證號不合法,請重新輸入!" << endl;
}
refundTickets(id); // 退票
break;
}
case 3 :
showUserInfo(); // 查看購票資訊
break;
case 4: {
string id;
while (true) {
cout << "請輸入查詢用戶的身份證號碼:";
cin >> id;
// 判斷輸入身份證號是否合法
if (idNumIsLegal(id)) {
break;
}
cout << "身份證號不合法,請重新輸入!" << endl;
}
selectUserInfo(id); // 查看查詢用戶的資訊
break;
}
case 5 :
showAirplaneInfo(); // 查看航班資訊
break;
case 6 :
cout << "歡迎下次使用!" << endl;
exit(-1); // 退出
}
system("pause");
system("cls");
}
}
初始化資訊
主要就是為了開始把檔案中的內容初始化到記憶體中,方便之后的操作;
/// <summary>
/// 初始化資訊
/// </summary>
void initInfo() {
ifstream ifs01;
// 對航班資訊進行初始化
ifs01.open("airplane.txt", ios::in); // 打開檔案
// 如果檔案打開失敗
if (!ifs01.is_open()) {
cout << "檔案打開失敗!\a" << endl;
exit(-1);
}
// 如果航班資訊不為空
string ainfo; // 臨時存放每一個航班資訊
// 按行讀取客戶資訊
while (getline(ifs01, ainfo)) {
vector<string> separateInfo; // 存放分離的資訊
separateInfo = split(ainfo, " "); // 分離航班資訊
Airplane airplane; // 存放臨時航班資訊
airplane.airNum = separateInfo[0]; // 存放航班號
airplane.startPoint = separateInfo[1]; // 存放起點
airplane.destination = separateInfo[2]; // 存放目的地
airplane.tickets = separateInfo[3]; // 存放票數
airplane.ticketPrice = separateInfo[4]; // 存放票價
airplane.departureTime = separateInfo[5]; // 存放起飛時間
// 航班資訊存盤
string key = separateInfo[0]; // 獲取key值,即航班號
airplaneInfo.insert(pair<string, Airplane>(key, airplane)); // 將航班資訊存入map
}
ifs01.close();
// 對用戶資訊進行初始化
ifstream ifs02;
ifs02.open("userInfo.txt", ios::in); // 打開檔案
// 如果檔案打開失敗
if (!ifs02.is_open()) {
cout << "檔案打開失敗!\a" << endl;
exit(-1);
}
// 如果用戶資訊不為空
string info; // 臨時存放每一個客戶資訊
// 按行讀取客戶資訊
while (getline(ifs02, info)) {
vector<string> separateInfo; // 存放分離的資訊
separateInfo = split(info, " "); // 分離客戶資訊
Person person; // 存放臨時客戶資訊
person.name = separateInfo[0]; // 存放姓名
person.age = separateInfo[1]; // 存放年齡
person.gender = separateInfo[2]; // 存放性別
person.idNum = separateInfo[3]; // 存放身份證號
person.myAirNum = separateInfo[4]; // 存放航班號
// 客戶資訊存盤
string key = separateInfo[3].substr(13); // 身份證后六位為map索引值key
userInfo.insert(pair<string, Person>(key, person)); // 將客戶資訊存入map
}
ifs02.close();
}
售票
增加用戶并存入檔案;
/// <summary>
/// 售票
/// </summary>
void sellTickets() {
// 1,錄入用戶資訊
Person person; // 買票客戶
// 輸入姓名
while (true) {
cout << "請輸入姓名:";
cin >> person.name;
// 判斷姓名是否合法
if (nameIsLegal(person.name)) {
break;
}
cout << "姓名不合法,請重新輸入!" << endl;
}
// 輸入年齡
while (true) {
cout << "請輸入年齡:";
cin >> person.age;
// 判斷輸入年齡是否符合常理
if (stringToInt(person.age) > 110 || stringToInt(person.age) < 0) {
cout << "年齡不合法,請重新輸入!" << endl;
}
else {
break;
}
}
// 輸入性別
while (true) {
cout << "請輸入性別:";
cin >> person.gender;
// 判斷輸入性別是否合法
if (person.gender == "男" || person.gender == "女") {
break;
}
cout << "性別不合法,請重新輸入!" << endl;
}
// 輸入身份證號
while (true) {
cout << "請輸入身份證號:";
cin >> person.idNum;
// 判斷輸入身份證號是否合法
if (idNumIsLegal(person.idNum)) {
break;
}
cout << "身份證號不合法,請重新輸入!" << endl;
}
// 輸入航班號
while (true) {
cout << "請輸入航班號:";
cin >> person.myAirNum;
// 判斷 航班號是否合法 且 航班存在 且 人數未滿
if (person.myAirNum[0] == 'x' && person.myAirNum.length() == 4
&& airplaneInfo.find(person.myAirNum) != airplaneInfo.end()
&& stringToInt(airplaneInfo[person.myAirNum].tickets) != 0) {
// 購買當前航班,則該航班票數應該減一
int tickets = stringToInt(airplaneInfo[person.myAirNum].tickets); // 轉化為整形
tickets--; // 票數減一
airplaneInfo[person.myAirNum].tickets = to_string(tickets); // 轉化為字串
updateAirplaneFile(); // 更新航班檔案
break;
}
cout << "航班號不合法或者該航班不存在或者航班人數已滿,請重新選擇!" << endl;
}
// 2,用戶資訊存入userInfo中
string key = person.idNum.substr(13); // 獲取該用戶對應的key值:身份證號后六位
userInfo.insert(pair<string, Person>(key, person)); // 將用戶資訊存入map中
// 3,新增用戶資訊寫入檔案操作
fstream fs;
fs.open("userInfo.txt", ios::out | ios::app); // 打開檔案
// 寫入檔案
fs << person.name + " " << person.age + " "
<< person.gender + " " << person.idNum + " "
<< person.myAirNum + " " << endl;
cout << "購票成功!!" << endl;
fs.close();
}
顯示輸出用戶資訊
/// <summary>
/// 顯示輸出用戶資訊
/// </summary>
void showUserInfo() {
// 判斷用戶資訊是否為空
if (userInfo.empty()) {
cout << "用戶資訊為空!!" << endl;
return;
}
// 若用戶資訊不為空開始遍歷
map<string, Person>::iterator it; // 迭代器
for (it = userInfo.begin(); it != userInfo.end(); it++) {
cout << "姓名:" + it->second.name
<< " 年齡:" + it->second.age
<< " 性別:" + it->second.gender
<< " 身份證號:" + it->second.idNum
<< " 航班號:" + it->second.myAirNum
<< " 起點:" + airplaneInfo[it->second.myAirNum].startPoint
<< " 終點:" + airplaneInfo[it->second.myAirNum].destination
<< " 票價:" + airplaneInfo[it->second.myAirNum].ticketPrice
<< " 起飛時間:" + airplaneInfo[it->second.myAirNum].departureTime
<< endl;
}
}
示輸出航班資訊
/// <summary>
/// 顯示輸出航班資訊
/// </summary>
void showAirplaneInfo() {
// 如果航班資訊為空
if (airplaneInfo.empty()) {
cout << "航班資訊為空!" << endl;
return;
}
// 若航班資訊不為空開始遍歷
map<string, Airplane>::iterator it; // 迭代器
int i = 0;
for (it = airplaneInfo.begin(); it != airplaneInfo.end(); it++) {
cout << "航班號:" + it->second.airNum
<< " 起點:" + it->second.startPoint
<< " 終點:" + it->second.destination
<< " 票數:" + it->second.tickets
<< " 票價:" + it->second.ticketPrice
<< " 起飛時間:" + it->second.departureTime
<< endl;
}
}
查詢用戶資訊
map直接找就行;
/// <summary>
/// 查詢用戶資訊
/// </summary>
/// <param name="idNum">查詢用戶的身份證號</param>
void selectUserInfo(string idNum) {
string key = idNum.substr(13); // 獲取key值:身份證號后六位
// 判斷用戶資訊是否存在
if (userInfo.find(key) == userInfo.end()) {
cout <<"該用戶不存在!!" << endl;
return ;
}
Person person = userInfo[key]; // 通過key查找用戶資訊
// 輸出用戶資訊
cout << "姓名:" + person.name
<< " 年齡:" + person.age
<< " 性別:" + person.gender
<< " 身份證號:" + person.idNum
<< " 航班號:" + person.myAirNum
<< " 起點:" + airplaneInfo[person.myAirNum].startPoint
<< " 終點:" + airplaneInfo[person.myAirNum].destination
<< " 票價:" + airplaneInfo[person.myAirNum].ticketPrice
<< " 起飛時間:" + airplaneInfo[person.myAirNum].departureTime
<< endl;
}
退票
退票不僅要在記憶體中洗掉用戶資訊,檔案中也要洗掉;但是檔案操作并沒有直接洗掉的功能,所以當記憶體中的用戶資訊洗掉后,把所有檔案資訊清空,然后再重寫把記憶體中的用戶資訊寫入檔案就可以了;
/// <summary>
/// 退票
/// </summary>
/// <param name="idNum">退票用戶身份證號</param>
void refundTickets(string idNum) {
// 判斷該用戶是否存在
string key = idNum.substr(13);
// 如果不存在
if (userInfo.find(key) == userInfo.end()) {
cout << "用戶資訊不存在!" << endl;
return;
}
// 如果存在,該航班票數需要增加一
int tickets = stringToInt(airplaneInfo[userInfo[key].myAirNum].tickets); // 字串轉整型
tickets++; // 票數加一
airplaneInfo[userInfo[key].myAirNum].tickets = to_string(tickets); // 整型轉字串
updateAirplaneFile(); // 更新航班檔案
userInfo.erase(key); // 從用戶資訊中洗掉
// 從檔案中洗掉該用戶資訊
clearFile("userInfo.txt"); // 清空檔案
// 重新寫入檔案
fstream fs;
fs.open("userInfo.txt", ios::out | ios::app); // 打開檔案
// 遍歷重新寫入用戶資訊
for (auto i : userInfo) {
fs << i.second.name + " "
<< i.second.age + " "
<< i.second.gender + " "
<< i.second.idNum + " "
<< i.second.myAirNum << endl;
}
fs.close();
cout << "退票成功!!" << endl;
}
二、工具函式
函式宣告:
vector<string> split(const string& str, const string& delim); // 字串分割函式
bool idNumIsLegal(string idNum); // 判斷身份證號是否合法
bool nameIsLegal(string name); // 判斷姓名是否合法
int stringToInt(string str); // 將string型別轉化為int型別
void clearFile(const string fileName); // 清空檔案內容
void updateAirplaneFile(); // 更新航班檔案資訊
字串分割函式
因為C++沒有split函式,但是可以通過C語言的strtok函式實作相應的功能,于是我自行封裝了一個字串分割函式;
/// <summary>
/// 字串分割函式
/// </summary>
/// <param name="str">需要的分割的字串</param>
/// <param name="delim">分割標準</param>
/// <returns>字串陣列</returns>
vector<string> split(const string& str, const string& delim) {
vector<string> res; // 存放分割字串的陣列
if ("" == str) return res; // 字串為空則無法分割
// 先將要切割的字串從string型別轉換為char*型別
char* strs = new char[str.length() + 1];
strcpy(strs, str.c_str());
char* d = new char[delim.length() + 1];
strcpy(d, delim.c_str()); // 將delim轉化為char*賦給d
char* p = strtok(strs, d); // 首次呼叫指向分解字串
while (p) {
string s = p; // 分割得到的字串轉換為string型別
res.push_back(s); // 存入結果陣列
p = strtok(NULL, d); // 之后每一次分割,分解字串處為NULL
}
return res;
}
判斷身份證號是否合法
其實判斷身份證號合法只需要把身份證號分為幾部分分別判斷就可以了;
但是這樣有點麻煩,可以選擇更簡單的正則運算式實作判斷,正好C++也支持正則運算式,所以我就網上找到了身份證號的正則運算式直接用就好了;
/// <summary>
/// 判斷身份證號是否合法
/// </summary>
/// <param name="idNum">身份證號</param>
/// <returns>合法為true,非法為false</returns>
bool idNumIsLegal(string idNum) {
// 定義一個 正則運算式 為18位身份證號碼的判定規則
regex repPattern("^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");
// 宣告匹配結果變數
match_results<string::const_iterator> rerResult;
// 進行匹配
bool bValid = regex_match(idNum, rerResult, repPattern);
if (bValid) {
// 匹配成功
//cout << "身份證號格式合法!" << endl;
return true;
}
//cout << "身份證號格式不合法!" << endl;
return false;
}
判斷姓名是否合法
和身份證號判斷一樣,直接使用正則運算式;
/// <summary>
/// 判斷姓名是否合法
/// </summary>
/// <param name="name">姓名</param>
/// <returns>合法為true,非法為false</returns>
bool nameIsLegal(string name) {
// 定義一個 正則運算式 為18位身份證號碼的判定規則
regex repPattern("^[\u4e00-\u9fa5]+(·[\u4e00-\u9fa5]+)*$");
// 宣告匹配結果變數
match_results<string::const_iterator> rerResult;
// 進行匹配
bool bValid = regex_match(name, rerResult, repPattern);
if (bValid) {
// 匹配成功
//cout << "姓名格式合法!" << endl;
return true;
}
//cout << "姓名格式不合法!" << endl;
return false;
}
將string型別轉化為int型別
這個使用的是stoi函式,不了解的可以看一下我的這篇文章:C++中stoi(),atoi() ,to_string()使用技巧
/// <summary>
/// 將string型別轉化為int型別
/// </summary>
/// <param name="str">需轉化的字串</param>
/// <returns>該字串轉化后的整型</returns>
int stringToInt(string str) {
// 判斷字串是否為空
if (str.empty()) {
cout << "資料出現錯誤,請檢查檔案資料!" << endl;
return 0;
}
return stoi(str); // 字串轉化為整型
}
清空檔案內容
/// <summary>
/// 清空檔案內容
/// </summary>
/// <param name="fileName">檔案名</param>
void clearFile(const string fileName) {
fstream file(fileName, ios::out);
}
更新航班檔案資訊
這個就是和更新用戶資訊一樣的,就是洗掉了重寫,因為多次用到就單獨寫成函式了;
/// <summary>
/// 更新航班檔案資訊
/// </summary>
void updateAirplaneFile() {
// 從檔案中洗掉
clearFile("airplane.txt"); // 清空檔案
// 重新寫入檔案
fstream fs;
fs.open("airplane.txt", ios::out | ios::app); // 打開檔案
// 遍歷重新寫入航班資訊
for (auto i : airplaneInfo) {
fs << i.second.airNum + " "
<< i.second.startPoint + " "
<< i.second.destination + " "
<< i.second.tickets + " "
<< i.second.ticketPrice + " "
<< i.second.departureTime + " "
<< endl;
}
fs.close();
}
總結
因為這個專案功能實作很簡單,代碼實作起來并沒有那么難;其實一個專案的開始我感覺最不簡單的就是從0到1的程序,當整個框架有了之后其實就沒有什么難度了;
缺點
其實用戶資訊的設計還是存在問題的,就是如果獲取了身份證號,那么就可以直接獲取到用戶的性別和年齡了,這兩個變數就是多余的了,所以如果代碼進一步的改進的化,我會優先修改這一點;這也是最初設計存在的問題,結果到最后才想到,這時如果進行修改那么修改內容可就很多了;
對我來說這也是一個教訓,最初構思設計一定要保證嚴謹,這樣才能避免后期大規模的修改;
目前還沒想到其他問題,如果你有其他發現或者有新奇的想法也歡迎來交流;
代碼網盤鏈接
這里就放一個代碼和檔案的獲取鏈接,我使用的軟體是vs2019,有些gcc老版本可能無法識別正則運算式,所以你可以自行更新gcc版本或者使用vs2019即以上版本;
百度網盤:
鏈接:https://pan.baidu.com/s/1BkLI-FhpJ05O2sTMUuf6cw
提取碼:xxxx
歡迎大家的點評!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/401043.html
標籤:其他
下一篇:處理bash腳本中的特殊字符
