C语言实现可排序通讯录:柔性数组与动态扩容详解
本文使用C语言构建一个包含增删改查、清空及排序功能的通讯录系统。通过结构体与柔性数组技术实现数据的连续存储,结合动态内存管理(malloc和realloc)解决容量限制问题,并利用标准库函数qsort实现快速排序,提供完整的头文件、源文件及主程序代码。
设计思路与目标
本实例旨在实现一个支持多种操作的命令行通讯录。核心功能包括:
- 显示菜单界面
- 添加联系人(需检测重复及内存不足时的自动扩容)
- 删除联系人
- 查找联系人
- 修改联系人信息
- 显示所有联系人列表
- 清空当前通讯录
- 按姓名对联系人进行排序
数据结构与头文件定义
在开始编写逻辑前,需要定义联系人的基本属性以及通讯录容器的结构。这里使用person_t存储单个联系人信息,使用contact_t作为容器结构体。
关键设计点:
- 使用宏定义固定字段长度,如姓名最大32字节,电话16字节等。
- 通讯录结构体尾部使用
person_t friends[0],这是C99标准中的柔性数组(Flexible Array Member),允许在分配内存时只指定结构体头部大小,后续紧接存放数组元素,避免指针间接访问,提高缓存命中率。 - 引入
cap表示总容量,size表示当前已用数量,用于判断是否满员。
#ifndef _CONTACT_H_
#define _CONTACT_H_
// 定义各字段最大长度
#define NAME_SIZE 32
#define SEX_SIZE 8
#define TELPHONE_SIZE 16
#define ADDRESS_SIZE 128
#include <stdio.h>
#include <Windows.h>
#include <assert.h>
#pragma warning(disable:4996)
// 初始容量设定
#define INIT_NUM 1000 // 初始分配空间
#define INC_SIZE 5 // 每次扩容增加的数量
// 个人联系人结构体
typedef struct person {
char name[NAME_SIZE];
char sex[SEX_SIZE];
int age;
char telphone[TELPHONE_SIZE];
char address[ADDRESS_SIZE];
} person_t;
// 通讯录容器结构体(使用柔性数组)
typedef struct contact {
FILE *save;
int cap; // 总容量
int size; // 当前有效人数
person_t friends[0]; // 柔性数组成员
} contact_t;
// 函数声明
void InitContact(contact_t **ct);
void AddFriend(contact_t **ct);
void ShowContact(contact_t *ct);
void ModFriend(contact_t *ct);
void SearchFriend(contact_t *ct);
void ClearContact(contact_t *ct);
void DelFriend(contact_t *ct);
void SortContact(contact_t *ct);
#endif
初始化与动态扩容机制
通讯录创建时需分配内存,当空间不足时需进行扩容。由于使用了柔性数组,不能直接对数组指针调用realloc,而是对整个结构体指针进行重分配。
第1步:初始化通讯录
目的:分配初始内存并重置计数器。
入口:InitContact函数
动作:使用malloc分配sizeof(contact_t) + INIT_NUM * sizeof(person_t)大小的内存。若分配失败则报错退出;成功则设置size=0,cap=INIT_NUM。
void InitContact(contact_t **ct) {
*ct = (contact_t *)malloc(sizeof(contact_t) + INIT_NUM * sizeof(person_t));
if (*ct == NULL) {
perror("malloc");
exit(1);
}
(*ct)->size = 0;
(*ct)->cap = INIT_NUM;
}
第2步:实现自动扩容
目的:当通讯录已满且用户尝试添加新人时,扩大存储空间。
动作:使用realloc调整内存块大小,新大小为sizeof(contact_t) + ((*)->cap + INC_SIZE) * sizeof(person_t)。若realloc返回空指针,说明内存不足,返回0;否则更新指针并增加cap值。
static int Inc(contact_t **ct) {
assert(ct);
contact_t *ct_temp = (contact_t *)realloc(*ct, sizeof(contact_t) + ((*ct)->cap + INC_SIZE) * sizeof(person_t));
if (ct_temp == NULL) {
return 0;
}
*ct = ct_temp;
(*ct)->cap += INC_SIZE;
printf("自动扩容成功\n");
return 1;
}
第3步:辅助检查函数
判断是否已满:IsFull,比较size与cap。
判断是否为空:IsEmpty,检查size是否为0。
static int IsFull(contact_t *ct) { return ct->cap == ct->size; }
static int IsEmpty(contact_t *ct) { return ct->size == 0; }
核心业务逻辑实现
第4步:添加联系人
目的:向通讯录中插入新记录,并处理去重和扩容。
流程:先输入信息构造临时变量p,检查是否存在同名联系人(防止重复)。若未满或扩容成功,则将数据复制到通讯录中,并增加size。
void AddFriend(contact_t **ct) {
assert(ct);
// 若未满 或 扩容成功,才执行插入
if (!IsFull(*ct) || Inc(ct)) {
person_t p;
printf("请输入新增用户的姓名: "); scanf("%s", p.name);
printf("请输入新增用户的性别: "); scanf("%s", p.sex);
printf("请输入新增用户的年纪: "); scanf("%d", &(p.age));
printf("请输入新增用户的电话: "); scanf("%s", p.telphone);
printf("请输入新增用户的地址: "); scanf("%s", p.address);
// 查重
if (IsExist(*ct, &p)) {
printf("%s 已经存在,请不要重复插入\n", p.name);
return;
}
memcpy((*ct)->friends + (*ct)->size, &p, sizeof(p));
(*ct)->size += 1;
printf("新增用户%s成功\n", p.name);
} else {
printf("扩容失败\n");
}
}
第5步:查找联系人
目的:根据姓名检索联系人并打印详情。
动作:调用内部辅助函数SearchCore遍历数组,若找到返回索引,否则返回-1。主函数根据索引打印格式化表格或提示不存在。
// 核心查找逻辑
static int SearchCore(contact_t *ct, const char *name) {
assert(ct);
for (int i = 0; i < ct->size; i++) {
if (strcmp(name, ct->friends[i].name) == 0) {
return i;
}
}
return -1;
}
void SearchFriend(contact_t *ct) {
assert(ct);
char name[NAME_SIZE];
printf("请输入你要查找的人的姓名: ");
scanf("%s", name);
int i = SearchCore(ct, name);
printf("|%-10s|%-10s|%-10s|%-10s|%10s|\n", "姓名", "性别", "年纪", "电话", "地址");
if (i >= 0) {
person_t *p = ct->friends + i;
printf("| %-10s | %-10s | %-10d | %-10s | %10s | \n", p->name, p->sex, p->age, p->telphone, p->address);
} else {
printf("你要查找的人%s不存在\n", name);
}
}
第6步:修改联系人
目的:定位到特定联系人后覆盖其字段。
注意:需先获取目标索引,直接修改指针指向的结构体成员即可,无需再次复制。
void ModFriend(contact_t *ct) {
assert(ct);
char name[NAME_SIZE];
printf("请输入你要修改的人的姓名: ");
scanf("%s", name);
int i = SearchCore(ct, name);
if (i >= 0) {
person_t *p = ct->friends + i;
printf("请输入修改后的姓名: "); scanf("%s", p->name);
printf("请输入修改后的性别: "); scanf("%s", p->sex);
printf("请输入修改后的年纪: "); scanf("%d", &(p->age));
printf("请输入修改后的电话: "); scanf("%s", p->telphone);
printf("请输入修改后的地址: "); scanf("%s", p->address);
printf("修改用户%s成功\n", p->name);
} else {
printf("你要修改的联系人%s不存在\n", name);
}
}
第7步:删除与清空
删除策略:为简化操作,不保持原有顺序,直接将最后一个元素的数据覆盖到待删除元素的位置,然后将size减1。这是一种高效的删除方式。
清空策略:只需将size设为0,物理内存保持不变,下次添加可直接复用。
void DelFriend(contact_t *ct) {
assert(ct);
char name[NAME_SIZE];
printf("请输入你要删除的人的姓名: ");
scanf("%s", name);
int i = SearchCore(ct, name);
if (i >= 0) {
ct->friends[i] = ct->friends[ct->size - 1];
ct->size -= 1;
printf("删除成功\n");
} else {
printf("你要删除的人%s不存在\n", name);
}
}
void ClearContact(contact_t *ct) {
assert(ct);
ct->size = 0;
}
排序功能实现
C语言标准库提供了qsort函数用于快速排序。我们需要提供一个比较函数,该函数接收两个const void *类型的参数。
static int CmpPerson(const void *p1, const void *p2) {
assert(p1 && p2);
person_t *_p1 = (person_t *)p1;
person_t *_p2 = (person_t *)p2;
// 按姓名字符串字典序排序
return strcmp(_p1->name, _p2->name);
}
void SortContact(contact_t *ct) {
assert(ct);
if (!IsEmpty(ct)) {
qsort(ct->friends, ct->size, sizeof(person_t), CmpPerson);
}
}
显示通讯录
遍历size个元素,按表头格式输出每一项的信息,便于用户查看当前状态。
void ShowContact(contact_t *ct) {
assert(ct);
printf("|cap:%d|size:%d|\n", ct->cap, ct->size);
printf("|%-10s|%-10s|%-10s|%-10s|%10s|\n", "姓名", "性别", "年纪", "电话", "地址");
for (int i = 0; i < ct->size; i++) {
person_t *p = ct->friends + i;
printf("|%-10s|%-10s|%-10d|%-10s|%10s|\n", p->name, p->sex, p->age, p->telphone, p->address);
}
}
主程序交互流程
主函数中构建无限循环,调用菜单函数展示选项,并根据用户输入的整数分支调用对应的业务函数。
#include "contact.h"
void ShowMenu() {
printf("#######################\n");
printf("# 1.Add 2.Del 3.Search #\n");
printf("# 4.Mod 5.Show 6.Clear #\n");
printf("# 7.Sort 0.Exit #\n");
printf("#######################\n");
printf("please select: ");
}
int main() {
contact_t *ct = NULL;
InitContact(&ct);
int quit = 0;
while (!quit) {
int select = 0;
ShowMenu();
scanf("%d", &select);
switch (select) {
case 1: AddFriend(&ct); break;
case 2: DelFriend(ct); break;
case 3: SearchFriend(ct); break;
case 4: ModFriend(ct); break;
case 5: ShowContact(ct); break;
case 6: ClearContact(ct); break;
case 7: SortContact(ct); break;
case 0: return 0;
default: break;
}
}
system("pause");
return 0;
}
以上就是C语言实现可排序通讯录的详细内容,更多关于C语言数据结构开发的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













