我們先思考一個通訊錄都有那些信息,很明顯通訊錄記錄的是人
人有哪些信息呢
這裏我就寫5個吧,分別是姓名,年齡,電話,性別,地址
然後我們把他們寫成一個結構體,最好定義在頭文件裏,這樣在使用的時候更方便
我們還可以把要使用的一些常用的或者要修改的常量定義成枚舉,後續有什麼要添加的,直接就能在枚舉中添加
//枚舉的一些關鍵常量
enum NUM
{
NAME = 20, //名字
TELE = 12, //電話
SEX = 10, //性別
ADDR = 100, //地址
ADD = 1,DEL = 2,SEARCH = 3,MODIFY = 4,SHOW = 5,SORT = 6,EXIT = 0,CLEAR =7,//菜單
//ARRDATA = 100
SIZE = 3, //通訊錄初始容量大小
SIZEADD = 2 //每次擴容的增加量
};
//人的信息
typedef struct peo
{
char name[NAME]; //姓名
int age; //年齡
char tele[TELE]; //電話
char sex[SEX]; //性別
char addr[ADDR]; //地址
}peo;
這裏的枚舉內容我直接把參數都寫裏了,後續在思考的過程就不再改動了
接下來思考,光有一個人的信息是不夠的,無法完整表達一個通訊錄,當然,由於我們只是簡單實現,我們就只再記錄通訊錄的人員數量和通訊錄的容量
把他們簡單封裝成一個結構體
typedef struct contact
{
peo* data; //人的相關數據
size_t count; //成員數量
size_t capacity; //通訊錄容量
}contact;
這裏我都是進行了重命名了的,方便後續的使用
這些都是頭文件裏面要用的,還有一些庫函數,我就在這裏提前定義了
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<errno.h>
然後我們寫主函數
我們考慮通訊錄運行一定要有菜單
然後我們用switch語句寫分支結構
我們需要用户主動輸入選擇,所以我們還需要一個變量接收,這個變量還可以用於switch語句的判斷
菜單我們可以單獨封裝一個函數
可以實現增加聯繫人,刪除聯繫人,查找聯繫人,修改聯繫人,顯示聯繫人,按名字排序聯繫人,清空聯繫人,和退出保存,將數據保存到文件中,把這些功能都封裝成函數
還得創建一個結構體變量用於存放通訊錄的數據
還需要一個函數對結構體進行初始化,並把保存在文件中的信息放入到結構體中
於是主函數如下
#include"contact.h"
static void menu() //菜單函數
{
printf("****************************************\n");
printf("****************************************\n");
printf("****************************************\n");
printf("*** 1.add 2.del ******\n");
printf("*** 3.search 4.modify ******\n");
printf("*** 5.show 6.sort ******\n");
printf("*** 0.exit 7.clear ******\n");
printf("****************************************\n");
printf("****************************************\n");
printf("****************************************\n");
}
int main()
{
int input = 0; //存放菜單選擇輸入的值
contact con; //創建存放通訊錄數據的結構體
init_contact(&con);//初始化結構體
do
{
menu();
printf("請輸入要進行的功能\n");
scanf("%d", &input); //菜單的選擇輸入
switch (input) //菜單選擇的判斷邏輯
{
case ADD:
add_contact(&con); //添加函數
break;
case DEL:
del_contact(&con); //刪除函數
break;
case SEARCH:
search_contact(&con); //查找函數
break;
case MODIFY:
modify_contact(&con); //修改函數
break;
case SHOW:
show_contact(&con); //顯示函數
break;
case SORT:
sort_contact(&con); //排序函數
break;
case CLEAR:
clear_contact(&con); //清空函數
break;
case EXIT:
save_contact(&con); //保存函數
destroycontact(&con); //推出後內存的銷燬
printf("程序退出\n");
break;
default:
printf("選擇錯誤,請重新選擇\n");//其他邏輯判斷
break;
}
} while (input)//do while的循環判斷
return 0;
}
主函數別忘了引用頭文件
接下來我們封裝函數
把函數單獨放在一個源文件
#include"contact.h"
//初始化函數
//對於初始化我們不僅要初始化結構體內容,還要把之前保存在文件中的信息放入到結構體中,當然還涉及到一個動態擴容的問題,我們的結構體初始設計的是有容量的,如果裏面放的信息多了,還要涉及到擴容的問題,
這些都要設計函數
static void expandCapacity(contact* con) //擴容
{
assert(con); //斷言防止空指針
if (con->capacity == con->count) //容量判斷
{
void* p = 0;
p = realloc(con->data, (con->capacity + SIZEADD) * sizeof(peo));//追加信息容量
if (NULL == p) //報錯判斷
{
printf("expandCapacity::%s\n", strerror(errno));
return;
}
con->capacity += SIZEADD;//修改容量標記
con->data = (peo*)p; //修改指針位置(其實沒變)
printf("增容成功\n");
}
}
static int read_conact(contact* con) //讀取文件中保存的通訊錄信息
{
static void expandCapacity(contact * con); //函數聲明
FILE* p = fopen("contact.txt", "r"); //打開文件
if (p == NULL) //報錯判斷
{
return 1;
}
peo a = { 0 };
while (fread(&a, sizeof(peo), 1, p) == 1)//循環提取信息,這裏取決於保存函數的寫法
{
expandCapacity(con); //擴容
con->data[con->count] = a;//賦值
(con->count)++;//成員計數加一
}
fclose(p);
p = NULL;//關閉文件,指針空置
return 0;
}
void init_contact(contact* con) //初始化函數
{
static int read_conact(contact * con);//函數調用聲明
assert(con);////斷言防止空指針
con->count = 0;//初始化人數
void* p = 0;//內存指針
p = calloc(SIZE, sizeof(peo));//創建內存
if (NULL == con->data)//報錯判斷
{
printf("init_contact:calloc:%s\n", strerror(errno));
return;
}
con->data = (peo*)p;//初始化人信息指針
con->capacity = SIZE;//初始化容量
if (read_conact(con) == 1)//讀取之前的數據
{
perror("讀取舊數據失敗");//問題判斷
return;
}
}
//初始化我們已經完成了
//接下來我們寫添加函數
void add_contact(contact* con)
{
static void expandCapacity(contact * con); //函數聲明
assert(con);//斷言防止空指針
expandCapacity(con);//擴容判斷
printf("請輸入姓名\n");
scanf("%s", (con->data+con->count)->name);
printf("請輸入年齡\n");
scanf("%d", &((con->data + con->count)->age));
printf("請輸入電話\n");
scanf("%s", con->data[con->count].tele);
printf("請輸入性別\n");
scanf("%s", con->data[con->count].sex);
printf("請輸入地址\n");
scanf("%s", con->data[con->count].addr);//這些都是信息是輸入
con->count++;//計數加一
printf("增加成功\n");
}
//接下來我們寫顯示函數
void show_contact(contact* con)
{
assert(con);//斷言防止空指針
int i = 0;
printf("%-20s\t%-5s\t%5s\t%12s\t%30s\n", "姓名", "年齡", "電話", "性別", "地址");
//顯示時方便觀察,打印一個標籤
for (i = 0; i < con->count; i++)//循環打印人員信息
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
con->data[i].name,
con->data[i].age,
con->data[i].tele,
con->data[i].sex,
con->data[i].addr);
}
}
//查找函數
//查找函數不僅要能查找,還要能顯示,我們分成兩個函數,因為刪除也需要查找
//查找部分
static int find_by_name(const contact* con,const char* arr) //依據名字查找
{
assert(con && arr);//斷言防止空指針
int i = 0;
for (i = 0; i < con->count; i++)//循環遍歷查找
{
if (0 == strcmp(con->data[i].name, arr))
{
return i;//找到返回下標
}
}
return -1;//找不到返回-1
}
//查找
void search_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函數聲明
assert(con);//斷言防止空指針
if (con->count == 0)//無聯繫人情況
{
printf("無聯繫人可查找\n");
return;
}
printf("請輸入要查找的人的姓名\n");
char arr[NAME] = { 0 };
scanf("%s", arr);
int a = find_by_name(con, arr);//套用查找函數
if (a == -1)
{
printf("要查找的人不存在\n");
return;
}
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "年齡", "電話", "性別", "地址");
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n",
con->data[a].name,
con->data[a].age,
con->data[a].tele,
con->data[a].sex,
con->data[a].addr);//顯示部分
}
//刪除
void del_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函數聲明
assert(con);//斷言防止空指針
char arr[NAME] = { 0 };
if (con->count == 0)//無聯繫人情況
{
printf("無聯繫人可刪除\n");
return;
}
printf("請輸入要刪除人的名字\n");
scanf("%s", arr);
int a = find_by_name(con,arr);//函數回調
if (a == -1)
{
printf("要刪除的人不存在\n");
return;
}
for (; a < con->count - 1; a++)//刪除本質就是把後面的信息提前,覆蓋掉這個下標的信息
//當然也還有其他的寫法
{
con->data[a] = con->data[a + 1];
}
con->count--;
printf("刪除成功\n");
}
//修改函數
//修改也要先找
void modify_contact(contact* con)
{
static int find_by_name(const contact * con, const char* arr);//函數聲明
assert(con);//斷言防止空指針
if (con->count == 0)
{
printf("無聯繫人可修改\n");//沒有的情況判斷
return;
}
printf("請輸入要修改的人的姓名\n");
char arr[NAME] = { 0 };
scanf("%s", arr);
int a = find_by_name(con, arr);//調用函數
if (a == -1)
{
printf("要修改的人不存在\n");
return;
}
printf("請輸入修改姓名\n");
scanf("%s", con->data[a].name);
printf("請輸入修改年齡\n");
scanf("%d", &(con->data[a].age));
printf("請輸入修改電話\n");
scanf("%s", con->data[a].tele);
printf("請輸入修改性別\n");
scanf("%s", con->data[a].sex);
printf("請輸入修改地址\n");
scanf("%s", con->data[a].addr);
printf("修改成功\n");//信息的修改
}
//排序函數
//這裏我使用了qsort
static int cmp_peo_by_name(const void* e1, const void* e2)//qsort調用的排序函數
{
return strcmp(((const peo*)e1)->name, ((const peo*)e2)->name);
}
void sort_contact(contact* con)//本體
{
static int cmp_peo_by_name(const void* e1, const void* e2);//函數調用
assert(con);//斷言
if (con->count == 0)
{
printf("無聯繫人可排序\n");//沒人的情況
return;
}
printf("正在按姓名排序\n");
qsort(con->data, con->count, sizeof(peo), cmp_peo_by_name);//qsort排序
printf("排序成功\n");
show_contact(con);//這裏不聲明是因為這些函數都會在頭文件中聲明,但內些靜態函數不會
}
//銷燬,保存,清空
void destroycontact(contact* con)//釋放內存
{
assert(con);
free(con->data);
con->data = NULL;
}
void save_contact(const contact* con)//保存信息
{
assert(con);
FILE* p = fopen("contact.txt", "wb");
if (p == NULL)
{
perror("保存失敗:save_contact:fopen");
return;
}
fwrite(con->data, sizeof(peo), con->count, p);
fclose(p);
p = NULL;
}
void clear_contact(contact* con)//清空文件信息和當前結構體信息
{
assert(con);
FILE* p = fopen("contact.txt", "w");
if (p == NULL)
return;
fclose(p);
p = NULL;
con->count = 0;
memset(con->data,0, sizeof(peo));
printf("已全部清除\n");
}
最後我們再在頭文件中聲明一下這些函數,為了調用
//初始化通訊錄
void init_contact(contact* con);
//增加聯繫人
void add_contact(contact* con);
//顯示
void show_contact(contact* con);
//刪除
void del_contact(contact* con);
//查找
void search_contact(contact* con);
//修改
void modify_contact(contact* con);
//排序
void sort_contact(contact* con);
//退出後內存銷燬
void destroycontact(contact* con);
//退出後的保存
void save_contact(const contact*con);
//清空通訊錄
void clear_contact(contact*con);
靜態函數不用聲明,主要和一些商業習慣有關
好了,這樣就結束了,比較簡陋,旨在練手,大佬勿噴