標籤:style blog http color 使用 os io strong
最近一個同學在寫個銀行管理系統,然後問我怎麼從檔案搜尋帳戶,給了方法後又不懂檔案裡面的密碼怎麼與輸入的匹配。一般來說,高效的做法是用鏈表實現。用數組實現不是高效的方法,而且浪費空間。再者,對於賬戶類有個人資訊集合的,一般用結構體,代碼寫起來也方便簡單。
但是他卻用數組來做,而且沒有用結構體。對於這種情況下如何搜尋帳戶,如何驗證密碼呢? 我嘗試了一下,發現不難解決。
解決這個問題的方法:用檔案資料的排序定位來做。
第一步:檔案資訊讀入
用fscanf 實現檔案讀出檔案資訊。
1 char a[20];2 3 FILE* fp;4 fp = fopen("test.txt","rb");5 fscanf (fp, "%s", a);6 fclose(fp);
fcanf讀取資料,以空格為分割點。比如對於檔案內容為“abc ABC”(雙引號之內)的情況,用如下代碼:
fscanf (fp, "%s", a);fscanf (fp, "%s", b);
得到字元數組a 為abc;字元數組b為 ABC;
若檔案內容為“abcABC”(雙引號之內)的情況,即abc和ABC之間沒有空格隔開,用以上代碼,得到的結果將是:
字元數組a 為abcABC;字元數組b 為亂碼。
fscanf();還有一個特點,就是在一個程式裡面是順序讀入的。
在此舉個例子:
txt檔案(每個資料一行)內容:
abc
ABC
ruby
然後執行下面的代碼:
int main(){ char a[20],b[20],c[20]; FILE* fp; fp = fopen("test.txt","rb"); fscanf(fp, "%s", a); //……此處省略n行代碼 fscanf(fp, "%s",b); //……此處省略n行代碼 fscanf(fp, "%s",c); fclose(fp);return 0;}
得到的結果是:
字元數組a為 abc,b為ABC,c為ruby;
第二步:搜尋帳號
帳號搜尋的方法是遍曆檔案資料,找到與輸入匹配的帳號就停止搜尋。
用 while(fscanf(fp,"%s",a) > 0) 實現遍曆檔案資料。
作用是把檔案內容一行一行讀入賦值給字元數組a,然後再與輸入的帳號進行比較。
同時使用標記 flag 判斷是否找到匹配的帳號,以便後續處理各種不同情況。
代碼如下:
char a[20];char shuRu[20] = {‘\0‘}; //輸入int flag = 0;cout << "請輸入帳號名: ";cin >> shuRu;FILE* fp;fp = fopen("test.txt","rb");while(fscanf(fp,"%s",a) > 0) //遍曆檔案資料{ if (strcmp(shuRu,a) == 0){ cout << "找到匹配帳號" << endl;flag = 1; break; }}if (flag == 0){ cout << "使用者不存在,請註冊!" << endl;
//下一步
}else //下一步;fclose(fp);
代碼中判斷資料相同用strcmp(str1,str2); 如果兩個字元數組儲存的內容相同,則 strcmp(str1,str2)== 0
此時停止搜尋,用break;跳出迴圈。
現在舉例一下:
其中第三行為帳號,第五行為密碼。其他的為姓名,地址,年齡等其他資訊。
運行如下:
第三步:定位檔案密碼資料
如果是用結構體,當檢測到帳號的時候,再用結構體的 “.” 也就是 “點”密碼 來解決。簡單方便。但是用的不是結構體,所以只能用其他方法。隨筆開頭寫了用“排序定位”來做,如何??
從這個檔案資料來看,帳號與密碼分別是第三行, 第五行,中間隔了個第四行。下面另一個帳戶也是同樣的排序。
那麼就定位到第五行,然後再進行 輸入密碼 與 檔案資料的比較。
代碼如下:
if (strcmp(shuRu,a) == 0){
flag = 1;fscanf(fp,"%s",a);fscanf(fp,"%s",a);break;}
這裡面當檢測到帳號的時 flag = 1;表示找到匹配帳號。
然後用了 兩個fscanf(fp,"%s",a); 這不是代碼錯誤,前面提到了fscanf(); 是順序讀入,並且舉了 a,b,c三個字元數組的例子。
這裡再說明一下為何用兩個fscanf();
第一個fscanf();是把帳號下面的第一個資料賦值給了 字元數組a;
第二個fscanf();是把帳號下面的第二個資料賦值給了 字元數組a;
由於密碼資料是帳號資料下面的第二個資料,所以必須用兩個fscanf(); 因為fscanf();為順序讀入,無法進行跳躍。
第四步:驗證密碼
定位了密碼資料,那麼就可以進行密碼驗證了。為了實現當密碼輸入錯誤時,能重新輸入,我們必須把驗證密碼這個環節寫成一個函數,然後自身迴圈調用,類似遞迴的用法。
代碼如下:
void checkKey(char a[20]){char mima[20];cout << "輸入密碼:";cin >> mima;if (strcmp(a, mima) == 0)cout << "登入成功" << endl;else{cout << "密碼輸入錯誤!請重新輸入。" << endl;Sleep(2000);checkKey(a);}return;}
第五步:demo運行
初步完成了這個功能,現在把完整代碼貼出來。
#include <iostream>#include <cstdlib>#include <cstring>#include <windows.h>using namespace std;//檢測密碼void checkKey(char a[20]){char mima[20];cout << "輸入密碼:";cin >> mima;if (strcmp(a, mima) == 0)cout << "登入成功" << endl;else{cout << "密碼輸入錯誤!請重新輸入。" << endl;Sleep(2000);checkKey(a);}return;}int main(){char a[20];char shuRu[20] = {‘\0‘};int flag = 0;cout << "請輸入帳號名: ";cin >> shuRu;FILE* fp;fp = fopen("test.txt","rb");while(fscanf(fp,"%s",a) > 0){if (strcmp(shuRu,a) == 0){flag = 1;fscanf(fp,"%s",a);fscanf(fp,"%s",a);break;}}if (flag == 0)cout << "使用者不存在,請註冊!" << endl;elsecheckKey(a);fclose(fp);return 0;}
我們來運行一下。
首先檔案資料如下:(每個帳戶的第三行為帳號,第五行為密碼)
運行結果:
第六步:bug修複
看上去好像完成了相應預期功能,但是細心觀察,不難發現一個bug。舉例說明一下:
當檔案內容為:
可以看出,第一個帳戶的密碼和第二個帳戶的帳號相同,都是aabbcc,此時程式運行就有錯誤,當搜尋到了aabbcc,程式就把他當成了帳號,於是出錯。
修複bug也很簡單,用一種特殊字元對帳號進行處理。比如在帳號後面追加一個 “@”。
因此,程式要修改兩個地方
1. 帳戶註冊後把資訊寫入檔案時,在帳號後面追加一個 “@”
2. 登入時,當輸入帳號完畢後,也給輸入的帳號後面追加一個“@”
第一個地方不是我們討論的範圍,我們來看看與本篇隨筆有關的要修改的第二個地方:
相關函數: strcat(str1,str2);實現把字元數組 str2 追加到 str1 後面。
代碼如下:
cout << "請輸入帳號名: ";cin >> shuRu;strcat(shuRu,"@");
所以,當你登入時,輸入帳號完成按斷行符號鍵時,程式會自動給你輸入的帳號後面追加一個字元”@“,然後再與檔案資料進行比較。
修改後的完整代碼如下:
#include <iostream>#include <cstdlib>#include <cstring>#include <windows.h>using namespace std;//檢測密碼void checkKey(char a[20]){char mima[20];cout << "輸入密碼:";cin >> mima;if (strcmp(a, mima) == 0)cout << "登入成功" << endl;else{cout << "密碼輸入錯誤!請重新輸入。" << endl;Sleep(2000);checkKey(a);}return;}int main(){char a[20];char shuRu[20] = {‘\0‘};int flag = 0;cout << "請輸入帳號名: ";cin >> shuRu;strcat(shuRu,"@");FILE* fp;fp = fopen("test.txt","rb");while(fscanf(fp,"%s",a) > 0){if (strcmp(shuRu,a) == 0){flag = 1;fscanf(fp,"%s",a);fscanf(fp,"%s",a);break;}}if (flag == 0)cout << "使用者不存在,請註冊!" << endl;elsecheckKey(a);fclose(fp);return 0;}
檔案資料:
運行結果:
密碼匹配成功。
總結:對於沒有用鏈表 + 結構體的來寫帳戶登記的程式,屬於純檔案資訊處理。那麼就只能差強人意的用一些方法來解決。用的是”排序定位“的方法。
代碼是追求高效,簡潔的。一開始沒有用好的方法去解決,雖然也能通向羅馬,從程式的維護和更新的角度來看,是不推薦的。