在本博客中,我们将介绍如何使用C语言构建一个基本的通讯录。主要涉及C语言的指针、结构体、动态内存管理、文件操作等方面的知识。我们还将学习如何使用C语言的各种功能和技巧来实现通讯录的各种操作,如添加联系人、编辑联系人、删除联系人和搜索联系人等,并且还会对通讯录进行多个版本的优化。
无论您是初学者还是有一定编程经验的开发者,通过学习和实践C语言通讯录的构建,你都能够加深对C语言的理解,并提升自己的编程能力。这部分知识的学习和代码实践对后面学习数据结构也有很大帮助。希望你能够通过本博客的学习,掌握C语言通讯录的开发技巧,并能够在实际项目中灵活应用。
让我们开始学习并构建自己的C语言通讯录吧!
目录
🌹零、前置知识复习:
🌸1.结构体:
🌸2.动态内存管理
🌸3.文件操作
🌸4.分文件编写:
🌹一、版本一:静态通讯录
🌸1.结构体初始化
🌸2.添加联系人
🌸3.显示联系人
🌸4.删除联系人
🌸5.查找联系人
🌸6.修改联系人
🌸7.排序
🌹二、版本二:动态顺序表
🌸1.结构体初始化
🌸2.添加联系人
🌸3.销毁通讯录
🌹三、版本三:通讯录与文件
🌸1.通讯录读取文件信息
🌸2.通讯录信息保存在文件
附录:通讯录最终版本代码
🌹零、前置知识复习:
🌸1.结构体:
详见博客:结构体
通过一个结构体来简单定义联系人信息:
#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;
结构体传参:只能传结构体地址
原因:
- 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
- 传参时,形参是实参的拷贝,我们要改变实参,只能传递指针。
🌸2.动态内存管理
malloc,realloc等函数的使用,尤其是realloc
详见博客:动态内存管理
🌸3.文件操作
fwite和fread函数的使用
详见博客:文件操作
🌸4.分文件编写:
Contact.h 主要写类型和函数的声明
Contact.c 主要写函数的实现
test.c 通讯录测试
详见博客:文件包含相关博客
具体细节下面介绍:
🌹一、版本一:静态通讯录
这个版本的通讯录是通过大小指定的数组来存储联系人信息。
静态通讯录结构体的实现如下:
//静态版本
typedef struct Contact
{
PeoInfo data[MAX];
int sz;
}Contact;
这里我们的Contact结构体中存放的是PeoInfo类型的数组data,并且大小固定为MAX,超出容量后就不能添加联系人了。这也是静态通讯录的一个缺陷。sz是联系人个数。
下面详解各种功能的实现:
🌸1.结构体初始化
//静态版本
void InitContact(Contact* pc)
{
assert(pc);
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
使用memset内存函数进行初始化
memset函数使用详见:内存操作函数
🌸2.添加联系人
//静态版本
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("成功增加联系人\n");
}
🌸3.显示联系人
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");//预留一定空间,并且左对齐
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.删除联系人
删除联系人之前,我们要查找联系人。不管删除需要查找,我们修改联系人也需要查找。所以这里我们可以封装一个按姓名查找的函数。
static int FindByName(const Contact* pc, char name[])
//static修饰函数,只能在该源文件中使用
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;//找到了
}
}
return -1;//没找到
}
注意:
- 函数用static修饰,表示函数由外部链接属性转变为内部链接属性。只能在本源文件中使用。
- 字符串的比较,要用strcmp函数。
删除联系人函数:
//静态
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("成功删除联系人\n");
}
🌸5.查找联系人
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", "名字", "年龄", "性别", "电话", "地址");
//打印数据
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.修改联系人
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.排序
复习:qsort函数的使用
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);
}
}
🌹二、版本二:动态顺序表
上面我们说过,静态顺序表只用一个数组来存储联系人信息,满了之后就不能扩容,这是一个很大的缺陷,下面我们通过动态内存管理来实现动态通讯录。
动态通讯录结构体如下:
#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//扩容添加容量
//动态版本
typedef struct Contact
{
PeoInfo* data;
int sz;
int capacity;
}Contact;
PeoInfo指针用来维护动态开辟的空间。sz是联系人数量。capacity是通讯录容量。
与静态版本相比,我们只需修改初始化、添加函数,并添加一些其他函数。
🌸1.结构体初始化
//动态版本
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.添加联系人
如果容量满了,就可以使用realloc函数进行扩容,我们可以把扩容函数进行封装。
realloc函数的使用细节,详见:动态内存管理
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;
}
添加联系人:
//动态版本
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("成功增加联系人\n");
}
🌸3.销毁通讯录
自己开辟的空间用完后要销毁,我们封装成Destroy函数。
void DestroyContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
🌹三、版本三:通讯录与文件
我们发现,以上两个版本都是在内存中保存信息,退出程序后通讯录信息就会丢失,无法做到数据持久化,这里就能用到文件了。
🌸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修饰,只在本源文件使用
二进制读写函数的返回值
详见:文件操作
🌸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;
//通讯录
静态版本
//typedef struct Contact
//{
// PeoInfo data[MAX];
// int sz;
//}Contact;
//动态版本
typedef struct Contact
{
PeoInfo* data;
int sz;
int capacity;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//显示所有联系人的信息
void ShowContact(const Contact* pc);
//删除指定联系人
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(const Contact* pc);
//修改指定联系人
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;
}
静态版本
//void InitContact(Contact* pc)
//{
// assert(pc);
// memset(pc->data, 0, sizeof(pc->data));
// pc->sz = 0;
//}
//动态版本
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);
}
静态版本
//
//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("成功增加联系人\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;
}
//动态版本
//静态版本有一个致命缺点,就是满了就不能添加了,很明显这种版本是会被淘汰的,所以我们使用动态内存管理的方式开辟空间
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("成功增加联系人\n");
}
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");//预留一定空间,并且左对齐
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修饰函数,只能在该源文件中使用
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;//找到了
}
}
return -1;//没找到
}
静态
//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;
// }
// //删除坐标位del的联系人
// for (i = del; i < pc->sz-1; i++)
// {
// pc->data[i] = pc->data[i + 1];
// }
// pc->sz--;
//
// printf("成功删除联系人\n");
//}
//动态
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("成功删除联系人\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", "名字", "年龄", "性别", "电话", "地址");
//打印数据
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;
}