一.前言
今天给小伙伴们分享的是运用结构体以及指针等相关C语言知识实现一个简易版的通讯录。咱们写的通讯录的功能主要包括添加及删除联系人,修改联系人信息,显现所有联系人,查找已添加联系人,以及对联系人进行排序,并且最后可以把添加的联系人保存到电脑文件中。那咱们就开始动手写吧 @
二.框架构建
1.文件创建
咱们先建立一个项目工程,然后从里面创建源文件及头文件。
源文件可以创建两个,一个用来写完整的通讯录结构,但其功能实现放在另一个源文件中,通过函数调用来使用功能。这样子好处就在于后续如果咱们想再添加或者修改一些功能都是非常方便的。头文件的话咱们自己创建一个,然后把需要使用的相关头文件,一些自己定义的宏以及函数的声明都可以放在里面,这样子在其他源文件中使用时就比较方便和简洁了。
另外小伙伴们在给文件命名的时候尽量取跟项目相关的名字,我这里就直接用 了通讯录的英文(contact)~
2.整体结构实现
接着咱们就开始把通讯录的整体结构写出来,这部分在 test.c 文件中操作。
在写之前,可以参考一下以前给小伙伴们分享的扫雷以及三子棋,咱们也可以先写给通讯录写一个简易的功能菜单界面,这里咱们就直接把它封装成一个函数,方便修改和使用。
//功能菜单界面实现
void menu()
{printf("***********通讯录************\n");printf("*******1.add 2.del*******\n");printf("*******3.serc 4.mod*******\n");printf("*******5.show 6.sort******\n");printf("*******7.clear 0.exit******\n");printf("*****************************\n");}
通过菜单咱们可以看到已经列出来一些功能,并且给它们分别编号了。那接下来问题就是怎样方便使用呢?这里咱们首先想到 switch 语句的使用,通过switch case 语句可以很方便的调用不同的功能,这也是为什么在菜单界面给功能编号了。
另外在使用switch语句之前,在给大家分享一下枚举类型相关知识,因为它可以使switch case语句体现其代表的功能。
枚举(Enumeration)类型是一种用户自定义的数据类型,它允许你为一组整数值定义一个更易读、更有意义的名字。在C语言中,枚举类型通过 enum
关键字定义,而在C++中,枚举类型则更加强大,可以包含方法和属性。
C语言中的枚举类型
在C语言中,枚举类型的定义如下:
enum Color { RED, GREEN, BLUE };
这个定义创建了一个名为 Color
的枚举类型,它包含了三个枚举常量:RED
、GREEN
和 BLUE
。这些常量默认从0开始依次递增,所以 RED
的值为0,GREEN
的值为1,BLUE
的值为2。你也可以为枚举常量显式地指定值
enum Color { RED = 1, GREEN, BLUE };
在这个例子中,RED
的值为1,GREEN
的值为2,BLUE
的值为3。
所以在使用枚举类型后,可以完美将case语句中的数字替换成相关功能的英文缩写,这样在咱们以后看代码时能够快速知道这条case语句所表达的意思
enum Option
{EXIT, //默认为零,依次递增,与菜单中的数字相匹配ADD,DEL,SEARCH,MODIFY,SHOW,SORT,CLEAR};
接着就是在主函数中使用do while循环语句实现功能选择,因为咱们可能一次需要添加多个联系人,所以采用do while 循环语句比较方便一些
int main()
{int input = 0;Contact con;InitContact(&con);do{menu();printf("请选择功能\n");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;case CLEAR:ClearContact(&con);break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
小伙伴们可能对主函数中调用的一些函数不理解其功能,可以先看下咱们创建的头文件。
#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 30
#define TELE_MAX 12
#define DEFAULT 3
#define INC_SZ 2typedef struct PeoInfo //存放联系人相关信息
{char name[NAME_MAX];char sex[SEX_MAX];char addr[ADDR_MAX];char tele[TELE_MAX];int age;
}PeoInfo;typedef struct Contact //通讯录大小
{PeoInfo* data;int sz;int capacity;}Contact;
//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//删除指定联系人
void DelContact(Contact* pc);//显示通讯录中的信息
void ShowContact(const Contact* pc);//查找指定联系人
void SearchContact(const Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);
//排序联系人
void SortContact(Contact* pc);
//清空所有联系人
void ClearContact(Contact* pc);
//销毁动态内存创建的空间
void DestroyContact(Contact*pc);
//保存联系人到文件
void SaveContact(Contact* pc);
//显现文件中的联系人
void LoadContact(Contact* pc);
咱们写的通讯录主要时通过结构体指针调用来实现各个函数功能,然后呢咱们还通过两个结构体来放联系人信息以及通讯录大小调整。另外还定义了一些宏,以便于以后调整。
咱们通讯录通过运用之前介绍过的动态内存函数,可以做到不多占用内存空间,即需要使用多少,咱们就开辟多少。更多详细介绍咱们看下一章相关功能实现~
这个通讯录的核心就是通过调用结构体指针,所以小伙伴们得掌握好指针知识哟@
三.函数功能的实现
函数功能的实现主要在 contact.c 这个源文件中进行,这样方便咱们调整以及调试。
咱们就按照菜单上的函数顺序来逐个实现吧~
1.初始化通讯录
void InitContact(Contact* pc)assert(pc);pc->sz = 0;PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT, sizeof(PeoInfo));if (ptr == NULL){perror("InitContact::calloc");return;}pc->data = ptr;pc->capacity = DEFAULT;LoadContact(pc);
这段代码定义了一个名为 InitContact
的函数,其目的是初始化一个 Contact
类型的指针 pc
。函数中的步骤如下:
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。如果pc
是NULL
,程序将终止,并通常会输出一个错误信息。 -
初始化成员变量:将
pc->sz
设置为 0,这可能表示初始化Contact
结构体中的某个大小或计数器。 -
动态内存分配:
PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT, sizeof(PeoInfo));
这行代码使用calloc
函数分配DEFAULT
个PeoInfo
结构体的内存。calloc
会初始化分配的内存为零。 -
错误检查:检查
calloc
返回的指针ptr
是否为NULL
。如果是NULL
,说明内存分配失败,函数将使用perror
打印错误信息并返回。 -
设置成员变量:如果内存分配成功,将
ptr
赋值给pc->data
,并将pc->capacity
设置为DEFAULT
,这可能表示Contact
结构体可以存储的PeoInfo
结构体的最大数量。 -
加载联系人:调用
LoadContact(pc);
函数,这个函数的具体实现没有给出,但根据函数名,它可能用于加载或初始化联系人信息。
这段代码是一个典型的资源分配和初始化的例子,其中包含了错误处理以确保程序的健壮性。如果 calloc
分配内存失败,程序会打印错误信息并返回,而不是继续执行可能引发更多错误的代码。这种错误处理是良好的编程实践,可以防止程序在资源不足的情况下继续执行。
请注意,这段代码中缺少了包含 DEFAULT
宏定义的头文件,以及 Contact
和 PeoInfo
结构体的定义,还有 LoadContact
函数的实现。这些部分需要在其他地方定义,以便这段代码能够正确编译和运行。此外,calloc
函数的定义在 <stdlib.h>
头文件中,因此需要在文件顶部包含这个头文件。
2.增加联系人
这个功能实现咱们创建了两个函数,其中 check_capacity() 是为了检查添加联系人是,是不是需要添加多个,如有,则调用realloc 函数重新调整动态空间。
void check_capacity(Contact* pc)
{if (pc->sz == pc->capacity){printf("通讯录已满,无法添加\n");//return;//增加容量PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){perror("check_capacity::realloc");return;}pc->data = ptr;pc->capacity += INC_SZ;printf("增容成功\n");}
}void AddContact(Contact* pc)
{assert(pc);check_capacity(pc);//增加一个人的信息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].addr);printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);pc->sz++;
}
这段代码包含两个函数:check_capacity
和 AddContact
,它们用于管理一个通讯录的数据结构。以下是每个函数的详细解释:
check_capacity
函数
这个函数检查通讯录的容量是否已满。如果已满,它将尝试增加容量。
-
检查容量:如果
pc->sz
(当前存储的联系人数量)等于pc->capacity
(通讯录的总容量),则认为通讯录已满。 -
打印信息:如果通讯录已满,打印一条消息告知用户。
-
增加容量:使用
realloc
函数增加通讯录的容量。新容量是当前容量加上INC_SZ
(一个预定义的增量大小)。 -
错误检查:如果
realloc
返回NULL
,表示内存重新分配失败,函数将打印错误信息并返回。 -
更新指针和容量:如果
realloc
成功,更新pc->data
指针指向新的内存地址,并将pc->capacity
增加INC_SZ
。 -
打印成功消息:打印一条消息告知用户容量增加成功。
AddContact
函数
这个函数用于向通讯录中添加一个新的联系人。
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
检查容量:调用
check_capacity
函数确保通讯录有足够的空间添加新的联系人。 -
输入联系人信息:使用
scanf
函数从用户那里获取新联系人的名字、年龄、性别、地址和电话,并将这些信息存储在pc->data[pc->sz]
中。 -
更新联系人数量:增加
pc->sz
的值,表示通讯录中联系人的数量增加。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
、age
、sex
、addr
和tele
等成员。 INC_SZ
应该是一个预定义的常量,表示每次增加容量时增加的联系人数量。realloc
函数的原型在<stdlib.h>
头文件中,因此需要在文件顶部包含这个头文件。- 使用
scanf
函数时,需要确保输入的格式与预期匹配,并且输入的字符串不会超出目标数组的大小,以避免缓冲区溢出。 - 在实际应用中,可能需要对用户输入进行验证,以确保数据的有效性和安全性。
这段代码展示了如何在C语言中使用动态内存分配和管理通讯录的基本操作。通过 check_capacity
和 AddContact
函数,可以实现通讯录的动态扩容和联系人信息的添加。
3.删除指定联系人
这个功能的实现,也借助了一个叫FindByName () 的函数,这个函数主要通过名字来找到联系人在数组中的位置下标,然后找到联系人进行相关操作。
int FindByName(const Contact* pc, char name[]);void DelContact(Contact* pc)
{assert(pc);char name[NAME_MAX] = { 0 };if (pc->sz == 0){printf("通讯录为空,无法操作\n");return;}printf("请输入要删除联系人的姓名:>");scanf("%s", name);int ret = FindByName(pc, name);if (ret == -1){printf("要删除的联系人不存在\n");return;}int i = 0;for (i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功\n");
}
int FindByName(const Contact* pc, char name[])
{assert(pc);int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0){return i;}return -1;}}
这段代码包含两个函数:DelContact
和 FindByName
,它们用于管理一个通讯录的数据结构。以下是每个函数的详细解释:
DelContact
函数
这个函数用于从通讯录中删除一个联系人。
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
检查通讯录是否为空:如果
pc->sz
为 0,表示通讯录为空,无法进行删除操作。 -
输入联系人姓名:提示用户输入要删除的联系人的姓名。
-
查找联系人:调用
FindByName
函数查找联系人在通讯录中的位置。 -
检查联系人是否存在:如果
FindByName
返回-1
,表示联系人不存在,打印消息并返回。 -
删除联系人:如果联系人存在,使用循环将联系人后面的所有联系人向前移动一个位置,覆盖要删除的联系人。
-
更新联系人数量:减少
pc->sz
的值,表示通讯录中联系人的数量减少。 -
打印成功消息:打印一条消息告知用户删除成功。
FindByName
函数
这个函数用于在通讯录中查找一个联系人。
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
遍历通讯录:使用
for
循环遍历通讯录中的每个联系人。 -
比较姓名:使用
strcmp
函数比较当前联系人的姓名和传入的姓名。 -
返回位置:如果找到匹配的姓名,返回当前联系人在通讯录中的位置。
-
返回
-1
:如果遍历完通讯录都没有找到匹配的姓名,返回-1
。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
等成员。 NAME_MAX
应该是一个预定义的常量,表示姓名的最大长度。FindByName
函数中的return -1;
应该在for
循环外面,否则如果通讯录中有多个联系人,即使找到了匹配的姓名,也会错误地返回-1
。- 在实际应用中,可能需要对用户输入进行验证,以确保数据的有效性和安全性。
这段代码展示了如何在C语言中使用数组和字符串操作来管理通讯录的基本操作。通过 DelContact
和 FindByName
函数,可以实现通讯录中联系人的删除和查找。
4.显示通讯录中的联系人信息
void ShowContact(const Contact* pc)
{assert(pc);int i = 0;printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].tele,pc->data[i].addr);}
}
这段代码定义了一个名为 ShowContact
的函数,其目的是显示通讯录中所有联系人的信息。函数中的步骤如下:
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
打印表头:使用
printf
函数打印表头,包括姓名、年龄、性别、电话和地址。表头中的%-20s
、%-4s
、%-5s
、%-12s
和%-20s
是格式化字符串,它们指定了每个字段的宽度和对齐方式。%-20s
表示左对齐,宽度为20个字符的字符串。 -
遍历通讯录:使用
for
循环遍历通讯录中的每个联系人。 -
打印联系人信息:对于每个联系人,使用
printf
函数打印其姓名、年龄、性别、电话和地址。这里的格式化字符串与表头相同,确保了输出的整齐对齐。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
、age
、sex
、addr
和tele
等成员。 - 使用
%s
格式化字符串打印字符串类型的数据,使用%d
格式化字符串打印整数类型的数据。 - 为了保持输出的整齐,每个字段的宽度应该根据实际数据的长度进行调整。例如,如果姓名通常不超过10个字符,那么
%-20s
可以改为%-10s
。 - 在实际应用中,可能需要对用户输入进行验证,以确保数据的有效性和安全性。
这段代码展示了如何在C语言中使用格式化输出来显示通讯录中的联系人信息。通过 ShowContact
函数,可以实现以表格形式整齐地显示所有联系人的详细信息。
5.查找指定联系人
void SearchContact(const Contact* pc)
{assert(pc);char name[NAME_MAX] = { 0 };printf("请输入要查找的联系人:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("要查找的人不存在\n");return;}printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n", pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].tele,pc->data[pos].addr);
}
这段代码定义了一个名为 SearchContact
的函数,其目的是在通讯录中搜索一个联系人并显示其信息。函数中的步骤如下:
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
输入联系人姓名:提示用户输入要查找的联系人的姓名,并使用
scanf
函数读取用户输入的字符串。 -
查找联系人:调用
FindByName
函数(假设已定义)查找联系人在通讯录中的位置。 -
检查联系人是否存在:如果
FindByName
返回-1
,表示联系人不存在,打印消息并返回。 -
打印表头:如果联系人存在,首先打印表头,包括姓名、年龄、性别、电话和地址。
-
打印联系人信息:然后打印找到的联系人的详细信息,包括姓名、年龄、性别、电话和地址。这里的格式化字符串与表头相同,确保了输出的整齐对齐。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
、age
、sex
、addr
和tele
等成员。 NAME_MAX
应该是一个预定义的常量,表示姓名的最大长度。FindByName
函数应该返回联系人在通讯录中的索引,如果未找到则返回-1
。- 使用
%s
格式化字符串打印字符串类型的数据,使用%d
格式化字符串打印整数类型的数据。 - 为了保持输出的整齐,每个字段的宽度应该根据实际数据的长度进行调整。
这段代码展示了如何在C语言中使用字符串操作和结构体来搜索和显示通讯录中的联系人信息。通过 SearchContact
函数,可以实现用户输入姓名后,程序查找并显示对应联系人的详细信息。
6.修改指定联系人
void ModifyContact(Contact* pc)
{assert(pc);char name[NAME_MAX] = { 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("%s", pc->data[pos].sex);printf("请输入联系人年龄 :>");scanf("%d", &pc->data[pos].age);printf("请输入联系人电话 :>");scanf("%s", pc->data[pos].tele);printf("请输入联系人住址 :>");scanf("%s", pc->data[pos].addr);printf("修改完成\n");
}
这段代码定义了一个名为 ModifyContact
的函数,其目的是修改通讯录中一个现有联系人的信息。函数中的步骤如下:
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
输入要修改的联系人姓名:提示用户输入要修改的联系人的姓名,并使用
scanf
函数读取用户输入的字符串。 -
查找联系人:调用
FindByName
函数(假设已定义)查找联系人在通讯录中的位置。 -
检查联系人是否存在:如果
FindByName
返回-1
,表示联系人不存在,打印消息并返回。 -
输入新的联系人信息:如果联系人存在,提示用户输入新的联系人姓名、性别、年龄、电话和住址,并使用
scanf
函数读取这些信息。 -
更新联系人信息:将用户输入的新信息存储到
pc->data[pos]
中,即更新通讯录中该联系人的信息。 -
打印完成消息:打印一条消息告知用户修改完成。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
、age
、sex
、addr
和tele
等成员。 NAME_MAX
应该是一个预定义的常量,表示姓名的最大长度。FindByName
函数应该返回联系人在通讯录中的索引,如果未找到则返回-1
。- 使用
%s
格式化字符串打印字符串类型的数据,使用%d
格式化字符串打印整数类型的数据。 - 在实际应用中,可能需要对用户输入进行验证,以确保数据的有效性和安全性,例如检查年龄是否为合理的数值,电话和地址是否符合预期格式等。
这段代码展示了如何在C语言中使用结构体和用户输入来修改通讯录中的联系人信息。通过 ModifyContact
函数,可以实现用户指定要修改的联系人,然后输入新的信息来更新该联系人的记录。
7.排序联系人
int CmpByAge(const void* a, const void* b)
{const PeoInfo* da = (const PeoInfo*)a;const PeoInfo* db = (const PeoInfo*)b;return da->age - db->age; // 升序排序
}int CmpByName(const void* e1, const void* e2)
{const PeoInfo* da = (const PeoInfo*)e1;const PeoInfo* db = (const PeoInfo*)e2;return(strcmp(da->name, db->name));
}
void SortContact(Contact* pc)
{assert(pc);if (pc->sz > 1) {//qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);//printf("已按年龄大小完成排序\n");qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);printf("已按姓名大小完成排序\n");}else {printf("通讯录中联系人不足两人,无需排序。\n");}
}
这段代码定义了两个比较函数 CmpByAge
和 CmpByName
,以及一个 SortContact
函数,用于对通讯录中的联系人进行排序。
CmpByAge
函数
- 这是一个用于比较两个
PeoInfo
结构体的函数,用于按年龄进行排序。 - 它接受两个
void
指针作为参数,这两个指针指向要比较的PeoInfo
结构体。 - 函数通过返回两个结构体中
age
成员的差值来实现比较。如果返回值小于0,则表示第一个参数应该排在第二个参数之前(升序排序)。
CmpByName
函数
- 这是一个用于比较两个
PeoInfo
结构体的函数,用于按姓名进行排序。 - 它同样接受两个
void
指针作为参数,这两个指针指向要比较的PeoInfo
结构体。 - 函数使用
strcmp
来比较两个结构体中name
成员的字符串。strcmp
返回0表示两个字符串相等,返回负值表示第一个字符串小于第二个字符串,返回正值表示第一个字符串大于第二个字符串。
SortContact
函数
- 这个函数用于对通讯录中的联系人进行排序。
- 它首先检查通讯录中的联系人数量是否大于1,因为至少需要两个联系人才能进行排序。
- 如果联系人数量大于1,它使用
qsort
函数进行排序。qsort
是C标准库中的一个函数,用于对数组进行快速排序。 qsort
需要四个参数:要排序的数组、数组中的元素数量、每个元素的大小以及一个比较函数。- 在这个例子中,
qsort
使用CmpByName
作为比较函数,按姓名对pc->data
数组进行排序。 - 排序完成后,打印一条消息告知用户排序已完成。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了name
和age
等成员。 qsort
函数的原型在<stdlib.h>
头文件中,因此需要在文件顶部包含这个头文件。- 在实际应用中,可能需要对用户输入进行验证,以确保数据的有效性和安全性。
- 这段代码中的
CmpByAge
函数被注释掉了,如果需要按年龄排序,可以取消注释并使用CmpByAge
作为qsort
的比较函数。
这段代码展示了如何在C语言中使用 qsort
函数和自定义的比较函数来对结构体数组进行排序。通过 SortContact
函数,可以实现按姓名或年龄对通讯录中的联系人进行排序
8.清空所有联系人
void ClearContact(Contact* pc)
{assert(pc);int i = 0;for (i = 0; i < pc->sz; i++){// 假设 PeoInfo 结构体中有动态分配内存的成员,需要释放它们free(pc->data[i].name);free(pc->data[i].addr);free(pc->data[i].tele);// 清零结构体pc->data[i] = (PeoInfo){ 0 };}pc->sz = 0; // 清空联系人数量printf("已清空所有联系人\n");
}
注意事项
- 确保
PeoInfo
结构体中的所有动态分配内存的成员都被正确释放,以避免内存泄漏。 - 在实际应用中,可能需要对
PeoInfo
结构体的定义进行调整,以确保所有动态分配的内存都能被正确管理。 - 清空操作应该在循环外部设置
pc->sz
为0,并打印清空消息。
这段代码展示了如何在C语言中清空一个通讯录结构体中的所有联系人信息,包括释放动态分配的内存。正确管理内存是防止内存泄漏和保持程序稳定性的关键。
9.销毁动态内存开辟的空间
void DestroyContact(Contact* pc)
{assert(pc);if (pc->sz == MAX)free(pc->data);pc->data = NULL;pc->capacity = 0;pc->sz = 0;pc = NULL;
}
这段代码定义了一个名为 DestroyContact
的函数,其目的是销毁通讯录中的所有联系人,并释放相关资源。函数中的步骤如下:
-
断言检查:使用
assert(pc);
确保传入的指针pc
不是NULL
。 -
检查联系人数量:如果
pc->sz
等于MAX
(假设MAX
是一个预定义的常量,表示通讯录的最大容量),则释放pc->data
指向的内存。 -
设置指针为
NULL
:将pc->data
设置为NULL
,表示通讯录中的联系人数据已被释放。 -
重置容量和数量:将
pc->capacity
和pc->sz
设置为0,表示通讯录的容量和当前存储的联系人数量都被重置。 -
设置
pc
为NULL
:最后,将传入的指针pc
设置为NULL
。
注意事项
- 在C语言中,函数参数是按值传递的,因此在函数内部对指针的修改不会影响到调用者。如果需要改变调用者的指针,应该使用指向指针的指针(即双重指针)。
- 在释放内存之前,应该检查指针是否为
NULL
,以避免重复释放或释放未分配的内存。 - 如果
pc->data
是通过calloc
或malloc
分配的,确保在释放之前没有其他指针指向这块内存,否则会导致悬空指针。
10.保存联系人到文件
void SaveContact(Contact* pc)
{FILE* pf = fopen("contact,txt", "wb");if (NULL == pf){perror("Savecontact");}else{int i = 0;for (i = 0; i < pc->sz; i++){fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}fclose(pf);pf = NULL;printf("保存成功\n");}
}
这段代码定义了一个名为 SaveContact
的函数,其目的是将通讯录中的所有联系人信息保存到一个名为 "contact.txt" 的文件中。函数中的步骤如下:
-
打开文件:使用
fopen
函数以二进制写入模式("wb")打开 "contact.txt" 文件。如果文件打开失败,fopen
将返回NULL
。 -
错误检查:如果文件打开失败,使用
perror
函数打印错误信息。 -
写入数据:如果文件成功打开,使用
fwrite
函数将通讯录中的每个PeoInfo
结构体写入文件。这里使用了一个for
循环遍历通讯录中的每个联系人。 -
关闭文件:完成写入后,使用
fclose
函数关闭文件。 -
设置指针为
NULL
:将pf
设置为NULL
,这是一个好习惯,可以避免悬空指针。 -
打印成功消息:打印一条消息告知用户保存成功。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了可以被写入文件的成员。 - 文件名 "contact.txt" 应该根据实际需要进行调整。注意,文件名中的逗号后面没有空格,如果需要在文件名中包含逗号和空格,应该使用引号将文件名括起来,例如
"contact, txt"
。 - 使用二进制模式写入文件可以确保数据的精确存储,但这意味着读取文件时也需要以二进制模式打开。
- 在实际应用中,可能需要对文件的读写权限进行检查,以确保程序有权限操作文件。
- 如果
PeoInfo
结构体中包含指针成员(如字符串),直接写入这些指针的值是没有意义的,因为它们只是内存地址。在这种情况下,应该写入指针指向的数据。
这段代码展示了如何在C语言中使用文件操作来保存通讯录中的联系人信息。通过 SaveContact
函数,可以实现将联系人数据持久化存储到文件中。
11.显现文件中的联系人
void LoadContact(Contact* pc)
{FILE* pf = fopen("contact,txt", "rb");if (NULL == pf){perror("LoadContact");}else{PeoInfo tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(PeoInfo), 1, pf)){check_capacity(pc);pc->data[i] = tmp;pc->sz++;i++;}fclose(pf);pf = NULL;}
}
这段代码定义了一个名为 LoadContact
的函数,其目的是从名为 "contact.txt" 的文件中加载联系人信息到通讯录中。函数中的步骤如下:
-
打开文件:使用
fopen
函数以二进制读取模式("rb")打开 "contact.txt" 文件。如果文件打开失败,fopen
将返回NULL
。 -
错误检查:如果文件打开失败,使用
perror
函数打印错误信息。 -
读取数据:如果文件成功打开,使用
fread
函数从文件中读取PeoInfo
结构体。这里使用了一个while
循环,它会一直读取直到文件结束。 -
检查容量:在每次读取新的联系人信息之前,调用
check_capacity
函数确保通讯录有足够的空间存储新的联系人。 -
存储联系人信息:将读取的联系人信息存储到
pc->data
数组中,并更新pc->sz
以反映通讯录中的联系人数量。 -
关闭文件:完成读取后,使用
fclose
函数关闭文件。 -
设置指针为
NULL
:将pf
设置为NULL
,这是一个好习惯,可以避免悬空指针。
注意事项
- 这段代码假设
Contact
结构体和PeoInfo
结构体已经定义,并且包含了可以被读取和写入文件的成员。 - 文件名 "contact.txt" 应该根据实际需要进行调整。注意,文件名中的逗号后面没有空格,如果需要在文件名中包含逗号和空格,应该使用引号将文件名括起来,例如
"contact, txt"
。 - 使用二进制模式读取文件可以确保数据的精确读取,但这意味着写入文件时也需要以二进制模式打开。
check_capacity
函数应该能够处理通讯录的动态扩容,以确保在添加新联系人时有足够的空间。- 在实际应用中,可能需要对文件的读写权限进行检查,以确保程序有权限操作文件。
- 如果
PeoInfo
结构体中包含指针成员(如字符串),直接读取这些指针的值是没有意义的,因为它们只是内存地址。在这种情况下,应该读取指针指向的数据。
这段代码展示了如何在C语言中使用文件操作来加载通讯录中的联系人信息。通过 LoadContact
函数,可以实现从文件中恢复联系人数据。
四.运行效果及源码
还有许多功能就不一一演示了,感兴趣的小伙伴可以自己尝试写一写或者再实现一些其他功能。
//"contact.h"
#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 30
#define TELE_MAX 12
#define DEFAULT 3
#define INC_SZ 2typedef struct PeoInfo
{char name[NAME_MAX];char sex[SEX_MAX];char addr[ADDR_MAX];char tele[TELE_MAX];int age;
}PeoInfo;//typedef struct Contact
//
//{
// PeoInfo data[MAX];
// int sz;
//}Contact;typedef struct Contact
{//PeoInfo data[MAX];PeoInfo* data;int sz;int capacity;}Contact;
//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//删除指定联系人
void DelContact(Contact* pc);//显示通讯录中的信息
void ShowContact(const Contact* pc);//查找指定联系人
void SearchContact(const Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);
//排序联系人
void SortContact(Contact* pc);
//清空联系人
void ClearContact(Contact* pc);
//销毁动态内存创建的空间
void DestroyContact(Contact*pc);
//保存联系人
void SaveContact(Contact* pc);
//加载通讯录信息
void LoadContact(Contact* pc);//contact.c#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"void InitContact(Contact* pc)//自己
{assert(pc);pc->sz = 0;//memset(pc->data, 0, sizeof(pc->data));//PeoInfo* ptr = NULL;PeoInfo* ptr = (PeoInfo*)calloc(DEFAULT, sizeof(PeoInfo));if (ptr == NULL){perror("InitContact::calloc");return;}pc->data = ptr;pc->capacity = DEFAULT;LoadContact(pc);
}void check_capacity(Contact* pc)
{if (pc->sz == pc->capacity){printf("通讯录已满,无法添加\n");//return;//增加容量PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){perror("check_capacity::realloc");return;}pc->data = ptr;pc->capacity += INC_SZ;printf("增容成功\n");}
}
//动态版本
void AddContact(Contact* pc)
{assert(pc);check_capacity(pc);//增加一个人的信息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].addr);printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);pc->sz++;
}
void DestroyContact(Contact* pc)
{assert(pc);if (pc->sz == MAX)free(pc->data);pc->data = NULL;pc->capacity = 0;pc->sz = 0;pc = NULL;
}int FindByName(const Contact* pc, char name[]);void DelContact(Contact* pc)
{assert(pc);char name[NAME_MAX] = { 0 };if (pc->sz == 0){printf("通讯录为空,无法操作\n");return;}printf("请输入要删除联系人的姓名:>");scanf("%s", name);int ret = FindByName(pc, name);if (ret == -1){printf("要删除的联系人不存在\n");return;}int i = 0;for (i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功\n");
}
int FindByName(const Contact* pc, char name[])
{assert(pc);int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0){return i;}return -1;}}
void ShowContact(const Contact* pc)
{assert(pc);int i = 0;printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].tele,pc->data[i].addr);}
}
void SearchContact(const Contact* pc)
{assert(pc);char name[NAME_MAX] = { 0 };printf("请输入要查找的联系人:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("要查找的人不存在\n");return;}printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\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[NAME_MAX] = { 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("%s", pc->data[pos].sex);printf("请输入联系人年龄 :>");scanf("%d", &pc->data[pos].age);printf("请输入联系人电话 :>");scanf("%s", pc->data[pos].tele);printf("请输入联系人住址 :>");scanf("%s", pc->data[pos].addr);printf("修改完成\n");
}// 比较函数,按年龄排序
int CmpByAge(const void* a, const void* b)
{const PeoInfo* da = (const PeoInfo*)a;const PeoInfo* db = (const PeoInfo*)b;return da->age - db->age; // 升序排序
}int CmpByName(const void* e1, const void* e2)
{const PeoInfo* da = (const PeoInfo*)e1;const PeoInfo* db = (const PeoInfo*)e2;return(strcmp(da->name, db->name));
}
void SortContact(Contact* pc)
{assert(pc);if (pc->sz > 1) {//qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);//printf("已按年龄大小完成排序\n");qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);printf("已按姓名大小完成排序\n");}else {printf("通讯录中联系人不足两人,无需排序。\n");}
}void ClearContact(Contact* pc)
{assert(pc);char arr[1] = { 0 };int i = 0;for (i = 0; i < pc->sz; i++){{pc->data[i] = (PeoInfo){ 0 };}pc->sz = 0;printf("已清空所有联系人\n");}
}void SaveContact(Contact* pc)
{FILE* pf = fopen("contact,txt", "wb");if (NULL == pf){perror("Savecontact");}else{int i = 0;for (i = 0; i < pc->sz; i++){fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}fclose(pf);pf = NULL;printf("保存成功\n");}
}
void LoadContact(Contact* pc)
{FILE* pf = fopen("contact,txt", "rb");if (NULL == pf){perror("LoadContact");}else{PeoInfo tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(PeoInfo), 1, pf)){check_capacity(pc);pc->data[i] = tmp;pc->sz++;i++;}fclose(pf);pf = NULL;}
}//test.c
#include"contact.h"void menu()
{printf("***********通讯录************\n");printf("*******1.add 2.del*******\n");printf("*******3.serc 4.mod*******\n");printf("*******5.show 6.sort******\n");printf("*******7.clear 0.exit******\n");printf("*****************************\n");}
enum Option
{EXIT,ADD,DEL,SEARCH,MODIFY,SHOW,SORT,CLEAR};
int main()
{int input = 0;Contact con;InitContact(&con);do{menu();printf("请选择功能\n");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;case CLEAR:ClearContact(&con);break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}