?前言
前面詳細介紹了靜態版通訊錄【C語言】靜態通訊錄 -- 詳解_炫酷的伊莉娜的博客-CSDN博客,但是靜態版通訊錄的空間是無法被改變的,而且空間利用率也不高。為了解決靜態通訊錄這一缺點,這時就要有一個能夠隨著存入聯系人數量的增加而增大空間的通訊錄。接下來我們將探討動態通訊錄,在能夠實現靜態版通訊錄的基礎上,能夠靈活調整通訊錄聯系人數量并實現其基本功能。
以下內容是在靜態通訊錄的基礎上進行修改,大致思路不變。
一、實現目標
1、功能
- 保存 1000 個聯系人的信息
- 添加聯系人
- 刪除聯系人
- 修改聯系人
- 查找聯系人
- 排序
2、個人信息(結構體)
- 名字
- 年齡
- 性別
- 電話
- 地址
動態版通訊錄的要求:
- 默認能夠存放 3 個聯系人信息。
- 不夠的話,每次增加 2 個聯系人信息。
二、創建文件
- test.c(專門測試通訊錄的功能)
- contact.c(接口的實現)
- contact.h(接口的聲明)
三、初始頁面的實現
1、實現主菜單頁面
// test.c
(1)menu()
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");
}
效果圖如下:
(2)main()
int main()
{int input = 0;// 創建通訊錄struct Contact con; // con就是通訊錄,里邊包含:data指針和size,capacity// 初始化通訊錄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:DestoryContact(&con); // 銷毀通訊錄-釋放動態開辟的內存printf("退出通訊錄!\n");break;default:printf("選擇錯誤!\n");break;}} while (input);return 0;
}
main() 函數代碼中與之前的區別主要就是要在初始化的時候開辟動態內存空間,在輸入 0 退出程序時,會將通訊錄刪除。其實仔細想想,確實需要這一步,因為在整個程序都沒有結束時,動態開辟內存也并沒有結束,所以只能是在整個程序結束的時候才能將動態開辟的內存釋放掉。?
2、在contact.h 中引用所需要的文件和必要的定義
首先我們要 聲明一個結構體,其中 包含了我們想要保存的信息(可以自己設置):
typedef struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
}PeoInfo;
?定義結構體(與靜態通訊錄不同!):
????????動態通訊錄在定義結構體時,應該增加一個元素(capacity)來記錄當前通訊錄的最大容量,當達到這個最大容量的時候,就對動態通訊錄進行擴容,增加內存空間,這樣就能夠很好地實現動態通訊錄。?
????????靜態通訊錄與動態通訊錄最大的不同就在這,靜態版是直接使用一個開辟好的數組,而動態版是使用一個指針變量,用于指向使用動態內存申請函數 malloc 所開辟的空間,然后使用開辟的這塊空間對聯系人信息進行儲存,最大的優點是可以使用 realloc 函數對其進行擴容。這樣就解決了靜態通訊錄在空間上的不足。
// 動態通訊錄類型
typedef struct Contact
{struct PeoInfo *data; // 存放數據int size; // 記錄當前已經有的元素個數int capacity; // 記錄當前通訊錄的最大容量
}Contact;// 靜態通訊錄類型
struct Contact
{struct PeoInfo data[MAX];// 可以存放1000個人的信息int size;// 記錄通訊錄中已經保存的信息個數
};
整體邏輯:
// contact.h
#pragma once // 避免頭文件被重復引用// #define MAX 1000#define DEFAULT_SZ 3 // 通訊錄初始狀態的容量大小#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30#include <stdio.h>
#include <string.h> // memset strcmp
#include <stdlib.h> // qsort malloc realloc
#include <assert.h> // assertenum Option
{EXIT,//0ADD,//1DEL,//2SEARCH,//3MODIFY,//4SHOW,//5SORT//6
};struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
};// 動態通訊錄類型
typedef struct Contact
{struct PeoInfo *data; // 存放數據int size; // 記錄當前已經有的元素個數int capacity; // 記錄當前通訊錄的最大容量
}Contact;// 聲明函數
// 初始化通訊錄
void InitContact(struct Contact* ps);
// 增加聯系人
void AddContact(struct Contact* ps);
// 展示通訊錄信息
void ShowContact(const struct Contact* ps);
// 刪除指定聯系人信息
void DelContact(struct Contact* ps);
// 查找指定聯系人信息
void SearchContact(const struct Contact* ps);
// 修改指定聯系人信息
void ModifyContact(struct Contact* ps);
// 排序通訊錄內容(按姓名排)
void SortContact(struct Contact* ps);
// 銷毀通訊錄
void DestoryContact(Contact* ps);
四、在 contact.c 上實現各個接口函數
1、初始化通訊錄
malloc 函數在這里并沒有把其數據初始化為 0 ,這里可以使用?memset?函數初始化存放聯系人信息的結構體數組 data。
對于動態通訊錄的初始化,就是申請一塊初始的空間,用來存放信息,如果滿了在在此基礎上進行擴容。
// 初始化通訊錄
void InitContact(struct Contact* ps)
{assert(ps);ps->size = 0;ps->capacity = DEFAULT_SZ;ps->data = (struct PeoInfo*)malloc(ps->capacity * sizeof(struct PeoInfo));if (ps->data == NULL){perror("InitContact::malloc");return;}memset(ps->data, 0, ps->capacity * sizeof(struct PeoInfo));
}
2、添加聯系人
先判斷通訊錄人數是否已滿,滿了需要擴容,對已經申請過的空間進行擴容,可以使用庫函數 realloc 實現。滿了一次擴容 2 聯系人信息的空間。
// 檢測當前通訊錄的容量
void CheckCapacity(struct Contact* ps)
{if (ps->size == ps->capacity){//增容struct PeoInfo* ptr = realloc(ps->data, (ps->capacity + 2) * sizeof(PeoInfo));if (ptr != NULL){ps->data = ptr;ps->capacity += 2;printf("增容成功!\n");}else{printf("增容失敗!\n");}}
}// 添加聯系人
void AddContact(struct Contact* ps)
{assert(ps);// 檢測當前通訊錄的容量// 1、如果滿了就增加空間// 2、如果沒滿無需任何操作CheckCapacity(ps);// 增加數據printf("請輸入名字:>");scanf("%s", ps->data[ps->size].name);printf("請輸入年齡:>");scanf("%d", &(ps->data[ps->size].age)); // 年齡是一個整型變量 需要取地址printf("請輸入性別:>");scanf("%s", ps->data[ps->size].sex);printf("請輸入電話:>");scanf("%s", ps->data[ps->size].tele);printf("請輸入地址:>");scanf("%s", ps->data[ps->size].addr);ps->size++;printf("添加成功!\n");
}
?注意:由于 age 在這里是一個整型變量,所以要加上 &。
3、展示通訊錄中的信息
// 展示通訊錄中的信息
void ShowContact(const struct Contact* ps)
{if (ps->size == 0){printf("通訊錄為空!\n"); // 判斷通訊錄人數是否為0}else{int i = 0;// 打印標題printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年齡", "性別", "電話", "地址");// 打印數據for (i = 0; i < ps->size; i++){printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->data[i].name,ps->data[i].age,ps->data[i].sex,ps->data[i].tele,ps->data[i].addr);}}
}
注意:因為只是打印信息,不會修改內容,所以加上 const 更加安全。?
為了讓結果更美觀,采用了左對齊的方式。printf 中 %-20s 中的 20 是指輸出字段的寬度,負號表示左對齊,如省略表示右對齊,如果輸出的數據位數小于 20,則在數據右端補齊空格。
4、通過姓名查找指定聯系人所在的下標?
// 通過姓名查找指定聯系人所在下標
static int FindByName(const struct Contact* ps, char name[MAX_NAME])
{int i = 0;for (i = 0; i < ps->size; i++){if (0 == strcmp(ps->data[i].name, name)){return i; // 查找到返回聯系人的下標}}return -1; // 找不到返回-1
}
為了解決代碼的冗余,我們不妨分裝一個函數來完成查找功能。該函數會在刪除 / 修改 / 查找指定聯系人信息的函數中被調用到,這些函數剛好又在同一源文件中,所以用?static?修飾函數,只能在所在此文件內使用。
5、刪除指定聯系人信息
先判斷通訊錄是否為空,如果不為空,再通過?FindByName() 函數查找通訊錄中是否有你要刪除的聯系人,如果有則刪除。
原理:把要刪除的聯系人后面一個人的信息依次從前向后往前移動一位,覆蓋掉其信息,然后再將人數 -1。
// 刪除指定聯系人
void DelContact(struct Contact* ps)
{char name[MAX_NAME];int pos = 0;printf("請輸入要刪除人的名字:>");scanf("%s", name);// 1、查找要刪除的人在什么位置// 找到了返回名字所在元素的下標// 找不到返回 -1pos = FindByName(ps, name);// 2、刪除if (pos == -1){printf("要刪除的人不存在!\n");}else{// 刪除數據int j = 0;for (j = pos; j < ps->size - 1; j++){// 把要刪除的人后面一個人的信息依次從前向后往前移動一位,覆蓋掉其信息ps->data[j] = ps->data[j + 1];}ps->size--;printf("刪除成功!\n");}
}
6、查找指定聯系人信息
通過 FindByName() 函數查找通訊錄中是否有你要查找的聯系人,如果查找到,則打印該聯系人的信息。?
// 查找指定聯系人信息
void SearchContact(const struct Contact* ps)
{int pos = 0;char name[MAX_NAME];printf("請輸入要查找人的名字:>");scanf("%s", name);pos = FindByName(ps, name); // 查找if (pos == -1){printf("要查找的人不存在!\n");}else{printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年齡", "性別", "電話", "地址");//數據printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->data[pos].name,ps->data[pos].age,ps->data[pos].sex,ps->data[pos].tele,ps->data[pos].addr);}
}
?注意:因為只是查找信息,不會修改內容,所以加上 const 更加安全?。
7、修改指定聯系人信息
通過 FindByName() 函數查找通訊錄中是否有你要修改信息的聯系人,如果有則修改。?
// 修改指定聯系人信息
void ModifyContact(struct Contact* ps)
{int pos = 0;char name[MAX_NAME];printf("請輸入要修改人的名字:>");scanf("%s", name);pos = FindByName(ps, name);if (pos == -1){printf("要修改人的信息不存在!\n");}else{printf("請輸入名字:>");scanf("%s", ps->data[pos].name);printf("請輸入年齡:>");scanf("%d", &ps->data[pos].age);printf("請輸入性別:>");scanf("%s", ps->data[pos].sex);printf("請輸入電話:>");scanf("%s", ps->data[pos].tele);printf("請輸入地址:>");scanf("%s", ps->data[pos].addr);printf("修改完成!\n");}
}
8、排序通訊錄內容
// 排序通訊錄內容
// 比較結構體數組中兩個元素的姓名成員
int compareStructType_name(const void* elem1, const void* elem2)
{// 姓名成員是字符串,用strcmp比較return strcmp(((struct PeoInfo*)elem1)->name, ((struct PeoInfo*)elem2)->name);
}// 以名字排序所有聯系人
void SortContact(struct Contact* ps)
{// 判斷通訊錄是否為空if (ps->size == 0){printf("通訊錄為空,無法排序!\n");return;}else{// 根據姓名成員對結構體數組升序排列qsort(ps->data, ps->size, sizeof(ps->data[0]), compareStructType_name);printf("以名字排序聯系人成功!\n");}
}
該函數排序用的是姓名作為標準,也可以自己更換其他的排序方式。
9、銷毀通訊錄
使用動態內存開辟的空間是需要歸還的,當通訊錄使用完后是需要歸還內存的,也就需要我們銷毀通訊錄。
// 銷毀通訊錄
void DestoryContact(Contact* ps)
{free(ps->data);ps->data = NULL;ps->capacity = 0;ps->size = 0;printf("銷毀成功!\n");
}
五、代碼整合
1、test.c
// test.c
#define _CRT_SECURE_NO_WARNINGS 1#include "contact.h"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;// 創建通訊錄struct Contact con; // con就是通訊錄,里邊包含:data指針和size,capacity// 初始化通訊錄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:DestoryContact(&con); // 銷毀通訊錄-釋放動態開辟的內存printf("退出通訊錄!\n");break;default:printf("選擇錯誤!\n");break;}} while (input);return 0;
}
2、contact.h
// contact.h
#pragma once // 避免頭文件被重復引用// #define MAX 1000#define DEFAULT_SZ 3 // 通訊錄初始狀態的容量大小#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30#include <stdio.h>
#include <string.h> // memset strcmp
#include <stdlib.h> // qsort malloc realloc
#include <assert.h> // assertenum Option
{EXIT,//0ADD,//1DEL,//2SEARCH,//3MODIFY,//4SHOW,//5SORT//6
};struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
};// 動態通訊錄類型
typedef struct Contact
{struct PeoInfo *data; // 存放數據int size; // 記錄當前已經有的元素個數int capacity; // 記錄當前通訊錄的最大容量
}Contact;// 聲明函數
// 初始化通訊錄
void InitContact(struct Contact* ps);
// 增加聯系人
void AddContact(struct Contact* ps);
// 展示通訊錄信息
void ShowContact(const struct Contact* ps);
// 刪除指定聯系人信息
void DelContact(struct Contact* ps);
// 查找指定聯系人信息
void SearchContact(const struct Contact* ps);
// 修改指定聯系人信息
void ModifyContact(struct Contact* ps);
// 排序通訊錄內容(按姓名排)
void SortContact(struct Contact* ps);
// 銷毀通訊錄
void DestoryContact(Contact* ps);
3、contact.c
// contact.c
#define _CRT_SECURE_NO_WARNINGS 1#include "contact.h"// 初始化通訊錄
void InitContact(struct Contact* ps)
{assert(ps);ps->size = 0;ps->capacity = DEFAULT_SZ;ps->data = (struct PeoInfo*)malloc(ps->capacity * sizeof(struct PeoInfo));if (ps->data == NULL){perror("InitContact::malloc");return;}memset(ps->data, 0, ps->capacity * sizeof(struct PeoInfo));
}// 檢測當前通訊錄的容量
void CheckCapacity(struct Contact* ps)
{if (ps->size == ps->capacity){//增容struct PeoInfo* ptr = realloc(ps->data, (ps->capacity + 2) * sizeof(PeoInfo));if (ptr != NULL){ps->data = ptr;ps->capacity += 2;printf("增容成功!\n");}else{printf("增容失敗!\n");}}
}// 添加聯系人
void AddContact(struct Contact* ps)
{assert(ps);// 檢測當前通訊錄的容量// 1、如果滿了就增加空間// 2、如果沒滿無需任何操作CheckCapacity(ps);// 增加數據printf("請輸入名字:>");scanf("%s", ps->data[ps->size].name);printf("請輸入年齡:>");scanf("%d", &(ps->data[ps->size].age)); // 年齡是一個整型變量 需要取地址printf("請輸入性別:>");scanf("%s", ps->data[ps->size].sex);printf("請輸入電話:>");scanf("%s", ps->data[ps->size].tele);printf("請輸入地址:>");scanf("%s", ps->data[ps->size].addr);ps->size++;printf("添加成功!\n");
}// 展示通訊錄中的信息
void ShowContact(const struct Contact* ps)
{if (ps->size == 0){printf("通訊錄為空!\n"); // 判斷通訊錄人數是否為0}else{int i = 0;// 打印標題printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年齡", "性別", "電話", "地址");// 打印數據for (i = 0; i < ps->size; i++){printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->data[i].name,ps->data[i].age,ps->data[i].sex,ps->data[i].tele,ps->data[i].addr);}}
}// 通過名字查找指定聯系人所在的下標
static int FindByName(const struct Contact* ps, char name[MAX_NAME])
{int i = 0;for (i = 0; i < ps->size; i++){if (0 == strcmp(ps->data[i].name, name)){return i;}}return -1; // 找不到返回-1
}// 刪除指定聯系人
void DelContact(struct Contact* ps)
{char name[MAX_NAME];int pos = 0;printf("請輸入要刪除人的名字:>");scanf("%s", name);// 1、查找要刪除的人在什么位置// 找到了返回名字所在元素的下標// 找不到返回 -1pos = FindByName(ps, name);// 2、刪除if (pos == -1){printf("要刪除的人不存在!\n");}else{// 刪除數據int j = 0;for (j = pos; j < ps->size - 1; j++){// 把要刪除的人后面一個人的信息依次從前向后往前移動一位,覆蓋掉其信息ps->data[j] = ps->data[j + 1];}ps->size--;printf("刪除成功!\n");}
}// 查找指定聯系人信息
void SearchContact(const struct Contact* ps)
{int pos = 0;char name[MAX_NAME];printf("請輸入要查找人的名字:>");scanf("%s", name);pos = FindByName(ps, name); // 查找if (pos == -1){printf("要查找的人不存在!\n");}else{printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年齡", "性別", "電話", "地址");//數據printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->data[pos].name,ps->data[pos].age,ps->data[pos].sex,ps->data[pos].tele,ps->data[pos].addr);}
}// 修改指定聯系人信息
void ModifyContact(struct Contact* ps)
{int pos = 0;char name[MAX_NAME];printf("請輸入要修改人的名字:>");scanf("%s", name);pos = FindByName(ps, name);if (pos == -1){printf("要修改人的信息不存在!\n");}else{printf("請輸入名字:>");scanf("%s", ps->data[pos].name);printf("請輸入年齡:>");scanf("%d", &ps->data[pos].age);printf("請輸入性別:>");scanf("%s", ps->data[pos].sex);printf("請輸入電話:>");scanf("%s", ps->data[pos].tele);printf("請輸入地址:>");scanf("%s", ps->data[pos].addr);printf("修改完成!\n");}
}// 排序通訊錄內容
// 比較結構體數組中兩個元素的姓名成員
int compareStructType_name(const void* elem1, const void* elem2)
{// 姓名成員是字符串,用strcmp比較return strcmp(((struct PeoInfo*)elem1)->name, ((struct PeoInfo*)elem2)->name);
}// 以名字排序所有聯系人
void SortContact(struct Contact* ps)
{// 判斷通訊錄是否為空if (ps->size == 0){printf("通訊錄為空,無法排序!\n");return;}else{// 根據姓名成員對結構體數組升序排列qsort(ps->data, ps->size, sizeof(ps->data[0]), compareStructType_name);printf("以名字排序聯系人成功!\n");}
}// 銷毀通訊錄
void DestoryContact(Contact* ps)
{free(ps->data);ps->data = NULL;ps->capacity = 0;ps->size = 0;printf("銷毀成功!\n");
}