摘要:那么我們首先來改造儲存空間也就是通訊錄結構體靜態版本人信息存放在數組中統計存放的人數動態版本統計存放的人數有效容量我們將原本的結構體數組改為一個結構體指針,以此來維護用以儲存個人信息的空間。
上一期我們編寫了一個C語言版本的簡易通訊錄,但是我們的之前的通訊錄是沒有記憶功能的,也就是說,一旦關閉了程序我們存儲在里面的數據也就消失了。那么今天我們就來實現一個附帶數據儲存的通訊錄。
在此之前,我們先來了解一下C語言中文件的讀寫函數:
1.fopen及fclose
fopen的作用是打開我們計算機儲存的某個文件,函數返回值是FILE*類型,需要兩個參數:1.文件路徑 2.操作類型。下面我們來演示一下:
int main(){ FILE* pf = fopen("data.txt", "r"); //打開文件 if (pf == NULL) { perror("fopen"); return -1; } //讀文件 //關閉文件 fclose(pf); pf = NULL; return 0;}
我們試著運行這段代碼:
我們設置的錯誤函數提醒我們不存在該文件。
關于參數:
一.文件路徑:我們在使用該函數的時候,打開文件所用的路徑有兩種:1.絕對路徑 2.相對路徑。
下面我們一一演示一下:
1.絕對路徑:
int main(){ FILE* pf = fopen("C://Users//win10//Desktop//data.txt", "r"); //打開文件 if (pf == NULL) { perror("fopen"); return -1; } //讀文件 //關閉文件 fclose(pf); pf = NULL; return 0;}
?要注意使用絕對路徑時,原本"/"處我們應再加上一條“/”,防止其變成轉義字符導致路徑失效。我們運行看看:
現在我們可以看到程序運行成功沒有報錯。
2.相對路徑:相對路徑是指將文件放置在源文件的文件夾內
int main(){ FILE* pf = fopen("data.txt", "r"); //打開文件 if (pf == NULL) { perror("fopen"); return -1; } //讀文件 //關閉文件 fclose(pf); pf = NULL; return 0;}
運行結果與絕對路徑一致。
二.操作符號
下面我們嘗試在文件中寫入一些我們想要的數據。
int main(){ FILE* pf = fopen("data.txt", "w"); //打開文件 if (pf == NULL) { perror("fopen"); return -1; } //寫文件 fputc("h", pf); fputc("e", pf); fputc("l", pf); fputc("l", pf); fputc("o", pf); //關閉文件 fclose(pf); pf = NULL; return 0;}
?可以注意到,我們使用的是相對路徑,打開后的操作為寫。寫入數據的函數為fputs,該函數一次只能輸入一個字符,第一個參數為想要輸入的字符,第二個參數為先前打開文件返回的地址。下面我們來看一下運行結果:
看起來好像什么都沒有發生,實際上:
打開文檔的時候我們發現,我們想要的東西已經寫入到了文檔中。
int main(){ FILE* pf = fopen("data.txt", "w"); //打開文件 if (pf == NULL) { perror("fopen"); return -1; } //讀文件 /*fputc("h", pf); fputc("e", pf); fputc("l", pf); fputc("l", pf); fputc("o", pf);*/ fputs("hello", pf); //關閉文件 fclose(pf); pf = NULL; return 0;}
?另外我們也可以使用fputs函數,一次可以輸入一串字符串(如上圖所示)。
實現了寫入的方法后,我們不禁會去思考,既然可以把數據寫入一個我們準備好的文檔,那么我們是否也可以從那個文檔中拿出我們想要的數據呢?答案是肯定的。下面我們就來讀取我們剛剛寫入到文檔中的"hello"。
int main(){ FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } int ch = getc(pf); printf("%c", ch); ch = getc(pf); printf("%c", ch); ch = getc(pf); printf("%c", ch); ch = getc(pf); printf("%c", ch); ch = getc(pf); printf("%c", ch); fclose(pf); pf = NULL; return 0;}
我們首先使用fopen函數打開文件,然后使用命令"r",也就是讀取命令。讀取字符的函數我們使用的是getc函數。該函數每次可以讀取一個字符,每讀取一次就會向后跳動一個字符,因此我們使用五次就可以讀取我們先前儲存在文檔中的單詞了,沒讀取一次我們就將它打在屏幕上:
我們也可以通過fgets函數一次性讀取一行的數據:
int main(){ char arr[20] = { 0 }; FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } char* ch = fgets(arr,6,pf); printf("%s",arr); fclose(pf); pf = NULL; return 0;}
?它的三個參數分別是:1.讀取后儲存的位置 2.讀取的字符(n-1個,因此在設置參數時應比想要讀取的字符要多一個)3.源數據地址。
我們可以看到這個函數也很好的實現了一樣的功能。
下面我們來介紹一下今天用于改造通訊錄的兩個函數:fread,fwrite?
我們直接來看一下fwrite的用法:
struct S { int n; double x; char name[10];};int main(){ struct S a = { 10,3.14,"張三" }; FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror(fopen); return -1; } fwrite(&a, sizeof(a), 1, pf); fclose(pf); pf = NULL; return 0;}
該函數一共需要四個參數:1.數據地址 2.數據大小 3.每次寫入的個數 4.寫到什么地方
程序運行結果:
該函數的寫入方式為二進制,所以我們未必可以直接讀。
下面我們用fread來讀取一下:?
struct S { int n; double x; char name[10];};int main(){ //struct S a = { 10,3.14,"張三" }; struct S s = { 0 }; FILE* pf = fopen("data.txt", "wb"); if (pf == NULL) { perror(fopen); return -1; } //fwrite(&a, sizeof(a), 1, pf); fread(&s, sizeof(struct S), 1, pf); printf("%d %lf %s",s.n, s.x, s.name); fclose(pf); pf = NULL; return 0;}
運行結果:
知道這些函數的作用和用法之后我們就可以開始對通訊錄進行改造了!
首先,我們先前的通訊錄的儲存版本是1000個人的數據,但是在實際生活當中,因為每個人的情況不一樣我們是用不了那么大的容量的。那么我們首先來實現通訊錄的動態版本!
首先我們要改造的是存放數據的空間,原先我們是創造了一個結構體,在結構體內定義了一個結構體數組,數組元素個數是1000,以及一個用來記錄儲存人數的整形。
那么如果我們想要一個可有隨儲存人數變化而變化的空間,那么我們就需要以下幾步:1.開辟初始空間 2.檢查空間是否充足 3.充足(存入數據);不充足(開辟新空間)4.存入新數據。
那么我們首先來改造儲存空間也就是通訊錄結構體:
靜態版本//struct contact//{// struct peoinfo data [max];//100人信息存放在數組中// int sz;//統計存放的人數//};動態版本struct contact{ struct peoinfo* data; int sz;//統計存放的人數 int capacity;//有效容量};
我們將原本的結構體數組改為一個結構體指針,以此來維護用以儲存個人信息的空間。同時我們增加了一個整形變量capacity,它代表的是有效的容量,在后期增加人數的時候,當有效容量等于儲存人數的時候我們就需要開辟新的空間用以儲存新的人員信息。
當儲存空間開辟方式發生變化,那么初始化函數也應當隨之變化:
///靜態版本//void initcontact(struct contact* con)//{// con->sz = 0;// memset(con->date,0,sizeof(struct peoinfo));// //memset(con->date, 0, sizeof(con->date));//}//動態版本void initcontact(struct contact* con){ con->sz = 0; con->data = (struct peoinfo* )malloc(def * sizeof(struct peoinfo ));//開辟空間 con->capacity = def;}
原本靜態的初始化函數只是將sz,儲存信息都初始化為0,在動態版本中,我們設置初始化容量為3(原本是1000),同時使用malloc函數將空間開辟為能夠儲存三個人信息的大小同時將這塊空間的指針轉化為結構體類型傳遞給我們剛剛設置的data指針。
到這里,之前我們提到的三步我們完成了第一步,現在我們要開始第二步:2.檢查空間是否充足 3.充足(存入數據);不充足(開辟新空間)
原本的靜態增加聯系人的函數我們只有判斷空間是否充足+新增聯系人兩步。而現在我們要加入第三步:空間不足時開辟新的空間。
void checksize(struct contact* con) { if (con->sz == con->capacity) { struct peoinfo* ptr = (struct peoinfo*)realloc(con->data, (con->capacity + 2) * sizeof(struct peoinfo)); if (ptr != NULL) { con->data = ptr; printf("增容成功!/n"); con->capacity += 2; } else { exit(1); } }//檢查容量 }
首先我們封裝一個判斷函數,一進入該函數首先檢查sz是否等于有效容量,如果容量不足我們就使用realloc函數開辟新的空間;需要注意的是,如果函數增容成功會返回一個非空指針,因此我們可以以此來判斷是否增容成功,如果失敗率則異常退出。
下面是完整的增加函數:
void modifycontact(struct contact* con) { int as = searcontact(con); if (as >= 0) { printf("請輸入聯系人姓名:"); scanf("%s", con->data[con->sz].name); printf("請輸入聯系人年齡:"); scanf("%d", &con->data[con->sz].age); printf("請輸入聯系人性別:"); scanf("%s", con->data[con->sz].sex); printf("請輸入聯系人電話:"); scanf("%s", con->data[con->sz].tele); printf("請輸入聯系人住址:"); scanf("%s", con->data[con->sz].adr); printf("修改成功!/n"); con->sz++;//儲存人數加一 } }
到這里我們的通訊錄的空間就可以隨著儲存人數的增加而增加了,但是這不意味著就結束了。那些我們開辟用來存放數據的空間在程序結束以后應當重新釋放掉,所以我們還有最后一步:銷毀通訊錄。
void destory(struct contact* con) { con->sz = 0; con->capacity = 0; free(con->data); con->data = NULL; }
我們用這個函數將人數和有效容量定義為0,同時釋放掉我們之前開辟的空間放置內存泄漏。最后將data置為空指針。
到這里我們就將之前的通訊錄成功改造成了動態內存的版本,但是這樣說的通訊錄依舊是不完美的,我們每次進入通訊錄都需要重新錄入信息才能使用,如果是這樣這通訊錄也就失去了它的價值,那么我們接下來將通訊錄再次改造使他成為具有記憶功能。
實現記憶功能我們分為下面兩大步:1.將之前輸入的數據輸入到我們實現準備好的文檔 2.啟動通訊錄進行初始化后讀取文檔內的信息
下面我們實現第一步:將數據輸入到文檔中。
我們事先創建好一個TXT文檔在源文件的文件夾當中,我們在結束時中加入寫入函數:
case EXIT: //儲存通訊錄數據 savecontact(&con); destory(&con); printf("退出通訊錄!/n"); break;
下面是儲存函數實現:
void savecontact(struct contact* con) { //打開文件 FILE* pf = fopen("contact.txt", "wb"); if (pf == NULL) { perror("savecontact::fopen"); return;//結束函數 } //寫入數據 int i = 0; for (i = 0; i < con->sz; i++) { fwrite(con->data+i, sizeof(struct contact), 1, pf); } //關閉文件 fclose(pf); pf = NULL; }
通訊錄有幾個人的信息我們就寫入幾次,該功能的實現主要依靠fwirte函數,該函數的使用我們已經在開頭介紹過就不在詳述了。
第二步:初始化讀取信息
void initcontact(struct contact* con){ con->sz = 0; con->data = (struct peoinfo* )malloc(def * sizeof(struct peoinfo ));//開辟空間 if (con->data == NULL) { perror(con->data); return; } con->capacity = def; //加載數據 loadcontact(con);}
我們在最后加入加載函數,下面是加載函數的代碼:
void loadcontact(struct contact* con) { int i = 0; FILE* pf = fopen("contact.txt", "rb"); if (pf == NULL) { printf("加載失敗/n"); return; } struct peoinfo tmp = { 0 }; while (fread(&tmp, sizeof(struct peoinfo), 1, pf)) { checksize(con); con->data[con->sz] = tmp; con->sz++; printf("加載成功!/n"); } //關閉文件 fclose(pf); pf = NULL; }
需要注意的是,我們每讀取一個數據前都要檢查一下容量是否充足,所以我們調用了之前寫的判斷函數。每讀取一次,我們就將信息儲存到我們用來保存信息的結構體中,同時有效容量和人數都++一次。最后加載完成關閉文件。
到這里,通訊錄就改造完成了哦。喜歡的話就一鍵三連吧!謝謝大家!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/121534.html
摘要:二靜態通訊錄接口函數實現文件名功能通訊錄函數接口的實現宏定義,頭文件,接口函數的聲明函數接口測試靜態通訊錄的基本結構通訊錄是一個結構體。 前言:之前,博主已經寫過兩個有意思的小項目:三子棋和掃雷,接下來,博主繼續更新一個小項目-通訊錄,包括3種版本,靜態版,動態版,文件保存版。接下來,我們先...
摘要:前言這里筑夢師是一名正在努力學習的開發工程師目前致力于全棧方向的學習希望可以和大家一起交流技術共同進步用簡書記錄下自己的學習歷程個人學習方法分享本文目錄更新說明目錄學習方法學習態度全棧開發學習路線很長知識拓展很長在這里收取很多人的建議以后決 前言 這里筑夢師,是一名正在努力學習的iOS開發工程師,目前致力于全棧方向的學習,希望可以和大家一起交流技術,共同進步,用簡書記錄下自己的學習歷程...
閱讀 2508·2023-04-26 02:47
閱讀 2999·2023-04-26 00:42
閱讀 865·2021-10-12 10:12
閱讀 1372·2021-09-29 09:35
閱讀 1688·2021-09-26 09:55
閱讀 478·2019-08-30 14:00
閱讀 1532·2019-08-29 12:57
閱讀 2350·2019-08-28 18:00