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