本文為系列第三篇,聚焦核心業務邏輯實現:數據文件讀寫持久化、用戶認證、航班查詢/訂票/退票、鏈表數據結構管理。展示純Win32 API下的典型桌面應用架構模式。
/* 乘客/用戶結構 */
typedef struct _Passenger {
char Name[32];
char IdNum[19]; // 身份證號
char PassWord[32];
int TicketNum; // 訂票總數
struct _Passenger *Next;
OrderLink OrderLink; // 訂單鏈表頭
} Passenger;
/* 航班結構 */
typedef struct _Flight {
char Flight_Number[16];
double Fare; // 價格
char Discount[8]; // 折扣
int Seat_Number; // 總座位
int Vacant_Seat; // 剩余座位
char Departure[16];
char Destination[16];
char Date[16];
char TakeOff_Time[16];
char Landing_Time[16];
struct _Flight *Next;
} Flight;
/* 訂單結構 */
typedef struct _OrderForm {
int Order_Number;
int Tickets_Num;
char IdNum[19];
char Flight_Number[16];
char Departure[16];
char Destination[16];
char Date[16];
char TakeOff_Time[16];
char Landing_Time[16];
struct _OrderForm *Next; // 全局訂單鏈表
struct _OrderForm *psgNext; // 用戶訂單鏈表
} OrderForm;
/* 鏈表頭結構 */
typedef struct { Passenger *head, *tail; int PsgNum; } PsgLink;
typedef struct { Flight *head, *tail; int Flight_Number; } FlightLink;
typedef struct { OrderForm *head, *tail; int AllOrderNum; } AllOrderLink;
BOOL ReadAccountData(HWND hwndDlg) {
FILE *fp;
passenger = (Passenger*)malloc(sizeof(Passenger)); // 登錄用戶緩存
if (!passenger) return ShowError(hwndDlg, "內存申請失敗");
psglink.head = psglink.tail = NULL;
psglink.PsgNum = 0;
if ((fp = fopen(".\\AccountData.txt", "r")) == NULL) {
return ShowError(hwndDlg, "賬戶文件讀取失敗");
}
while (!feof(fp)) {
Passenger *p = (Passenger*)malloc(sizeof(Passenger));
if (!p) { fclose(fp); return ShowError(hwndDlg, "內存不足"); }
/* 關鍵:檢查fscanf返回值防止feof多讀一行 */
if (fscanf(fp, "%s %s %s", p->Name, p->IdNum, p->PassWord) == EOF) {
free(p); break;
}
p->Next = NULL;
p->TicketNum = 0;
p->OrderLink.head = p->OrderLink.tail = NULL;
p->OrderLink.OrderNum = 0;
if (psglink.head == NULL) psglink.head = p;
else psglink.tail->Next = p;
psglink.tail = p;
psglink.PsgNum++;
}
fclose(fp);
return TRUE;
}
fopen/fscanf等「不安全」警告feof(fp)在讀操作嘗試讀取EOF後才返回真。直接while(!feof(fp))會導致最後一行被處理兩次。解決:檢查讀取函數返回值(如fscanf(...)==EOF)作為循環終止條件\r\n→\n,寫入時每行末尾有\n,讀取時需注意BOOL AccountLogIn(HWND hwndDlg) {
int flag = 0;
GetDlgItemText(hwndDlg, IDC_IDEDIT, passenger->IdNum, 256);
GetDlgItemText(hwndDlg, IDC_PSWEDIT, passenger->PassWord, 256);
Passenger *p = psglink.head;
while (p) {
if (!lstrcmp(passenger->IdNum, p->IdNum)) { // 找到用戶
flag = 1;
if (!lstrcmp(passenger->PassWord, p->PassWord)) { // 密碼匹配
lstrcpy(passenger->Name, p->Name); // 緩存用戶名
MessageBox(hwndDlg, "登錄成功", "提示", MB_OK|MB_ICONINFORMATION);
EndDialog(hwndDlg, TRUE);
if (ReadAccountOrder(hwndDlg, passenger)) // 讀取用戶訂單
DialogBox(hInst, MAKEINTRESOURCE(IDD_MAINDLG), NULL, MainDlgProc);
return TRUE;
} else {
MessageBox(hwndDlg, "密碼錯誤", "錯誤", MB_OK|MB_ICONERROR);
return FALSE;
}
}
p = p->Next;
}
if (!flag) {
MessageBox(hwndDlg, "賬戶不存在,請先註冊", "提示", MB_OK|MB_ICONINFORMATION);
}
return FALSE;
}
BOOL AccountRegister(HWND hwndDlg) {
GetDlgItemText(hwndDlg, IDC_IDEDIT, passenger->IdNum, 256);
GetDlgItemText(hwndDlg, IDC_PSWEDIT, passenger->PassWord, 256);
if (lstrlen(passenger->PassWord) == 0) return ShowError(hwndDlg, "請輸入密碼");
DialogBox(hInst, MAKEINTRESOURCE(IDD_NAMEDLG), NULL, NameDlgProc); // 彈出輸入姓名對話框
if (lstrlen(passenger->Name) == 0) return FALSE;
passenger->Next = NULL;
passenger->TicketNum = 0;
passenger->OrderLink.head = passenger->OrderLink.tail = NULL;
passenger->OrderLink.OrderNum = 0;
if (psglink.head == NULL) psglink.head = passenger;
else psglink.tail->Next = passenger;
psglink.tail = passenger;
psglink.PsgNum++;
MessageBox(hwndDlg, "註冊成功", "提示", MB_OK|MB_ICONINFORMATION);
EndDialog(hwndDlg, TRUE);
DialogBox(hInst, MAKEINTRESOURCE(IDD_MAINDLG), NULL, MainDlgProc);
return TRUE;
}
BOOL SearchFlight(HWND hMainDlg) {
int flag1 = 0, flag2 = 0; // flag1: 1=按航班號查, 0=按出發/到達查
TCHAR szDeparture[16], szDestination[16], szFltNum[16], szDate[16];
TCHAR szBuffer[256];
HWND hEdit = GetDlgItem(hMainDlg, IDC_INFORMATION);
GetDlgItemText(hMainDlg, IDC_DEPARTURE, szDeparture, 15);
GetDlgItemText(hMainDlg, IDC_DESTINATION, szDestination, 15);
GetDlgItemText(hMainDlg, IDC_FLIGHTNUM, szFltNum, 15);
GetDlgItemText(hMainDlg, IDC_DATE, szDate, 15);
SetDlgItemText(hMainDlg, IDC_INFORMATION, TEXT("")); // 清空顯示區
/* 輸入驗證邏輯 */
if (lstrlen(szFltNum) || (!lstrlen(szDeparture) && !lstrlen(szDestination)))
flag1 = 1;
else if (!lstrlen(szDeparture)) return ShowError(hMainDlg, "請輸入出發地");
else if (!lstrlen(szDestination)) return ShowError(hMainDlg, "請輸入目的地");
if (flag1 && !lstrlen(szFltNum)) return ShowError(hMainDlg, "請輸入查詢條件");
Flight *p = flightlink.head;
while (p) {
if (flag1) { // 按航班號+日期
if (!lstrcmp(p->Flight_Number, szFltNum) && !lstrcmp(p->Date, szDate)) {
if (!flag2) PrintHeader(hMainDlg); // 首次打印表頭
flag2++;
PrintFlight(hMainDlg, p);
}
} else { // 按出發地+目的地+日期
if (!lstrcmp(p->Departure, szDeparture) &&
!lstrcmp(p->Destination, szDestination) &&
!lstrcmp(p->Date, szDate)) {
if (!flag2) PrintHeader(hMainDlg);
flag2++;
PrintFlight(hMainDlg, p);
}
}
p = p->Next;
}
if (!flag2) MessageBox(hMainDlg, "無符合條件航班", "提示", MB_OK|MB_ICONINFORMATION);
return TRUE;
}
BOOL PrintFlight(HWND hwndDlg, Flight *p) {
TCHAR szBuffer[256];
HWND hEdit = GetDlgItem(hwndDlg, IDC_INFORMATION);
_stprintf(szBuffer, TEXT("\n%s\t%s\t%s\t%s\t%s\t%d\t%.2lf\t%s\t%s\n\n"),
p->Flight_Number, p->Departure, p->Destination,
p->TakeOff_Time, p->Landing_Time,
p->Vacant_Seat, p->Fare, p->Discount, p->Date);
SendMessage(hEdit, EM_SETSEL, -1, -1); // 定位到末尾
SendMessage(hEdit, EM_REPLACESEL, 0, (LPARAM)szBuffer); // 追加文本
return TRUE;
}
BOOL BookTickets(HWND hMainDlg) {
TCHAR szFltNum[16], szDate[16];
GetDlgItemText(hMainDlg, IDC_FLIGHTNUM, szFltNum, 15);
GetDlgItemText(hMainDlg, IDC_DATE, szDate, 15);
if (!lstrlen(szFltNum)) return ShowError(hMainDlg, "請輸入航班號");
DialogBox(hInst, MAKEINTRESOURCE(IDD_FLTNUM), NULL, FlightNumDlgProc); // 獲取訂票數
if (!lstrlen(szBuffer)) return FALSE;
int tickets_num = _ttoi(szBuffer);
Flight *p = flightlink.head;
while (p) {
if (!lstrcmp(p->Flight_Number, szFltNum) && !lstrcmp(p->Date, szDate)) {
_Book_Tickets(hMainDlg, p, tickets_num);
}
p = p->Next;
}
return TRUE;
}
BOOL _Book_Tickets(HWND hwndDlg, Flight *p, int Tickets_Num) {
if (Tickets_Num > p->Vacant_Seat) { // 余票不足
Recommend(hwndDlg, p, Tickets_Num); // 推薦替代航班
return FALSE;
}
OrderForm *new_order = (OrderForm*)malloc(sizeof(OrderForm));
if (!new_order) return ShowError(hwndDlg, "內存不足");
new_order->Order_Number = ++allorder.AllOrderNum;
new_order->Tickets_Num = Tickets_Num;
lstrcpy(new_order->IdNum, passenger->IdNum);
lstrcpy(new_order->Flight_Number, p->Flight_Number);
lstrcpy(new_order->Departure, p->Departure);
lstrcpy(new_order->Destination, p->Destination);
lstrcpy(new_order->Date, p->Date);
lstrcpy(new_order->TakeOff_Time, p->TakeOff_Time);
lstrcpy(new_order->Landing_Time, p->Landing_Time);
new_order->Next = new_order->psgNext = NULL;
/* 插入全局訂單鏈表尾部 */
if (allorder.head == NULL) allorder.head = new_order;
else allorder.tail->Next = new_order;
allorder.tail = new_order;
/* 插入用戶訂單鏈表尾部 */
if (passenger->OrderLink.head == NULL) passenger->OrderLink.head = new_order;
else passenger->OrderLink.tail->psgNext = new_order;
passenger->OrderLink.tail = new_order;
passenger->OrderLink.OrderNum++;
passenger->TicketNum += Tickets_Num;
p->Vacant_Seat -= Tickets_Num; // 扣減余票
MessageBox(hwndDlg, "訂票成功", "提示", MB_OK|MB_ICONINFORMATION);
return TRUE;
}
/* 余票不足時推薦同航線有票航班 */
BOOL Recommend(HWND hwndDlg, Flight *p, int Tickets_Num) {
Flight *q = flightlink.head;
int flag = 0;
while (q) {
if (!lstrcmp(p->Departure, q->Departure) &&
!lstrcmp(p->Destination, q->Destination) &&
lstrcmp(p->Flight_Number, q->Flight_Number) &&
Tickets_Num <= q->Vacant_Seat) {
if (!flag) { /* 打印推薦表頭 */ flag++; }
PrintFlight(hwndDlg, q);
}
q = q->Next;
}
if (!flag) MessageBox(hwndDlg, "無替代航班", "提示", MB_OK|MB_ICONINFORMATION);
return TRUE;
}
BOOL ReturnTickets(HWND hwndDlg) {
TCHAR szFltNum[16], szDate[16];
GetDlgItemText(hwndDlg, IDC_FLIGHTNUM, szFltNum, 15);
GetDlgItemText(hwndDlg, IDC_DATE, szDate, 15);
if (!lstrlen(szFltNum)) return ShowError(hwndDlg, "請輸入航班號");
DialogBox(hInst, MAKEINTRESOURCE(IDD_FLTNUM), NULL, FlightNumDlgProc);
if (!lstrlen(szBuffer)) return FALSE;
int tickets_num = _ttoi(szBuffer);
/* 先統計該航班用戶總訂票數 */
int total_booked = 0;
OrderForm *p = passenger->OrderLink.head;
while (p) {
if (!lstrcmp(p->Flight_Number, szFltNum) && !lstrcmp(p->Date, szDate))
total_booked += p->Tickets_Num;
p = p->psgNext;
}
if (tickets_num > total_booked)
return ShowError(hwndDlg, "退票數超過已訂票數");
/* 執行退票:需同步更新 用戶訂單鏈表、全局訂單鏈表、航班余票 */
p = passenger->OrderLink.head;
OrderForm *prePoint = p;
while (p) {
if (!lstrcmp(p->Flight_Number, szFltNum) && !lstrcmp(p->Date, szDate)) {
if (tickets_num >= p->Tickets_Num) { // 整單退掉
OrderForm *s = p->psgNext;
/* 更新用戶統計 */
passenger->TicketNum -= p->Tickets_Num;
passenger->OrderLink.OrderNum--;
allorder.AllOrderNum--;
tickets_num -= p->Tickets_Num;
/* 從用戶訂單鏈表刪除節點 */
if (passenger->OrderLink.head == p) {
passenger->OrderLink.head = p->psgNext;
if (!p->psgNext) passenger->OrderLink.tail = NULL;
} else if (!p->psgNext) {
passenger->OrderLink.tail = prePoint;
}
prePoint->psgNext = p->psgNext;
/* 從全局訂單鏈表刪除節點 */
OrderForm *q = allorder.head, *preQ = q;
while (q) {
if (q == p) {
if (allorder.head == q) {
allorder.head = q->Next;
if (!q->Next) allorder.tail = NULL;
} else if (!q->Next) {
allorder.tail = preQ;
}
preQ->Next = q->Next;
free(p);
break;
}
preQ = q; q = q->Next;
}
/* 恢復航班余票 */
Flight *fl = flightlink.head;
while (fl) {
if (!lstrcmp(fl->Flight_Number, szFltNum) && !lstrcmp(fl->Date, szDate)) {
fl->Vacant_Seat += p->Tickets_Num; break;
}
fl = fl->Next;
}
if (tickets_num == 0) { MessageBox(hwndDlg, "退票成功", "提示", MB_OK|MB_ICONINFORMATION); return TRUE; }
p = s; continue; // 繼續退下一張
} else { // 部分退票
p->Tickets_Num -= tickets_num;
passenger->TicketNum -= tickets_num;
Flight *fl = flightlink.head;
while (fl) {
if (!lstrcmp(fl->Flight_Number, szFltNum) && !lstrcmp(fl->Date, szDate)) {
fl->Vacant_Seat += tickets_num; break;
}
fl = fl->Next;
}
MessageBox(hwndDlg, "退票成功", "提示", MB_OK|MB_ICONINFORMATION);
return TRUE;
}
}
prePoint = p;
p = p->psgNext;
}
return ShowError(hwndDlg, "未找到對應訂單");
}
| 現狀問題 | 優化方向 |
|---|---|
| 全局變量耦合嚴重 | 封裝為Context結構體,通過GWLP_USERDATA關聯窗口 |
| 文件操作無鎖,多進程不安全 | 引入文件鎖或遷移SQLite嵌入式數據庫 |
| 鏈表查找O(n),數據量大時性能差 | 關鍵索引(航班號、身份證)引入哈希表 |
| UI邏輯與業務邏輯混雜 | 分離Presenter層,便於單元測試 |
| 錯誤處理重複代碼多 | 封裝ShowError/CheckMalloc等輔助宏 |
| 緩衝區固定大小,潛在溢出風險 | 使用動態字符串或strncpy_s/sprintf_s |
這個系統展示了Win32 API開發中典型的「對話框驅動 + 全局鏈表數據模型 + 文本文件持久化」架構。雖然代碼風格較為傳統,但清晰地演示了桌面應用的核心數據流:啟動加載→內存鏈表操作→退出持久化。現代化重構時,建議引入SQLite替代文本文件、使用WTL/ATL封裝窗口類、採用MVC模式分離關注點。