專案背景
模擬支付寶核心 User 類別,涵蓋帳號/密碼/手機格式驗證、性別設定、餘額查詢、存款/提款(密碼驗證)、命名空間封裝。強調輸入防呆、封裝性、C 風格字串介面轉換。
類別設計 (user.hpp)
namespace alipay {
namespace Gender {
enum Gender { Female = 0, Male = 1, Unknown = 2 };
}
class User {
string username, password, phone;
Gender::Gender gender;
long long balance; // 分為單位,避免浮點誤差
// 內部驗證輔助函數
static bool verifyUsernameFormat(const string&);
static bool verifyPasswordFormat(const string&);
static bool verifyPhoneFormat(const string&);
public:
User() : gender(Gender::Unknown), balance(0) {}
// 設定器 (C 風格字串介面)
bool setUsername(const char* username);
bool setPassword(const char* newPwd, const char* oldPwd);
bool setPhone(const char* phone);
bool setGender(Gender::Gender g);
// 取得器
string getUsername() const;
string getPhone() const;
Gender::Gender getGender() const;
// 資金操作
long long getMoneyAmount(const char* pwd); // 密碼錯誤回傳 -1
bool deposit(long long amount); // 金額>0
bool withdraw(long long amount, const char* pwd); // 驗證密碼、餘額足夠、金額>0
};
}
格式驗證規則
| 欄位 | 規則 | 合法範例 | 非法範例 |
| 用戶名 | 英數字,長度 6~20 | Alice1995, Heart2you, 5201314 | 222@_@222, 12306, abc12? |
| 密碼 | 英數字,長度 8~20 | 12345678, abc00000000 | 123, 21?=_=?12 |
| 手機 | 純數字,固定 13 碼 (含國碼 86) | 8618819473230 | 110, 330 |
核心實作細節
通用英數字檢查
static bool isAlnumOnly(const string& s) {
for (char c : s) {
if (!((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z'))) return false;
}
return true;
}
bool User::verifyUsernameFormat(const string& s) {
return s.length() >= 6 && s.length() <= 20 && isAlnumOnly(s);
}
bool User::verifyPasswordFormat(const string& s) {
return s.length() >= 8 && s.length() <= 20 && isAlnumOnly(s);
}
bool User::verifyPhoneFormat(const string& s) {
if (s.length() != 13) return false;
for (char c : s) if (c < '0' || c > '9') return false;
return true;
}
設定器實作要點
setUsername:驗證通過直接賦值。
setPassword:舊密碼正確 (或首次設定時舊密碼為空) 且 新密碼格式合法 才更新。
setPhone:單純格式驗證。
setGender:不可從 Unknown 改為 Unknown,其餘轉換允許。
資金操作邏輯
long long User::getMoneyAmount(const char* pwd) {
return (password == pwd) ? balance : -1;
}
bool User::deposit(long long amount) {
if (amount > 0) { balance += amount; return true; }
return false;
}
bool User::withdraw(long long amount, const char* pwd) {
if (password != pwd) return false; // 密碼錯誤
if (amount <= 0) return false; // 非正金額
if (balance < amount) return false; // 餘額不足
balance -= amount;
return true;
}
命名空間與編譯單元
- 頭檔
user.hpp:類別宣告,namespace alipay 包裹。
- 實作
user.cpp:#include "user.hpp",定義時加 alipay::User:: 前綴。
- 測試
test.hpp:unittest::TEST 執行三組測試案例 (合法流程、非法輸入、惡意提款)。
常見錯誤與避坑
| 錯誤 | 正確做法 |
頭檔使用 using namespace std; | 頭檔完整限定 std::string,汙染全域命名空間 |
setPassword 未驗證舊密碼 | 必須比對 old_password (首次設定允許空字串) |
| 手機號未檢查長度 13 | 嚴格檢查 length() == 13 且全數字 |
提款未檢查 amount > 0 | 防止負數提款變相存款 |
char* 直接比較字串內容 | 轉 std::string 再比較,或用 strcmp |
思考題
- 此設計缺點?→ 明文儲存密碼、無交易紀錄、無並發控制、金額用整數分單位但介面未文檔化。
- 為何頭檔避免
using namespace std;?→ 汙染引用該頭檔的所有編譯單元,易發生名稱衝突,違反封裝原則。