首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Windows API開發飛機訂票系統(三):核心業務邏輯實現與文件數據持久化

     編輯:關於C語言

Windows API飛機訂票系統核心模塊實現詳解

本文為系列第三篇,聚焦核心業務邏輯實現:數據文件讀寫持久化、用戶認證、航班查詢/訂票/退票、鏈表數據結構管理。展示純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;
}

關鍵技術點:VS2013+安全CRT與feof陷阱

  1. _CRT_SECURE_NO_WARNINGS:定義宏或項目屬性中添加,抑制fopen/fscanf等「不安全」警告
  2. feof陷阱:feof(fp)在讀操作嘗試讀取EOF後才返回真。直接while(!feof(fp))會導致最後一行被處理兩次。解決:檢查讀取函數返回值(如fscanf(...)==EOF)作為循環終止條件
  3. 文本模式換行符:Windows文本模式\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模式分離關注點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved