数据结构课程设计——机票售卖系统(C++)
引言
這學(xué)期最后的數(shù)據(jù)結(jié)構(gòu)課程設(shè)計需要我們完成一個簡單的小程序,我選擇了一個機票售賣系統(tǒng),實現(xiàn)了一些基本的功能;因為時間給的比較短,又趕在復(fù)習(xí)周補課,所以并沒有什么突出的地方,我就在這里聊聊我的代碼實現(xiàn)和可以進一步改進的地方;
注:該程序并沒有使用C++的面向?qū)ο蟛糠謨?nèi)容 而是使用面向過程編程,主要使用了C++的一些容器和函數(shù);
實現(xiàn)過程
基本功能
功能很簡單,就是以下五種:
1,購票
2,退票
3,顯示用戶信息
4,查詢用戶信息
5,查看航班信息
這里因為是售票系統(tǒng),所以對航班的增加刪除等操作并沒有添加,當然如果你想加也很容易實現(xiàn);
雖然功能很簡單,但是對于輸入信息的判斷需要保持嚴謹,所以如何制定判斷規(guī)則也是要考慮的;
數(shù)據(jù)結(jié)構(gòu)
首先考慮了用什么數(shù)據(jù)結(jié)構(gòu)存儲用戶信息和航班信息,因為需要快速檢索信息,并且在這里并沒有使用數(shù)據(jù)庫而使用最基本的文件操作,所以就考慮使用哈希表來存儲用戶信息和航班信息,并將key值分別設(shè)置為身份證后六位 和 航班號;(這里通過map容器來實現(xiàn)哈希表的操作)
結(jié)構(gòu)體
然后就考慮用戶和航班的結(jié)構(gòu)體,代碼如下:
// 客戶信息結(jié)構(gòu)體 struct Person {string name; // 姓名string age; // 年齡string gender; // 性別string idNum; // 身份證號string myAirNum; // 用戶航班號 };// 航班信息結(jié)構(gòu)體 struct Airplane {string airNum; // 航班號string startPoint; // 起點string destination; // 目的地string tickets; // 票數(shù)string ticketPrice; // 票價string departureTime; // 起飛時間 };很簡單,也沒什么可以說的,可能有人會發(fā)現(xiàn):為什么全部變量都用的是string類型?主要考慮到初始化時要把文件內(nèi)容讀取到內(nèi)存中,而從文件種讀取的都是字符串,所以就直接全部定義成了字符串;
當然這樣不一定是最好的辦法,我也想到了定義一套轉(zhuǎn)換體系,但是時間比較緊所以就沒有過多在這里糾結(jié);如果你有更好的方法也歡迎交流一下!
文件
因為在這我并沒有用到數(shù)據(jù)庫而使用文件,所以并不需要考慮表結(jié)構(gòu)的設(shè)計,但是還是要設(shè)計一下基本的文件格式的;
首先文件選用兩個.txt文件,一個是userInfo存放用戶信息,一個是airplane存放航班信息;
接下來需要考慮如何存放,因為初始化要把文件內(nèi)容讀入內(nèi)存中,所以我定義每個用戶信息(航班信息)占一行,每個屬性間以一個空格劃分,這樣讀取文件時一次就只讀一行,讀入后以空格為基礎(chǔ)劃分字符串;
文件內(nèi)容如圖:
函數(shù)
下面就可以想想需要什么函數(shù)了,我把函數(shù)分為兩大類:基本功能函數(shù) 和 工具函數(shù);
函數(shù)注釋已經(jīng)很詳細了,除非特殊的我會詳細說一下,其他就不多說了;
一、基本功能函數(shù)
函數(shù)聲明:
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航空購票系統(tǒng)**********" << 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");} }初始化信息
主要就是為了開始把文件中的內(nèi)容初始化到內(nèi)存中,方便之后的操作;
/// <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]; // 存放票數(shù)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索引值keyuserInfo.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;// 判斷 航班號是否合法 且 航班存在 且 人數(shù)未滿if (person.myAirNum[0] == 'x' && person.myAirNum.length() == 4&& airplaneInfo.find(person.myAirNum) != airplaneInfo.end() && stringToInt(airplaneInfo[person.myAirNum].tickets) != 0) {// 購買當前航班,則該航班票數(shù)應(yīng)該減一int tickets = stringToInt(airplaneInfo[person.myAirNum].tickets); // 轉(zhuǎn)化為整形tickets--; // 票數(shù)減一airplaneInfo[person.myAirNum].tickets = to_string(tickets); // 轉(zhuǎn)化為字符串updateAirplaneFile(); // 更新航班文件 break;}cout << "航班號不合法或者該航班不存在或者航班人數(shù)已滿,請重新選擇!" << endl;}// 2,用戶信息存入userInfo中string key = person.idNum.substr(13); // 獲取該用戶對應(yīng)的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<< " 票數(shù):" + 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; }退票
退票不僅要在內(nèi)存中刪除用戶信息,文件中也要刪除;但是文件操作并沒有直接刪除的功能,所以當內(nèi)存中的用戶信息刪除后,把所有文件信息清空,然后再重寫把內(nèi)存中的用戶信息寫入文件就可以了;
/// <summary> /// 退票 /// </summary> /// <param name="idNum">退票用戶身份證號</param> void refundTickets(string idNum) {// 判斷該用戶是否存在string key = idNum.substr(13); // 如果不存在if (userInfo.find(key) == userInfo.end()) {cout << "用戶信息不存在!" << endl;return;}// 如果存在,該航班票數(shù)需要增加一int tickets = stringToInt(airplaneInfo[userInfo[key].myAirNum].tickets); // 字符串轉(zhuǎn)整型tickets++; // 票數(shù)加一airplaneInfo[userInfo[key].myAirNum].tickets = to_string(tickets); // 整型轉(zhuǎn)字符串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; }二、工具函數(shù)
函數(shù)聲明:
vector<string> split(const string& str, const string& delim); // 字符串分割函數(shù) bool idNumIsLegal(string idNum); // 判斷身份證號是否合法 bool nameIsLegal(string name); // 判斷姓名是否合法 int stringToInt(string str); // 將string類型轉(zhuǎn)化為int類型 void clearFile(const string fileName); // 清空文件內(nèi)容 void updateAirplaneFile(); // 更新航班文件信息字符串分割函數(shù)
因為C++沒有split函數(shù),但是可以通過C語言的strtok函數(shù)實現(xiàn)相應(yīng)的功能,于是我自行封裝了一個字符串分割函數(shù);
/// <summary> /// 字符串分割函數(shù) /// </summary> /// <param name="str">需要的分割的字符串</param> /// <param name="delim">分割標準</param> /// <returns>字符串數(shù)組</returns> vector<string> split(const string& str, const string& delim) {vector<string> res; // 存放分割字符串的數(shù)組if ("" == str) return res; // 字符串為空則無法分割// 先將要切割的字符串從string類型轉(zhuǎn)換為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轉(zhuǎn)化為char*賦給dchar* p = strtok(strs, d); // 首次調(diào)用指向分解字符串while (p) {string s = p; // 分割得到的字符串轉(zhuǎn)換為string類型 res.push_back(s); // 存入結(jié)果數(shù)組 p = strtok(NULL, d); // 之后每一次分割,分解字符串處為NULL}return res; }判斷身份證號是否合法
其實判斷身份證號合法只需要把身份證號分為幾部分分別判斷就可以了;
但是這樣有點麻煩,可以選擇更簡單的正則表達式實現(xiàn)判斷,正好C++也支持正則表達式,所以我就網(wǎng)上找到了身份證號的正則表達式直接用就好了;
判斷姓名是否合法
和身份證號判斷一樣,直接使用正則表達式;
/// <summary> /// 判斷姓名是否合法 /// </summary> /// <param name="name">姓名</param> /// <returns>合法為true,非法為false</returns> bool nameIsLegal(string name) {// 定義一個 正則表達式 為18位身份證號碼的判定規(guī)則regex repPattern("^[\u4e00-\u9fa5]+(·[\u4e00-\u9fa5]+)*$");// 聲明匹配結(jié)果變量match_results<string::const_iterator> rerResult;// 進行匹配bool bValid = regex_match(name, rerResult, repPattern);if (bValid) {// 匹配成功//cout << "姓名格式合法!" << endl;return true;}//cout << "姓名格式不合法!" << endl;return false; }將string類型轉(zhuǎn)化為int類型
這個使用的是stoi函數(shù),不了解的可以看一下我的這篇文章:C++中stoi(),atoi() ,to_string()使用技巧
/// <summary> /// 將string類型轉(zhuǎn)化為int類型 /// </summary> /// <param name="str">需轉(zhuǎn)化的字符串</param> /// <returns>該字符串轉(zhuǎn)化后的整型</returns> int stringToInt(string str) {// 判斷字符串是否為空 if (str.empty()) {cout << "數(shù)據(jù)出現(xiàn)錯誤,請檢查文件數(shù)據(jù)!" << endl;return 0;}return stoi(str); // 字符串轉(zhuǎn)化為整型 }清空文件內(nèi)容
/// <summary> /// 清空文件內(nèi)容 /// </summary> /// <param name="fileName">文件名</param> void clearFile(const string fileName) {fstream file(fileName, ios::out); }更新航班文件信息
這個就是和更新用戶信息一樣的,就是刪除了重寫,因為多次用到就單獨寫成函數(shù)了;
/// <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(); }總結(jié)
因為這個項目功能實現(xiàn)很簡單,代碼實現(xiàn)起來并沒有那么難;其實一個項目的開始我感覺最不簡單的就是從0到1的過程,當整個框架有了之后其實就沒有什么難度了;
缺點
其實用戶信息的設(shè)計還是存在問題的,就是如果獲取了身份證號,那么就可以直接獲取到用戶的性別和年齡了,這兩個變量就是多余的了,所以如果代碼進一步的改進的化,我會優(yōu)先修改這一點;這也是最初設(shè)計存在的問題,結(jié)果到最后才想到,這時如果進行修改那么修改內(nèi)容可就很多了;
對我來說這也是一個教訓(xùn),最初構(gòu)思設(shè)計一定要保證嚴謹,這樣才能避免后期大規(guī)模的修改;
目前還沒想到其他問題,如果你有其他發(fā)現(xiàn)或者有新奇的想法也歡迎來交流;
代碼網(wǎng)盤鏈接
這里就放一個代碼和文件的獲取鏈接,我使用的軟件是vs2019,有些gcc老版本可能無法識別正則表達式,所以你可以自行更新gcc版本或者使用vs2019即以上版本;
百度網(wǎng)盤:
鏈接:https://pan.baidu.com/s/1BkLI-FhpJ05O2sTMUuf6cw
提取碼:xxxx
歡迎大家的點評!
總結(jié)
以上是生活随笔為你收集整理的数据结构课程设计——机票售卖系统(C++)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试官:谈谈equals() 和 ==
- 下一篇: [Java基础] 反射机制汇总