C语言通讯录
文章目录
1.需求分析
首先我们要自己写一个通讯录,我们就要先知道通讯录大概包括哪些内容。
这里我们就设计一个最简单的版本,可以容纳1000个人的静态通讯录。
通讯录中要包含:联系人名字、年龄、性别、电话、住址五个基本信息。
我们对通讯录的操作无非就是增、删、查、改四大类。
所以接下来我们要实现以下几个模块:
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息
- 修改指定联系人信息
- 显示所有联系人信息
- 清空所有联系人
- 排序
综上我们采用分为三个文件进行编写
test.c
用于测试程序。
contact.h
用于头文件的引用,类型定义,函数声明等。
contact.c
用于实现程序的主要业务代码等。
2. 具体实现
2.1 准备工作
当我们得到以上信息时就可以逐步开始实现我们的通讯录了。
首先通讯录要包含:联系人名字、年龄、性别、电话、住址五个基本信息。显而易见我们要定义一个结构体来表示一个人的信息
// 联系人的信息
typedef struct PeoInfo
{
char name[10];
int age;
char sex[5];
char tele[12];
char addr[30];
} PeoInfo;
我们这样写真的是好的写法吗?就比如名字我们只给了10个字符,如果有的名字超过了10个字符,那我们程序不就错了吗?
所以我们采用定义符号常量的方法,来便于以后不够了对其进行修改。
#define NAME_MAX 10 // 名字最大容量
#define SEX_MAX 5 // 性别最大容量
#define TELE_MAX 12 // 电话最大容量
#define ADDR_MAX 30 // 地址最大容量
// 联系人的信息
typedef struct PeoInfo
{
char name[NAME_MAX]; // 名字
int age; // 年龄
char sex[SEX_MAX]; // 性别
char tele[TELE_MAX]; // 电话
char addr[ADDR_MAX]; // 住址
} PeoInfo;
这时我们已经定义好了每个联系人的结构体类型,可以创建我们的数组了。
#define MAX_SIZE 1000 // 通讯录最大容量
PeoInfo con[MAX_SIZE]; // 存储1000个人的信息
这样写总觉得少了些什么?我们并不知道此时通讯录中到底有多少数据。这里有人可能直接说那还不简单,我再定义一个变量 sz 来表示当前通讯录中有效信息的个数不就可以了吗? 这个办法可以是可以,但是欠缺考虑。比如我们在对函数传参时 ,不仅要把结构体传过去,还要单独再传一个 sz表示当前通讯录中有效信息的个数 ,这就比较麻烦了。
所以我们选择再封装一个结构体类型来表示通讯录。
// 通讯录
typedef struct contact
{
PeoInfo data[MAX_SIZE]; // 存放添加进来的人的信息
int sz; // 记录的是当前通讯录中有效信息的个数
} contact;
这下我们终于大功告成可以创建我们的通讯录了。
contact con;
2.2 初始化通讯录
通讯录创建好之后,我们要先对其进行初始化之后才可以正常使用,不然里面存储的全部都随机值。
我们创建一个函数来初始化通讯录,对于结构体而言我们普遍采用传地址的方式来实现,并不是说传值调用不可以,只是不好,在传值调用时形参是实参的一份临时拷贝,修改形参的值不会影响实参,传值调用只适用于不对结构体进行修改的函数。同时当我们的结构体非常大时,在函数传参时会造成极大的开销,不像我们传址调用,仅传递一个指向该结构体的指针。
// 初始化通讯录
void InitContact(contact* pc)
{
assert(pc); // 断言保证指针不为空
memset(pc->data, 0, sizeof(pc->data)); // 将数组中每个元素置为0
pc->sz = 0;
}
函数的实现就比较简单了,将所有变量全部置为0即可。
到此准备步骤已经全部完成了,让我们来开始实现具体细节吧!
首先程序一开始执行应该给我们显示一个菜单来供我们来选择进行什么操作。
// 打印菜单
void menu()
{
printf("*****************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.modify *****\n");
printf("***** 5.print 6.remove *****\n");
printf("***** 7.sort 0.exit *****\n");
printf("*****************************\n");
}
接下来我们就根据输入的值,进行判断执行什么操作,因为可能并不只使用一次,所以我们要写一个循环。
enum Option
{
EXIT, // 0
ADD, // 1
DEL, // 2
SEARCH, // 3
MODIFY, // 4
PRINT, // 5
REMOVE, // 6
SORT // 7
};
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 PRINT:
PrintContact(&con); // 显示
break;
case REMOVE:
RemoveContact(&con); // 清除
break;
case SORT:
SortContact(&con); // 排序
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
这里的 switch … case 语句中的 case 语句表达示使用枚举常量来代替。增强了程序的可读性。
接下来就是一步一步实现各个模块
2.3 增加一个联系人
// 增加联系人
void AddContact(contact* pc)
{
assert(pc); // 断言保证指针不为空
if (pc->sz == MAX_SIZE)
{
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");
}
在增加新的联系人之前,我们应先判断当前通讯录是否已满,如果已满,提示用户无法添加,函数直接结束即可。
未满则添加一条新记录,我们的 sz (sz 从0开始)正好指向当前应添加记录的下标,直接输入对应数据,添加成功后,sz++ 表示当前通讯录新增了一条数据。
同时也可以提醒一下用户,添加成功。
2.4 打印通讯录
// 打印联系人信息
void PrintContact(const contact* pc)
{
assert(pc);
int i = 0;
// 打印标题
printf("%-15s %-5s %-5s %-11s %-30s\n", "名字", "年龄", "性别", "电话", "地址");
// 打印记录
for (i = 0; i < pc->sz; i++)
{
printf("%-15s %-5d %-5s %-11s %-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
添加成功后我们就可以打印出来看看对不对,打印就比较简单了,直接遍历所有记录打印出来即可,注意在打印数据之前,应先把标题打印出来。关于格式大家可自行调整为自己喜欢的样子。
这里给大家一个建议,在我们写这种模块化程序时,我们最好能够,写好一个模块,就测试一个模块,不要等到最后,整个程序都写完了,程序一运行几十个 bug,到底错在了哪里也不知道,会花费大量时间来调试程序,搞的自己一头雾水。写好一个模块后,立即测试,如果有问题,我们可以迅速定位 bug 出现了什么地方,大大减少了我们调试程序的时间。
2.5 删除一个联系人
// 删除联系人
void DelContact(contact* pc)
{
assert(pc);
char name[NAME_MAX] = { 0 };
printf("请输入要删除人的名字:>");
scanf("%s", name);
// 查找要删除的联系人
int pos = FindByName(pc, name);
// 没有找到
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
// 找到了,删除
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1]; // 从后向前覆盖
}
pc->sz--;
printf("删除成功\n");
}
删除一个联系人我们是通过查找名字的方式, 查找程序中第一次出现这个名字的位置,找不到,则通讯录查无此人,找到了,后面的数据依次往前移动,之后 sz–,将当前通讯录中的记录减少一条。同时提示用户删除成功
这里我们想到删除需要查找联系人,修改、查询同时也需要通过名字来查找指定联系人,这时我们就再封装一个函数 FindByName 通过名字查找指定联系人是否存在,存在返回对应下标,不存在返回 -1。
// 通过名字查找,指定联系人
// 找到了返回对应下标
// 找不到返回 -1
static 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; // 找不到
}
2.6 查找指定联系人
// 查找指定联系人
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("%-15s %-5s %-5s %-11s %-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-15s %-5d %-5s %-11s %-30s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
通过前面实现的函数,查找指定联系人也变得很简单,先按名字查找看是否存在,没有则提示用户,找不到。找到了直接打印出指定记录即可,打印数据的代码基本和打印通讯录的代码相似,直接拷贝过来修改一下就可以了。
2.7 修改指定联系人
// 修改指定联系人
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("请输入要修改什么信息 (1-名字,2-年龄,3-性别,4-电话,5-地址):>");
int msg = 0;
scanf("%d", &msg);
switch (msg)
{
case 1:
printf("请输入新的名字:>");
scanf("%s", pc->data[pos].name);
break;
case 2:
printf("请输入新的年龄:>");
scanf("%d", &(pc->data[pos].age));
break;
case 3:
printf("请输入新的性别:>");
scanf("%s", pc->data[pos].sex);
break;
case 4:
printf("请输入新的电话:>");
scanf("%s", pc->data[pos].tele);
break;
case 5:
printf("请输入新的地址:>");
scanf("%s", pc->data[pos].addr);
break;
default:
printf("输入错误,修改失败\n");
return;
break;
}
printf("修改成功\n");
}
同理,先按名字查找,看看联系人是否存在,不存在提示用户,找不到,程序返回。找到了则询问用户要修改什么信息,输入信息代码基本和增加一个联系人的代码相同,拷贝过来修改一下即可。
2.8 清空所有联系人
// 清空联系人
void RemoveContact(contact* pc)
{
assert(pc);
InitContact(pc);
}
直接调用我们的初始化通讯录函数即可,数据全部被清空。
2.9 排序
// 按名字升序
static int cmp_name(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name) < 0)
return -1;
else
return 0;
}
// 按年龄升序
static int cmp_age(const void* e1, const void* e2)
{
return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}
// 按性别升序
static int cmp_sex(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->sex, ((PeoInfo*)e2)->sex) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->sex, ((PeoInfo*)e2)->sex) < 0)
return -1;
else
return 0;
}
// 按电话升序
static int cmp_tele(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->tele, ((PeoInfo*)e2)->tele) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->tele, ((PeoInfo*)e2)->tele) < 0)
return -1;
else
return 0;
}
// 按住址升序
static int cmp_addr(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->addr, ((PeoInfo*)e2)->addr) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->addr, ((PeoInfo*)e2)->addr) < 0)
return -1;
else
return 0;
}
// 按任意类型排序
void SortContact(contact* pc)
{
assert(pc);
int input = 0;
// 函数指针数组,存储对应用于排序的函数指针
int(*p[])(const void*, const void*) = {0, cmp_name, cmp_age, cmp_sex, cmp_tele, cmp_addr};
printf("请输入您要按什么排序 (1-名字,2-年龄,3-性别,4-电话,5-地址):>");
scanf("%d", &input);
if (input >= 1 && input <= 5)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), p[input]); // 调用库函数
PrintContact(pc); // 打印显示出来
}
else
{
printf("选择错误\n");
}
}
这里我们实现了对任意一个信息排序的功能,当然为了简单也可以只实现按照名字排序,代码基本都一样的。
排序我们直接调用了库函数 qsort 来实现,同样我们也可以自己写一个函数来实现。这里就不详细介绍了。
至此一个简单的通讯录程序已经实现了。
3. 完整代码
contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#define MAX_SIZE 1000 // 通讯录最大容量
#define NAME_MAX 10 // 名字最大容量
#define SEX_MAX 5 // 性别最大容量
#define TELE_MAX 12 // 电话最大容量
#define ADDR_MAX 30 // 地址最大容量
// 类型定义
enum Option
{
EXIT, // 0
ADD, // 1
DEL, // 2
SEARCH, // 3
MODIFY, // 4
PRINT, // 5
REMOVE, // 6
SORT // 7
};
// 联系人的信息
typedef struct PeoInfo
{
char name[NAME_MAX]; // 名字
int age; // 年龄
char sex[SEX_MAX]; // 性别
char tele[TELE_MAX]; // 电话
char addr[ADDR_MAX]; // 住址
} PeoInfo;
// 通讯录
typedef struct contact
{
PeoInfo data[MAX_SIZE]; // 存放添加进来的人的信息
int sz; // 记录的是当前通讯录中有效信息的个数
} contact;
// 函数声明
// 初始化通讯录
void InitContact(contact* pc);
// 增加联系人
void AddContact(contact* pc);
// 删除联系人
void DelContact(contact* pc);
// 查找指定联系人
void SearchContact(const contact* pc);
// 修改指定联系人
void ModifyContact(contact* pc);
// 打印联系人信息
void PrintContact(const contact* pc);
// 清空联系人
void RemoveContact(contact* pc);
// 排序
void SortContact(contact* pc);
test.c
#include "contact.h"
// 打印菜单
void menu()
{
printf("*****************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.modify *****\n");
printf("***** 5.print 6.remove *****\n");
printf("***** 7.sort 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 PRINT:
PrintContact(&con);
break;
case REMOVE:
RemoveContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
contact.c
#include "contact.h"
// 初始化通讯录
void InitContact(contact* pc)
{
assert(pc);
memset(pc->data, 0, sizeof(pc->data));
pc->sz = 0;
}
// 增加联系人
void AddContact(contact* pc)
{
assert(pc);
if (pc->sz == MAX_SIZE)
{
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");
}
// 通过名字查找,指定联系人
// 找到了返回对应下标
// 找不到返回 -1
static 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 DelContact(contact* pc)
{
assert(pc);
char name[NAME_MAX] = { 0 };
printf("请输入要删除人的名字:>");
scanf("%s", name);
// 查找要删除的人
int pos = FindByName(pc, name);
// 没有找到
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
// 找到了,删除
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1]; // 从后向前覆盖
}
pc->sz--;
printf("删除成功\n");
}
// 修改指定联系人
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("请输入要修改什么信息 (1-名字,2-年龄,3-性别,4-电话,5-地址):>");
int msg = 0;
scanf("%d", &msg);
switch (msg)
{
case 1:
printf("请输入新的名字:>");
scanf("%s", pc->data[pos].name);
break;
case 2:
printf("请输入新的年龄:>");
scanf("%d", &(pc->data[pos].age));
break;
case 3:
printf("请输入新的性别:>");
scanf("%s", pc->data[pos].sex);
break;
case 4:
printf("请输入新的电话:>");
scanf("%s", pc->data[pos].tele);
break;
case 5:
printf("请输入新的地址:>");
scanf("%s", pc->data[pos].addr);
break;
default:
printf("输入错误,修改失败\n");
return;
break;
}
printf("修改成功\n");
}
// 查找指定联系人
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("%-15s %-5s %-5s %-11s %-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-15s %-5d %-5s %-11s %-30s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
// 打印联系人信息
void PrintContact(const contact* pc)
{
assert(pc);
int i = 0;
// 打印标题
printf("%-15s %-5s %-5s %-11s %-30s\n", "名字", "年龄", "性别", "电话", "地址");
// 打印记录
for (i = 0; i < pc->sz; i++)
{
printf("%-15s %-5d %-5s %-11s %-30s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
// 清空联系人
void RemoveContact(contact* pc)
{
assert(pc);
InitContact(pc);
}
// 按名字升序
static int cmp_name(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name) < 0)
return -1;
else
return 0;
}
// 按年龄升序
static int cmp_age(const void* e1, const void* e2)
{
return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}
// 按性别升序
static int cmp_sex(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->sex, ((PeoInfo*)e2)->sex) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->sex, ((PeoInfo*)e2)->sex) < 0)
return -1;
else
return 0;
}
// 按电话升序
static int cmp_tele(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->tele, ((PeoInfo*)e2)->tele) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->tele, ((PeoInfo*)e2)->tele) < 0)
return -1;
else
return 0;
}
// 按住址升序
static int cmp_addr(const void* e1, const void* e2)
{
if (strcmp(((PeoInfo*)e1)->addr, ((PeoInfo*)e2)->addr) > 0)
return 1;
else if (strcmp(((PeoInfo*)e1)->addr, ((PeoInfo*)e2)->addr) < 0)
return -1;
else
return 0;
}
// 按任意类型排序
void SortContact(contact* pc)
{
assert(pc);
int input = 0;
int(*p[])(const void*, const void*) = {0, cmp_name, cmp_age, cmp_sex, cmp_tele, cmp_addr};
printf("请输入您要按什么排序 (1-名字,2-年龄,3-性别,4-电话,5-地址):>");
scanf("%d", &input);
if (input >= 1 && input <= 5)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), p[input]);
PrintContact(pc);
}
else
{
printf("选择错误\n");
}
}