运用C语言实现一个简单的通讯录管理系统,要求对数据有 增删改查清排显 等功能的实现(这里由于还没学到文件,所以下面所有的存储都是在内存中,也就是当程序结束的时候添加的信息都会清空掉 - 后续会加以改进的)
在基本的程序设计中,都会将项目分为三个文件:
- test.c - 主函数(负责调用各个功能的接口)
- contact.c - 实现各个模块的功能
- contact.h - 用于存放头文件、宏定义、函数声明等
#include
#include
#include
#include
#include
#include
#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 50
#define TELE_MAX 12
#define DEFAULT_SZ 3
// 数组版本
//typedef struct PeoInfo
//{
// char name[NAME_MAX];
// int age;
// char sex[SEX_MAX];
// char addr[ADDR_MAX];
// char tele[TELE_MAX];
//}PeoInfo;
//
//typedef struct Contact
//{
// //PeoInfo data[MAX]; // 存放信息
// PeoInfo data[MAX];
// int sz; // 存放通讯录有几个人的信息
//}Contact;
// 每个人的基本信息
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char addr[ADDR_MAX];
char tele[TELE_MAX];
}PeoInfo;
typedef struct Contact
{
//PeoInfo data[MAX]; // 存放信息
PeoInfo* data;
int sz; // 存放通讯录有几个人的信息
int capacity; // 记录当前通讯录的最大容量
}Contact;
void DestoryContact(Contact* pc);
void InitContact(Contact* pc);
void AddContact(Contact* pc);
void ShowContact(const Contact* pc);
void DeleteContact(Contact* pc);
void SearchContact(const Contact* pc);
void ModifyContact(Contact* pc);
void SortContact(Contact* pc);
里面被注释的代码则是之前的数组版本,规定死了通讯录大小;而下面我们会用动态增容来进行实现。头文件里面用宏定义主要为了之后我们更改对应的大小的时候更加的方便;结构体主要存放的就是
三大部分: 1. 一个指向每个人基本信息的结构体指针;2. sz记录着通讯录的记录条数;3. capacity 存放通讯录的容量
int main()
{
int input = 0;
Contact con; // 创建通讯录
InitContact(&con);
do
{
menu();
printf("请选择:->");
scanf("%d", &input);
switch (input)
{
case ADD:
AddContact(&con);
break;
case DEL:
DeleteContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SORT:
SortContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case EXIT:
DestoryContact(&con);
printf("退出\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
一般函数的主函数里面都可以用do ……while结构来控制程序的执行顺序,在case后面用的则是 枚举常量 ,这样能做到更好的见名知意。
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
PeoInfo* tmp = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
if (tmp != NULL)
{
pc->data = tmp;
}
else
{
printf("InitContact()::%s\n", strerror(errno));
}
pc->capacity = DEFAULT_SZ;
}
这里设定了通讯录的起始大小,后面可以通过动态增容来扩大(而数组版本的话就没有这么灵活了)
void check_capacity(Contact* pc)
{
assert(pc);
if (pc->sz == pc->capacity)
{
// 增容
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2)*sizeof(PeoInfo));
if (tmp != NULL)
{
pc->data = tmp;
pc->capacity += 2;
printf("增容成功\n");
}
else
{
printf("InitContact()::%s\n", strerror(errno));
}
}
}
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++;
printf("添加成功\n");
}
这里实现了一个check_capacity 的接口,用来判断当前通讯录是否存满了;若满了则进行扩容(一般按2倍进行扩容),
注意:添加成功后需要对 sz++
int FindByName(const Contact* pc, char* name)
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void DeleteContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
// 根据输入的人名进行删除
printf("请输入要删除的人名:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的人不存在\n");
}
else
{
// 删除
//int j = 0;
//for (j = pos; j < pc->sz-1; j++) // sz-1为了防止j+1非法访问
//{
// pc->data[j] = pc->data[j + 1];
//}
//pc->sz--;
//printf("删除成功\n");
//if (pc->sz>1)
memmove((pc->data) + pos, (pc->data) + pos + 1, (pc->sz - pos)*sizeof(PeoInfo));
pc->sz--;
}
}
这里我就只做了按照名字进行删除,你们也可以都尝试一下哟。下面写了两种删除方法(这里的按名字删除前需要进行查找,因此我们也可以直接使用我们下面要实现的查找函数,但如果查找里面进行了其他功能,那时不能直接使用的哟)
void SearchContact(const Contact* pc)
{
char name[NAME_MAX] = { 0 };
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("请输入要查找的姓名:");
scanf("%s", name);
int i = FindByName(pc, name);
if(-1 == i)
{
// 没有找到
printf("你要查找的人不存在\n");
}
else
{
// 找到了
printf("%-10s\t%-5s\t%-5s\t%-20s\t%-13s\n",
"姓名", "年龄", "性别", "地址", "电话");
printf("%-10s\t%-5d\t%-5s\t%-20s\t%-13s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele);
}
}
这里如果查找到了后,我打印显示了一下
void ModifyContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("请输入要修改信息的姓名:");
scanf("%s", name);
int i = FindByName(pc, name);
// 修改内容
printf("新的名字:");
scanf("%s", pc->data[i].name);
printf("新的年龄:");
scanf("%d", &(pc->data[i].age));
printf("新的性别:");
scanf("%s", pc->data[i].sex);
printf("新的地址:");
scanf("%s", pc->data[i].addr);
printf("新的电话:");
scanf("%s", pc->data[i].tele);
printf("修改后的信息为:\n");
printf("%-10s\t%-5s\t%-5s\t%-20s\t%-13s\n", "姓名", "年龄", "性别", "地址", "电话");
printf("%-10s\t%-5d\t%-5s\t%-20s\t%-13s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele);
}
void ShowContact(const Contact* pc)
{
assert(pc);
// 打印标题
printf("%-10s\t%-5s\t%-5s\t%-20s\t%-13s\n", "姓名", "年龄", "性别", "地址", "电话");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-5d\t%-5s\t%-20s\t%-13s\n",
pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].addr, pc->data[i].tele);
}
}
直接遍历打印即可
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
int cmp_by_age(const void* e1, const void* e2)
{
return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
// return strcmp(, ((PeoInfo*)e2)->name);
}
void SortContact(Contact* pc)
{
int choice = 0;
printf("请选择:(1、按姓名;2、按年龄)");
scanf("%d", &choice);
if (1 == choice)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
printf("排序后的数据:\n");
ShowContact(pc);
}
else if (2 == choice)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);
printf("排序后的数据:\n");
ShowContact(pc);
}
else
{
printf("输入有误\n");
}
}
这里使用qsort实现的排序,可以对 姓名 或 年龄进行排序的
void DestoryContact(Contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
}
直接将里面指向有效内容的 pc->data 进行free释放就行,因为它本来就是动态开辟的,后面要有个好习惯,free之后将其置为NULL,不然就为野指针了。
以上就是动态增容版本通讯录的全部代码,尽管用了动态增容,里面仍然会存在很多的空间浪费,后面等博主学了链表后,在进行改进。