????????在本博客中,我們將介紹如何使用C語言構(gòu)建一個基本的通訊錄。主要涉及C語言的指針、結(jié)構(gòu)體、動態(tài)內(nèi)存管理、文件操作等方面的知識。我們還將學(xué)習(xí)如何使用C語言的各種功能和技巧來實現(xiàn)通訊錄的各種操作,如添加聯(lián)系人、編輯聯(lián)系人、刪除聯(lián)系人和搜索聯(lián)系人等,并且還會對通訊錄進行多個版本的優(yōu)化。
????????無論您是初學(xué)者還是有一定編程經(jīng)驗的開發(fā)者,通過學(xué)習(xí)和實踐C語言通訊錄的構(gòu)建,你都能夠加深對C語言的理解,并提升自己的編程能力。這部分知識的學(xué)習(xí)和代碼實踐對后面學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)也有很大幫助。希望你能夠通過本博客的學(xué)習(xí),掌握C語言通訊錄的開發(fā)技巧,并能夠在實際項目中靈活應(yīng)用。
????????讓我們開始學(xué)習(xí)并構(gòu)建自己的C語言通訊錄吧!
???????
?
目錄
??零、前置知識復(fù)習(xí):?
??1.結(jié)構(gòu)體:
??2.動態(tài)內(nèi)存管理
??3.文件操作
??4.分文件編寫:
??一、版本一:靜態(tài)通訊錄
??1.結(jié)構(gòu)體初始化
??2.添加聯(lián)系人
??3.顯示聯(lián)系人
??4.刪除聯(lián)系人
??5.查找聯(lián)系人
??6.修改聯(lián)系人
??7.排序
??二、版本二:動態(tài)順序表
??1.結(jié)構(gòu)體初始化
??2.添加聯(lián)系人
??3.銷毀通訊錄
??三、版本三:通訊錄與文件
??1.通訊錄讀取文件信息
??2.通訊錄信息保存在文件
附錄:通訊錄最終版本代碼
??零、前置知識復(fù)習(xí):?
??1.結(jié)構(gòu)體:
詳見博客:結(jié)構(gòu)體
通過一個結(jié)構(gòu)體來簡單定義聯(lián)系人信息:
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
typedef struct PeoInfo
{
char name[MAX_NAME];//姓名
int age;//年齡
char sex[MAX_SEX];//性別
char tele[MAX_TELE];//電話
char addr[MAX_ADDR];//住址
}PeoInfo;
?結(jié)構(gòu)體傳參:只能傳結(jié)構(gòu)體地址
原因:
- 函數(shù)傳參的時候,參數(shù)是需要壓棧,會有時間和空間上的系統(tǒng)開銷。 如果傳遞一個結(jié)構(gòu)體對象的時候,結(jié)構(gòu)體過大,參數(shù)壓棧的的系統(tǒng)開銷比較大,所以會導(dǎo)致性能的下降。
- 傳參時,形參是實參的拷貝,我們要改變實參,只能傳遞指針。
??2.動態(tài)內(nèi)存管理
malloc,realloc等函數(shù)的使用,尤其是realloc
詳見博客:動態(tài)內(nèi)存管理
??3.文件操作
fwite和fread函數(shù)的使用
詳見博客:文件操作
??4.分文件編寫:
Contact.h 主要寫類型和函數(shù)的聲明
Contact.c 主要寫函數(shù)的實現(xiàn)
test.c 通訊錄測試
詳見博客:文件包含相關(guān)博客
具體細(xì)節(jié)下面介紹:
??一、版本一:靜態(tài)通訊錄
這個版本的通訊錄是通過大小指定的數(shù)組來存儲聯(lián)系人信息。
靜態(tài)通訊錄結(jié)構(gòu)體的實現(xiàn)如下:
//靜態(tài)版本
typedef struct Contact
{
PeoInfo data[MAX];
int sz;
}Contact;
這里我們的Contact結(jié)構(gòu)體中存放的是PeoInfo類型的數(shù)組data,并且大小固定為MAX,超出容量后就不能添加聯(lián)系人了。這也是靜態(tài)通訊錄的一個缺陷。sz是聯(lián)系人個數(shù)。
下面詳解各種功能的實現(xiàn):
??1.結(jié)構(gòu)體初始化
//靜態(tài)版本
void InitContact(Contact* pc)
{
assert(pc);
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
使用memset內(nèi)存函數(shù)進行初始化
memset函數(shù)使用詳見:內(nèi)存操作函數(shù)
??2.添加聯(lián)系人
//靜態(tài)版本
void AddContact(Contact* pc)
{
assert(pc);
if (pc->sz == MAX)
{
printf("通訊錄已滿,無法添加\n");
return;
}
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("成功增加聯(lián)系人\n");
}
??3.顯示聯(lián)系人
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");//預(yù)留一定空間,并且左對齊
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
??4.刪除聯(lián)系人
刪除聯(lián)系人之前,我們要查找聯(lián)系人。不管刪除需要查找,我們修改聯(lián)系人也需要查找。所以這里我們可以封裝一個按姓名查找的函數(shù)。
static int FindByName(const Contact* pc, char name[])
//static修飾函數(shù),只能在該源文件中使用
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;//找到了
}
}
return -1;//沒找到
}
注意:
- 函數(shù)用static修飾,表示函數(shù)由外部鏈接屬性轉(zhuǎn)變?yōu)閮?nèi)部鏈接屬性。只能在本源文件中使用。
- 字符串的比較,要用strcmp函數(shù)。
?刪除聯(lián)系人函數(shù):
//靜態(tài)
void DelContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通訊錄為空,無法刪除\n");
return;
}
char name[MAX_NAME] = {0};
assert(pc);
//查找
printf("請輸入要刪除的人名字:>");
scanf("%s", name);
int del = FindByName(pc,name);
if(del == -1)
{
printf("要刪除的人不存在!\n");
return;
}
//刪除
int i=0;
for (i = del; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("成功刪除聯(lián)系人\n");
}
??5.查找聯(lián)系人
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");
}
else
{
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");
//打印數(shù)據(jù)
printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
??6.修改聯(lián)系人
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");
else
{
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");
}
}
??7.排序
復(fù)習(xí):qsort函數(shù)的使用
int CmpByName(void* p1, void* p2)
{
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
int CmpByAge(void* p1, void* p2)
{
return ((PeoInfo*)p1)->age - ((PeoInfo*)p2)->age;
}
void SortContact(Contact* pc)
{
int option = 0;
printf("****1.姓名 2.年齡****\n");
printf("按著姓名還是年齡排序?>");
scanf("%d", &option);
if (option == 1)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);
}
else
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);
}
}
??二、版本二:動態(tài)順序表
上面我們說過,靜態(tài)順序表只用一個數(shù)組來存儲聯(lián)系人信息,滿了之后就不能擴容,這是一個很大的缺陷,下面我們通過動態(tài)內(nèi)存管理來實現(xiàn)動態(tài)通訊錄。
動態(tài)通訊錄結(jié)構(gòu)體如下:
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3//初始容量
#define INC_SZ 2//擴容添加容量
//動態(tài)版本
typedef struct Contact
{
PeoInfo* data;
int sz;
int capacity;
}Contact;
PeoInfo指針用來維護動態(tài)開辟的空間。sz是聯(lián)系人數(shù)量。capacity是通訊錄容量。?
與靜態(tài)版本相比,我們只需修改初始化、添加函數(shù),并添加一些其他函數(shù)。
??1.結(jié)構(gòu)體初始化
//動態(tài)版本
void InitContact(Contact* pc)
{
assert(pc);
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (!pc->data)
{
perror("InitContact");
exit(-1);
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
}
??2.添加聯(lián)系人
如果容量滿了,就可以使用realloc函數(shù)進行擴容,我們可以把擴容函數(shù)進行封裝。
realloc函數(shù)的使用細(xì)節(jié),詳見:動態(tài)內(nèi)存管理
static int CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)
{
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (!tmp)
{
perror("CheckCapacity");
return 0;
}
pc->data = tmp;
pc->capacity += INC_SZ;
printf("增容成功\n");
return 1;
}
return 1;
}
添加聯(lián)系人:
//動態(tài)版本
void AddContact(Contact* pc)
{
assert(pc);
if (CheckCapacity(pc) == 0)
{
exit(-1);
}
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("成功增加聯(lián)系人\n");
}
??3.銷毀通訊錄
自己開辟的空間用完后要銷毀,我們封裝成Destroy函數(shù)。
void DestroyContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
??三、版本三:通訊錄與文件
我們發(fā)現(xiàn),以上兩個版本都是在內(nèi)存中保存信息,退出程序后通訊錄信息就會丟失,無法做到數(shù)據(jù)持久化,這里就能用到文件了。
??1.通訊錄讀取文件信息
static void LoadContact(Contact* pc)
{
FILE* pf = fopen("data.txt", "rb");
if (!pf)
{
perror("fopen");
exit(-1);
}
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
if (!CheckCapacity(pc))
{
exit(-1);
}
pc->data[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf == NULL;
}
注意:
static修飾,只在本源文件使用
二進制讀寫函數(shù)的返回值文章來源:http://www.zghlxwxcb.cn/news/detail-603067.html
詳見:文件操作文章來源地址http://www.zghlxwxcb.cn/news/detail-603067.html
??2.通訊錄信息保存在文件
void SaveContact(Contact* pc)
{
FILE* pf = fopen("data.txt", "wb");
if (!pf)
{
perror("fopen");
exit(-1);
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
}
附錄:通訊錄最終版本代碼
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
#define INC_SZ 2
enum OPTION
{
EXIT,//0
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//通訊錄
靜態(tài)版本
//typedef struct Contact
//{
// PeoInfo data[MAX];
// int sz;
//}Contact;
//動態(tài)版本
typedef struct Contact
{
PeoInfo* data;
int sz;
int capacity;
}Contact;
//初始化通訊錄
void InitContact(Contact* pc);
//增加聯(lián)系人
void AddContact(Contact* pc);
//顯示所有聯(lián)系人的信息
void ShowContact(const Contact* pc);
//刪除指定聯(lián)系人
void DelContact(Contact* pc);
//查找指定聯(lián)系人
void SearchContact(const Contact* pc);
//修改指定聯(lián)系人
void ModifyContact(Contact* pc);
//排序
void SortContact(Contact* pc);
//通訊錄銷毀
void DestroyContact(Contact* pc);
//保存通訊錄信息到文件中
void SaveContact(Contact* pc);
#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"
static void LoadContact(Contact* pc)
{
FILE* pf = fopen("data.txt", "rb");
if (!pf)
{
perror("fopen");
return;
}
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
if (!CheckCapacity(pc))
{
return;
}
pc->data[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf == NULL;
}
靜態(tài)版本
//void InitContact(Contact* pc)
//{
// assert(pc);
// memset(pc->data, 0, sizeof(pc->data));
// pc->sz = 0;
//}
//動態(tài)版本
void InitContact(Contact* pc)
{
assert(pc);
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (!pc->data)
{
perror("InitContact");
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
//文件信息輸入到通訊錄中
LoadContact(pc);
}
靜態(tài)版本
//
//void AddContact(Contact* pc)
//{
// assert(pc);
// if (pc->sz == MAX)
// {
// printf("通訊錄已滿,無法添加\n");
// return;
// }
//
// 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("成功增加聯(lián)系人\n");
//}
static int CheckCapacity(Contact* pc)
{
if (pc->sz == pc->capacity)
{
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (!tmp)
{
perror("CheckCapacity");
return 0;
}
else
{
pc->data = tmp;
pc->capacity += INC_SZ;
printf("增容成功\n");
return 1;
}
}
return 1;
}
//動態(tài)版本
//靜態(tài)版本有一個致命缺點,就是滿了就不能添加了,很明顯這種版本是會被淘汰的,所以我們使用動態(tài)內(nèi)存管理的方式開辟空間
void AddContact(Contact* pc)
{
assert(pc);
if (CheckCapacity(pc) == 0)
{
return;
}
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("成功增加聯(lián)系人\n");
}
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");//預(yù)留一定空間,并且左對齊
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
static int FindByName(const Contact* pc, char name[])
//static修飾函數(shù),只能在該源文件中使用
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;//找到了
}
}
return -1;//沒找到
}
靜態(tài)
//void DelContact(Contact* pc)
//{
// if (pc->sz == 0)
// {
// printf("通訊錄為空,無法刪除\n");
// return;
// }
// char name[MAX_NAME] = {0};
// assert(pc);
// //刪除
// printf("請輸入要刪除的人名字:>");
// scanf("%s", name);
//
// //找到要刪除的人
// int i = 0;
// int del = 0;
// int flag = 0;
// for (i = 0; i < pc->sz; i++)
// {
// if (strcmp(pc->data[i].name, name) == 0)
// {
// del = i;
// flag = 1;
// break;
// }
// }
// if (flag == 0)
// {
// printf("要刪除的人不存在\n");
// return;
// }
// //刪除坐標(biāo)位del的聯(lián)系人
// for (i = del; i < pc->sz-1; i++)
// {
// pc->data[i] = pc->data[i + 1];
// }
// pc->sz--;
//
// printf("成功刪除聯(lián)系人\n");
//}
//動態(tài)
void DelContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通訊錄為空,無法刪除!\n");
return;
}
char name[MAX_NAME] = { 0 };
assert(pc);
//查找
printf("請輸入要刪除的人的名字:>");
scanf("%s", name);
int del = FindByName(pc, name);
if (del == -1)
{
printf("要刪除的人不存在\n");
return;
}
//刪除
int i = 0;
for (i = del; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("成功刪除聯(lián)系人\n");
}
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");
}
else
{
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");
//打印數(shù)據(jù)
printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
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");
else
{
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");
}
}
int CmpByName(void* p1, void* p2)
{
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
int CmpByAge(void* p1, void* p2)
{
return ((PeoInfo*)p1)->age - ((PeoInfo*)p2)->age;
}
void SortContact(Contact* pc)
{
int option = 0;
printf("****1.姓名 2.年齡****\n");
printf("按著姓名還是年齡排序?>");
scanf("%d", &option);
if (option == 1)
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);
}
else
{
qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);
}
}
void DestroyContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
void SaveContact(Contact* pc)
{
FILE* pf = fopen("data.txt", "wb");
if (!pf)
{
perror("fopen");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
}
#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");
}
void test()
{
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);
DestroyContact(&con);
printf("退出通訊錄\n");
break;
default:
printf("選擇錯誤, 重新選擇\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
到了這里,關(guān)于C語言通訊錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!