基于C語言打造高效通訊錄的示例代碼
本篇博客會講解如何使用C語言實現一個通訊錄。實現通訊錄的過程中,會大量用到C語言的知識點,包括但不限于:函數、自定義類型、指針、動態(tài)內存管理、文件操作,這些知識點在我的其他博客中都有講解過,歡迎大家閱讀,這里就不進行系統(tǒng)的復習了。
先來梳理下需求:
1.通訊錄能夠存儲的聯系人的信息有:姓名、年齡、性別、電話、住址。
2.這個通訊錄不能是“靜態(tài)的”,而應該是“動態(tài)的”,也就是說,需要用到動態(tài)內存管理的知識。這是因為,靜態(tài)的通訊錄的容量是固定的,空間太大可能浪費,太小了又不夠存。
3.由于當程序開始運行后,通訊錄的數據是存儲在內存中的,一旦程序運行結束,執(zhí)行完main函數的return 0;后,空間就被操作系統(tǒng)回收了,相當于數據就丟了。為了能夠實現“永久保存”的效果,我們要在程序退出前,把數據保存到文件中,這又涉及到文件操作的相關知識點。
4.類似順序表這種數據結構的基本操作,通訊錄要能做到:增刪查改+排序+打印,即增加聯系人、刪除聯系人、查找聯系人、修改聯系人、排序聯系人、打印聯系人等等。
下面我們開始吧!
準備工作
以下是菜單里的一些選項,聲明成枚舉類型是比較合適的。
// 菜單里的不同選項
enum Option
{
EXIT, // 退出
ADD, // 增加聯系人
DEL, // 刪除聯系人
SEARCH, // 查找聯系人
MODIFY, // 修改聯系人
SHOW, // 顯示聯系人
SORT // 排序
};由于聯系人的姓名、性別、電話和住址都是字符串,要存儲在字符數組中,最好先聲明它們的容量。
// 各信息的存儲容量 #define MAX_NAME 20 // 名字 #define MAX_SEX 5 // 性別 #define MAX_TELE 12 // 電話 #define MAX_ADDR 30 // 住址
我們后面在進行動態(tài)內存管理時,需要知道初始的容量和每次擴容的容量,也聲明一下:
// 動態(tài)內存默認存儲的數據 #define DEFAULT_SZ 3 // 若不夠存,每次擴容的數量 #define INC_SZ 2
再聲明一個結構體,表示一個人的信息,包括姓名、年齡、性別、電話、住址。
// 表示一個人的信息
typedef struct PeoInfo
{
char name[MAX_NAME]; // 姓名
int age; // 年齡
char sex[MAX_SEX]; // 性別
char tele[MAX_TELE]; // 電話
char addr[MAX_ADDR]; // 住址
}PeoInfo;類似數據結構中的“順序表”的結構,定義一個結構體,用于存儲通訊錄中的信息,包括一個動態(tài)開辟的數組,數組中有效數據的個數,以及數組當前動態(tài)開辟的容量。
// 通訊錄
typedef struct Contact
{
PeoInfo* data; // data指向了存放數據的空間
int sz; // 記錄通訊錄中的有效信息個數
int capacity; // 通訊錄當前的容量
}Contact;下面我們開始實現程序的主體邏輯。先從主函數寫起,把主要的功能都封裝成函數:
// 打印菜單
void menu()
{
printf("************************************\n");
printf("***** 1. add 2. del ***\n");
printf("***** 3. search 4. modify ***\n");
printf("***** 5. show 6. sort ***\n");
printf("***** 0. exit ***\n");
printf("************************************\n");
}
int main()
{
int input = 0; // 存儲用戶輸入的數據
Contact con; // 通訊錄
// 初始化通訊錄
// 加載文件的信息到通訊錄中
InitContact(&con);
do
{
menu(); // 菜單
printf("請選擇:>");
scanf("%d", &input);
switch (input)
{
case ADD: // 添加聯系人
AddContact(&con);
break;
case DEL: // 刪除聯系人
DelContact(&con);
break;
case SEARCH: // 查找指定聯系人
SearchContact(&con);
break;
case MODIFY: // 修改指定聯系人的信息
ModifyContact(&con);
break;
case SHOW: // 展示聯系人信息
ShowContact(&con);
break;
case SORT: // 排序
SortContact(&con);
break;
case EXIT: // 退出通訊錄
// 保存通訊錄到文件中
SaveContact(&con);
// 銷毀通訊錄
DestroyConact(&con);
printf("退出通訊錄\n");
break;
default:
printf("選擇錯誤\n");
break;
}
} while (input);
return 0;
}初始化通訊錄
先定義一個函數InitContact,它的作用是初始化通訊錄。函數的參數是一個指向Contact結構體的指針。函數的具體實現如下:
- 函數的第一行使用assert宏檢查指針是否有效,如果無效則程序會中止運行。
- 接下來,函數使用malloc函數開辟了一塊內存空間,用于存儲PeoInfo結構體數組。這個數組的大小是DEFAULT_SZ,即默認容量。如果開辟空間失敗,則會輸出錯誤信息并返回。
- 如果開辟空間成功,則將通訊錄的大小sz和容量capacity都設置為DEFAULT_SZ,并調用LoadContact函數將文件中的信息加載到通訊錄中。
void InitContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 先開辟默認的容量
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
// 檢查開辟空間是否成功
if (pc->data == NULL)
{
// 開辟空間失敗
printf("通訊錄初始化失敗:%s\n", strerror(errno));
return;
}
// 開辟空間成功
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
//加載文件的信息到通訊錄
LoadContact(pc);
}從文件中加載信息
再定義一個函數LoadContact,用于從文件中讀取聯系人信息并存儲到內存中。函數的參數是一個指向Contact結構體的指針,表示要將讀取到的聯系人信息存儲到哪個數據結構中。函數的具體實現如下:
- 首先使用assert函數檢查傳入的指針是否有效,如果無效則直接返回。
- 然后使用fopen函數打開名為"contact.dat"的二進制文件,如果打開失敗則說明可能是第一次運行通訊錄,沒有數據文件,直接返回。
- 接著使用一個while循環(huán),每次讀取一個PeoInfo結構體大小的數據,即一個聯系人的信息,存儲到臨時變量tmp中。
- 調用CheckCapacity函數檢查當前動態(tài)數組的容量是否足夠存儲讀取到的聯系人信息,如果不夠則進行擴容。
- 將讀取到的聯系人信息存儲到動態(tài)數組中,即將tmp變量中的數據存儲到data數組的末尾,并將sz變量加一。
- 循環(huán)結束后,關閉文件并將文件指針置為 NULL。
void LoadContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 打開文件
FILE* pf = fopen("contact.dat", "rb");
// 檢查打開文件是否成功
if (pf == NULL)
{
// 打開文件失敗,可能是第一次運行通訊錄,并沒有數據文件
//perror("LoadContact::fopen");
return;
}
// 讀文件
PeoInfo tmp = { 0 }; // 存儲讀取到的數據
// 每次讀一個數據
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
// 檢查容量,不夠的話要擴容
CheckCapacity(pc);
// 存儲從文件讀取到的數據
pc->data[pc->sz] = tmp;
pc->sz++;
}
// 關閉文件
fclose(pf);
pf = NULL;
}檢查容量
再定義一個函數CheckCapacity,用于檢查并擴容動態(tài)數組。函數的參數是一個指向Contact結構體的指針。函數的具體實現如下:
- 函數首先使用assert宏檢查傳入的指針是否有效。然后,它檢查當前數組是否需要擴容。如果數組的大小已經等于容量,就需要擴容。
- 在需要擴容的情況下,函數使用realloc函數重新分配內存。realloc函數會嘗試將原來分配的內存塊擴大到指定的大小。如果擴容成功,realloc函數會返回一個指向新內存塊的指針,否則返回NULL。
- 如果realloc函數返回NULL,說明擴容失敗,函數會輸出錯誤信息并返回0。如果realloc函數返回非空指針,說明擴容成功,函數會更新數組的起始位置和容量,并輸出擴容成功的信息。最后,函數返回1,表示擴容成功或者不需要擴容。
// 擴容失敗,返回0
// 擴容成功,不需要擴容,返回1
static int CheckCapacity(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 檢查是否需要擴容
if (pc->sz == pc->capacity)
{
// 需要擴容
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
// 檢查是否擴容成功
if (ptr == NULL)
{
// 擴容失敗
printf("CheckCapacity:%s\n", strerror(errno));
return 0;
}
else
{
// 擴容成功
// 更新數組的起始位置
pc->data = ptr;
// 更新容量
pc->capacity += INC_SZ;
printf("增容成功,當前容量:%d\n", pc->capacity);
}
}
return 1;
}銷毀通訊錄
再定義一個函數DestroyContact,用來銷毀通訊錄。函數的參數是一個指向Contact結構體的指針。函數的具體實現如下:
- 先使用assert宏檢查指針的有效性。
- 釋放動態(tài)數組占用的內存空間。
- 把結構體的變量置空。
void DestroyConact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 釋放內存空間
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
printf("釋放內存.....\n");
}添加聯系人
接著定義一個函數AddContact,參數仍然是一個指向Contact結構體的指針,實現向通訊錄中添加聯系人的功能。具體實現如下:
- 首先檢查傳入的指針是否有效,如果無效則使用assert宏觸發(fā)斷言,程序終止。
- 調用CheckCapacity函數檢查通訊錄是否需要擴容,如果需要則進行擴容操作。如果擴容失敗,則輸出提示信息并返回。
- 如果擴容成功,則提示用戶輸入聯系人信息,包括名字、年齡、性別、電話和地址。這些信息將被存儲在通訊錄的data數組中,下標為 sz。
- 最后更新有效數據個數sz,并輸出添加成功的提示信息。
void AddContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 擴容,同時檢查是否成功
if (0 == CheckCapacity(pc))
{
// 擴容失敗
printf("空間不夠,擴容失敗\n");
return;
}
else
{
// 輸入聯系人信息
printf("請輸入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("請輸入年齡:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("請輸入性別:>");
scanf("%s", pc->data[pc->sz].sex);
printf("請輸入電話:>");
scanf("%s", pc->data[pc->sz].tele);
printf("請輸入地址:>");
scanf("%s", pc->data[pc->sz].addr);
// 更新有效數據個數
pc->sz++;
printf("添加成功\n");
}
}打印數據
接下來定義一個函數 ShowContact,用于打印聯系人信息。函數接受一個指向Contact結構體的指針pc。具體實現如下:
- 函數首先使用assert宏檢查指針pc是否有效,如果無效則程序會崩潰并輸出錯誤信息。
- 接下來,函數使用printf函數打印聯系人信息。首先打印表頭,包括姓名、年齡、性別、電話和地址。然后使用循環(huán)遍歷pc中的每個聯系人,打印其姓名、年齡、性別、電話和地址。
- 在打印時使用格式化字符串,其中 %s 表示字符串,%-10s 表示左對齊并占用 10 個字符的字符串,%-4d 表示左對齊并占用 4 個字符的整數,%-5s 表示左對齊并占用 5 個字符的字符串,%-12s 表示左對齊并占用 12 個字符的字符串,%-30s 表示左對齊并占用 30 個字符的字符串。
void ShowContact(const Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 打印效果
// 姓名 年齡 性別 電話 地址
// zhangsan 20 男 123456 北京
//
// 打印標題
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年齡", "性別", "電話", "地址");
// 打印數據
for (int i = 0; i < pc->sz; ++i)
{
printf("%-10s %-4d %-5s %-12s %-30s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
}刪除聯系人
接下來實現通訊錄的刪除聯系人功能。具體實現如下:
FindByName函數:根據指定名字,在通訊錄中查找聯系人信息。函數參數為指向Contact結構體的指針和要查找的名字。函數返回值為查找到的聯系人在通訊錄中的下標,如果沒找到則返回-1。
DelContact函數:刪除通訊錄中指定聯系人。函數參數為指向Contact結構體的指針。函數實現如下:
- 檢查指針有效性,如果為空則直接返回。
- 檢查通訊錄中是否還有數據,如果沒有則輸出提示信息并返回。
- 獲取用戶輸入的要刪除的聯系人名字。
- 調用FindByName函數查找要刪除的聯系人在通訊錄中的下標。
- 如果沒找到,則輸出提示信息并返回。
- 如果找到了,則使用memmove函數將該聯系人后面的所有聯系人向前移動一個位置,覆蓋掉要刪除的聯系人。
- 更新通訊錄中的有效數據個數。
- 輸出刪除成功的提示信息。
// 根據指定名字,查找聯系人信息
static int FindByName(const Contact* pc, char name[])
{
// 檢查指針有效性
assert(pc);
assert(name);
// 遍歷數組
for (int i = 0; i < pc->sz; ++i)
{
// 檢查名字是否匹配
if (0 == strcmp(pc->data[i].name, name))
{
// 找到了
return i;
}
}
// 沒找到
return -1;
}
void DelContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 檢查是否還有數據
if (pc->sz == 0)
{
// 通訊錄已空
printf("通訊錄為空,無法刪除\n");
return;
}
char name[MAX_NAME] = { 0 }; // 存儲用戶輸入的數據
// 用戶輸入信息
printf("輸入要刪除人的名字:>");
scanf("%s", name);
// 找到要刪除的人的下標
int pos = FindByName(pc, name);
// 檢查是否找到
if (pos == -1)
{
// 沒找到
printf("要刪除的人不存在\n");
return;
}
// 刪除pos位置上的數據
// 挪動pos后面的數據,覆蓋pos
memmove(pc->data + pos, pc->data + pos + 1, sizeof(PeoInfo) * (pc->sz - pos - 1));
// 更新有效數據個數
pc->sz--;
printf("刪除成功\n");
}查找聯系人
接著實現在通訊錄中根據姓名查找聯系人的功能。具體實現如下:
- 函數名為SearchContact,接受一個指向Contact結構體的指針pc作為參數。
- 第一行代碼使用assert宏檢查指針pc是否有效,如果pc為 NULL,則程序會崩潰并輸出錯誤信息。
- 定義一個char類型的數組name,長度為MAX_NAME,用于存儲用戶輸入的要查找的人的名字。
- 使用printf函數輸出提示信息,讓用戶輸入要查找的人的名字。
- 使用scanf函數讀取用戶輸入的名字,存儲到name數組中。
- 調用FindByName函數,在通訊錄中查找名字為name的聯系人,返回值為該聯系人在通訊錄中的位置,如果沒找到則返回-1。
- 判斷FindByName函數的返回值,如果為-1,則說明沒有找到要查找的聯系人,使用printf函數輸出提示信息,并直接返回。
- 如果FindByName函數的返回值不為-1,則說明找到了要查找的聯系人,使用printf函數輸出通訊錄的標題行,包括姓名、年齡、性別、電話、地址等信息。
- 使用printf函數輸出找到的聯系人的具體信息,包括姓名、年齡、性別、電話、地址等信息,這些信息都存儲在Contact結構體中的data數組中,通過pc指針訪問。pos變量表示要查找的聯系人在data數組中的位置。注意,這里使用了%-10s、%-4d等格式控制符,表示輸出字符串時左對齊,并且占用固定的寬度,方便對齊。
void SearchContact(const Contact* pc)
{
// 檢查指針有效性
assert(pc);
char name[MAX_NAME] = { 0 }; // 存儲用戶輸入的信息
// 用戶輸入數據
printf("請輸入要查找人的名字:>");
scanf("%s", name);
// 查找
int pos = FindByName(pc, name);
// 檢查是否找到
if (pos == -1)
{
// 沒找到
printf("要查找的人不存在\n");
return;
}
// 打印標題行
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年齡", "性別", "電話", "地址");
// 打印數據
printf("%-10s %-4d %-5s %-12s %-30s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}修改聯系人
再下來實現一個函數,函數的參數仍然是一個指向通訊錄結構體類型Contact的指針,用于修改通訊錄中的聯系人信息。具體實現如下:
- 使用assert宏函數檢查指針有效性,如果指針為空,則程序會終止。
- 定義一個char類型數組name,用于存儲用戶輸入的聯系人名字。
- 使用printf函數提示用戶輸入要修改的聯系人名字,并使用scanf函數讀取用戶輸入的名字。
- 調用FindByName函數查找通訊錄中是否存在該聯系人,如果不存在則輸出提示信息并返回。
- 如果存在該聯系人,則使用scanf函數分別讀取用戶輸入的聯系人信息,包括名字、年齡、性別、電話和地址,并將這些信息存儲到通訊錄結構體中對應的位置。
- 最后使用printf函數輸出修改成功的提示信息。
void ModifyContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
char name[MAX_NAME] = { 0 }; // 存儲用戶輸入的信息
// 用戶輸入數據
printf("請輸入要修改人的名字:>");
scanf("%s", name);
// 查找
int pos = FindByName(pc, name);
// 檢查是否找到
if (pos == -1)
{
// 沒找到
printf("要修改的人不存在\n");
return;
}
// 修改
// 用戶輸入信息
printf("請輸入名字:>");
scanf("%s", pc->data[pos].name);
printf("請輸入年齡:>");
scanf("%d", &(pc->data[pos].age));
printf("請輸入性別:>");
scanf("%s", pc->data[pos].sex);
printf("請輸入電話:>");
scanf("%s", pc->data[pos].tele);
printf("請輸入地址:>");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}排序通訊錄
接下來實現排序功能。具體實現如下:
- 定義一個比較函數CmpByName,用于按照名字來排序聯系人信息。該函數的參數為兩個指向聯系人信息結構體的指針p1和p2,返回值為兩個名字字符串的比較結果。
- 在CmpByName函數中,首先使用assert宏檢查指針p1和p2的有效性,確保程序不會因為無效指針而崩潰。然后使用strcmp函數比較兩個聯系人信息結構體中的名字字符串大小,返回比較結果。
- 定義一個排序函數SortContact,用于對聯系人信息進行排序。該函數的參數為一個指向聯系人管理系統(tǒng)結構體的指針pc。
- 在SortContact函數中,首先使用assert宏檢查指針pc的有效性,確保程序不會因為無效指針而崩潰。然后調用qsort函數對聯系人信息進行排序,其中pc->data表示聯系人信息數組的首地址,pc->sz表示聯系人信息數組的大小,sizeof(PeoInfo)表示每個聯系人信息結構體的大小,CmpByName表示排序函數。
- 最后輸出排序成功的提示信息。
// 按照名字來排序
int CmpByName(const void* p1, const void* p2)
{
// 檢查指針有效性
assert(p1 && p2);
// 比較名字字符串大小
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
void SortContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 根據名字來排序
qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);
printf("排序成功\n");
}保存通訊錄
最后實現將聯系人信息保存到文件中的功能。具體實現如下:
- 函數定義:函數名為SaveContact,參數為一個指向Contact結構體的指針pc。
- 檢查指針有效性:使用assert宏函數檢查指針pc是否為NULL,如果是NULL則程序會直接終止。
- 打開文件:使用fopen函數打開名為"contact.dat"的文件,以二進制寫入模式(“wb”)打開。返回值為一個指向FILE結構體的指針pf。
- 檢查是否打開成功:使用if語句判斷指針pf是否為NULL,如果是NULL則說明打開文件失敗,使用perror函數輸出錯誤信息并返回。
- 寫數據:使用for循環(huán)遍歷pc指向的Contact結構體中的所有聯系人信息,使用fwrite函數將每個聯系人信息寫入文件中。其中,第一個參數為指向聯系人信息的指針,第二個參數為每個聯系人信息的大小,第三個參數為寫入的數量,第四個參數為指向文件的指針pf。
- 關閉文件:使用fclose函數關閉文件,釋放文件指針pf所占用的資源。將pf賦值為NULL,防止出現野指針。
- 輸出保存成功信息:使用printf函數輸出保存成功的信息。
void SaveContact(Contact* pc)
{
// 檢查指針有效性
assert(pc);
// 打開文件
FILE* pf = fopen("contact.dat", "wb");
// 檢查是否打開成功
if (pf == NULL)
{
// 打開文件失敗
perror("SaveContact::fopen");
return;
}
// 寫數據,一次寫一個
for (int i = 0; i < pc->sz; ++i)
{
fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
}
//關閉文件
fclose(pf);
pf = NULL;
printf("保存成功...\n");
}總結
- 動態(tài)內存管理一定要熟練掌握,包括使用malloc開辟空間,使用realloc擴容,使用free函數釋放空間等。
- 常見的文件操作函數得會用,包括fopen,fclose以及文件的順序讀寫和隨機讀寫操作等。
- 順序表的增刪查改、排序打印等常規(guī)操作一定要掌握,通訊錄的本質就是順序表。
以上就是基于C語言打造高效通訊錄的示例代碼的詳細內容,更多關于C語言通訊錄的資料請關注腳本之家其它相關文章!
相關文章
用C/C++代碼檢測ip能否ping通(配合awk和system可以做到批量檢測)
今天小編就為大家分享一篇關于用C/C++代碼檢測ip能否ping通(配合awk和system可以做到批量檢測),小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04

