简单通讯录实现
总体逻辑
实现通讯录,我们首先就需要先把需要完成的功能列一个清单,这样才能让实现过程更加有序化,不会摸不着头脑。
设计任务:
通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址。
我们需要用通讯录程序做到的功能有如下:
- 添加联系人信息(进行排序)
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息(二分查找)
- 修改指定联系人信息
- 显示所有联系人信息
- 清空所有联系人
设计思路
为了使该程序的设计更加模块化,我们将代码分为三个文件来写。
- contact.h用来放置结构体的定义,宏定义,以及通讯录功能函数的声明。
- contact.c用来编写通讯录功能函数的定义
- text.c用来编写和用户进行交互的代码
实现过程
1.结构体定义
一个人的姓名、性别、年龄、电话、住址等信息共同构成了一个人,所以这里定义了一个结构体per-info
便于找到同一个人的各种信息,具体如下:
#define SEX_MAX 5
#define TELE_MAX 12
#define NAME_MAX 20
#define ADDR_MAX 25
typedef struct per_info
{
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char name[NAME_MAX];
char addr[ADDR_MAX];
}per_info;
这里为了使程序便于修改参数,使用宏定义的方式定义了最大性别字符数,最大名字字符数等等,有了per-info
结构体之后,一个per-info
结构体就表示一个人,那么通讯录就是一堆的per-info结构体,我们可以用一个per-info数组用来保存通讯录的信息,并且为了能够明确直到通讯录内一共有多少人,我们再次定义一个结构体contact
,该结构体由一个per-info
数组和一个用来表示联系人个数的整型构成,如下:
typedef struct contact
{
per_info people[PEOPLE_MAX];
int sz;
}contact;
2.根据通讯录的功能写出具体框架
前面为了使程序更加模块化开发,我们将整个通讯录分成了3个模块,通讯录功能实现的主要细节在contact.c
文件中实现,text.c
中则需要实现通讯录的大致框架。
我们使用通讯录的思路是:根据屏幕显示的菜单,选择对应的选项让程序执行对应的功能,我们就按照这个思路来写出具体框架。
创建通讯录
首先,为了使用通讯录,程序开始的时候就必须创建一个通讯录,并且对函数进行初始化(不直接赋值0是因为如果你有自己初始化的想法可以直接修改函数内容)
contact record;
//为使得初始化更灵活,用函数封装
init_contact(&record);
初始化通讯录函数具体实现:
void init_contact(contact* pc)
{
//pc不能是空指针
assert(pc);
memset(pc->people, 0, sizeof(pc->people));
pc->sz = 0;
}
创建菜单
我们封装一个函数在每次用户每次使用通讯录时在屏幕上打印菜单,如下:
void menu(void)
{
printf("****************************\n");
printf("**** 1.add 2.del ****\n");
printf("**** 3.modify 4.search ****\n");
printf("**** 5.show 6.empty ****\n");
printf("**** 0.exit ****\n");
printf("****************************\n");
}
接着,我们可以运用一个变量input来储存用户的输入,让程序执行相关操作,如下:
int input = 0;
do
{
menu();
printf("请选择你需要做的操作:>");
scanf("%d", &input);
while (getchar() != '\n')
continue;
//清理用户的多余输入
switch (input)
{
//以下的函数都会在之后实现
case 1:
add_person(&record);
break;
case 2:
del_person(&record);
break;
case 3:
mod_person(&record);
break;
case 4:
search_person(&record);
break;
case 5:
show_person(&record);
break;
case 6:
empty_person(&record);
break;
case 0:
printf("退出通讯录");
break;
default:
printf("输入错误,请重新输入。");
break;
}
} while (input);
枚举的使用
但是如果在具体设计的时候,我们会发现每次每次都无法想到各个选项对应的是什么功能,所以我们使用一个枚举常量的方法进行优化,如下:
typedef enum option
{
EXIT,
ADD,
DEL,
MODIFY,
SEARCH,
SHOW,
EMPTY
}option;
定义了这样一个枚举常量之后,我们就可以用ADD
来代替1,EXIT
代替0等等这样的操作,例如:
case 1:
add_person(&record);
break;
这样我们的基本框架就做好了,接下来就可以进行函数功能实现。
我们将函数的声明放在contact.h文件中,具体实现放在contact.c文件中。
3.添加联系人
首先,由于添加联系人需要修改结构体内的内容,所以需要选择传址调用的方式进行函数传参,因此函数声明如下:
void add_person(contact* pc);
添加的过程很简单,也就是在通讯录内的数组当前的最后一项内添加信息,而当前的最后一项的下标恰好是结构体内表示当前存储人数的数字,有这些基础,该函数就可以这样定义:
void add_person(contact* pc)
{
assert(pc);
//检验pc的有效性
//pc->sz恰好表示信息下一步要放的位置
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pc->sz].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pc->sz].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pc->sz].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pc->sz].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pc->sz].tele);
++pc->sz;
}
但是这样做还不完善,我们想象一下我们自己的手机,每次添加联系人之后都会自动按照联系人的首字母进行排序,所以我们在添加联系人函数中也实现一下这一功能。
想要完成该功能,我们需要使用qsort
函数,不了解的小伙伴可以先看看以下链接了解使用方法:
qsort函数介绍
要使用qsort
,我们就需要先设计一个比较函数,如果前者的人的名字字母排序大于前者,返回小于0的数,反之返回大于0的数,而比较名字是比较两个字符串,而我们会想到strcmp
函数恰好满足这一要求,我们就可以以这个函数的结果作为返回值,具体代码如下:
int cmp(const void* a, const void* b)
{
return strcmp(((per_info*)a)->name, ((per_info*)b)->name);
}
add_person
函数全览:
void add_person(contact* pc)
{
assert(pc);
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pc->sz].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pc->sz].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pc->sz].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pc->sz].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pc->sz].tele);
++pc->sz;
qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
printf("添加成功!\n");
4.显示所有联系人函数
函数定义:
void show_person(const contact* pc);
//由于不会修改结构体内的内容,所以直接使用传值调用的方式也不会有问题
//但是为了使函数参数统一,避免使用出错,通讯录功能函数全部使用传址调用的方式进行设计
首先,我们需要一个表头来快速定位每一部分代表的内容属于什么,另外,由于每一部分的内容不同,所以打印的时候需要一点格式控制来让输出内容更加美观。
函数实现:
void show_person(const contact* pc)
{
assert(pc);
//-表示左对齐
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; ++i)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[i].name,
pc->people[i].age,
pc->people[i].sex,
pc->people[i].tele,
pc->people[i].addr);
}
printf("\n\n");
}
接下来的查找,修改,删除功能都必须要找到联系人所对应的下标然后再进行操作,所以我们可以先将搜查联系人返回对应下标的功能运用一个函数实现。
5.find_by_name
函数
首先,该函数在找到对应联系人的时候返回对应下标,如果没找到,返回-1,查找的方法一种是遍历整个结构体数组进行寻炸,但是这种方法效率很低,第二种方法是二分查找,这样每次进行查找都可以排除一半的数据,效率就高了不少。
(使用二分查找的前提是数组中的数据本身就是有序存放的),由于刚才在添加联系人之后就进行了排序操作,所以刚好满足这一条件,具体实现如下:
int find_by_name(const contact* pc, char* name)
{
int left = 0;
int right = pc->sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (strcmp(pc->people[mid].name, name) > 0)
right = mid - 1;
else if (strcmp(pc->people[mid].name, name) < 0)
left = mid + 1;
else
//找到对应下标,返回
return mid;
}
//没找到,返回-1.
return -1;
}
6.查找联系人
有了find_by_name
函数,这个函数的设计就很简单了,找到对应下标并且打印具体数据即可。
void search_person(const contact* pc)
{
assert(pc);
char name[NAME_MAX] = { 0 };
printf("请输入你要查找联系人的姓名:>");
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[pos].name,
pc->people[pos].age,
pc->people[pos].sex,
pc->people[pos].tele,
pc->people[pos].addr);
}
7.删除联系人
该函数的实现也相对简单,但是仍然有两种方式选择:
- 找到所需删除的联系人后采用后面覆盖前面的方式逐个覆盖
- 查找到之后用数组中的最后一项覆盖该项
稍加理解,我们会发现第二种方法效率更高,但是其会破坏数组的有序性,需要重新排序,会影响通讯录其他功能的实现,如果是第一种就不会出现该请现象,所以我们选择用第一种设计。
函数声明:
void del_person(contact* pc);
函数定义:
void del_person(contact* pc)
{
assert(pc);
printf("请输入你要删除的联系人:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("你要删除的联系人不存在.\n");
return;
}
for (int i = pos; i < pc->sz - 1; ++i)
{
pc->people[i] = pc->people[i + 1];
}
--pc->sz;
printf("删除成功!\n");
}
8.修改联系人
思路简单,找到所需修改的联系人的对应下标并且重新输入其中的内容即可。
函数定义:
void mod_person(contact* pc)
{
printf("请输入你要修改的联系人的名字:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到要修改的联系人。\n");
return;
}
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pos].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pos].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pos].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pos].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pos].tele);
qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
printf("修改成功!\n");
}
9.清空联系人
接下来就是最后一步了,清空所有联系人,我们可以使用memset
把contact
结构体中的内容全部设置为0即完成清空。
函数定义:
void empty_person(contact* pc)
{
assert(pc);
if (!pc->sz)
{
printf("无联系人,无需清空。\n");
Sleep(1000);
system("cls");
return;
}
memset(pc, 0, sizeof(contact));
printf("清空联系人成功!\n");
}
10.全部代码总览
使用了system("cls")
和Sleep()
函数提高了程序使用体验
system("cls")
——清空屏幕Sleep()
——休眠······ms
contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define PEOPLE_MAX 1000
#define SEX_MAX 5
#define TELE_MAX 12
#define NAME_MAX 20
#define ADDR_MAX 25
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
typedef enum option
{
EXIT,
ADD,
DEL,
MODIFY,
SEARCH,
SHOW,
EMPTY
}option;
typedef struct per_info
{
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char name[NAME_MAX];
char addr[ADDR_MAX];
}per_info;
typedef struct contact
{
per_info people[PEOPLE_MAX];
int sz;
}contact;
//使用传址调用能够减少空间的使用
//初始化通讯录
void init_contact(contact* pc);
//添加联系人
void add_person(contact* pc);
//显示联系人
void show_person(const contact* pc);
//搜索联系人
void search_person(const contact*pc);
//删除联系人
void del_person(contact* pc);
//修改联系人
void mod_person(contact* pc);
//清空联系人
void empty_person(contact* pc);
contact.c
#include"contact.h"
void init_contact(contact* pc)
{
//pc不能是空指针
assert(pc);
memset(pc->people, 0, sizeof(pc->people));
pc->sz = 0;
}
int cmp(const void* a, const void* b)
{
return strcmp(((per_info*)a)->name, ((per_info*)b)->name);
}
void add_person(contact* pc)
{
assert(pc);
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pc->sz].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pc->sz].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pc->sz].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pc->sz].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pc->sz].tele);
++pc->sz;
qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
printf("添加成功!\n");
Sleep(1000);
system("cls");
}
void show_person(const contact* pc)
{
system("cls");
assert(pc);
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
for (int i = 0; i < pc->sz; ++i)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[i].name,
pc->people[i].age,
pc->people[i].sex,
pc->people[i].tele,
pc->people[i].addr);
}
printf("\n\n");
}
int find_by_name(const contact* pc, char* name)
{
int left = 0;
int right = pc->sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (strcmp(pc->people[mid].name, name) > 0)
right = mid - 1;
else if (strcmp(pc->people[mid].name, name) < 0)
left = mid + 1;
else
return mid;
}
return -1;
}
void search_person(const contact* pc)
{
assert(pc);
char name[NAME_MAX] = { 0 };
printf("请输入你要查找联系人的姓名:>");
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-25s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-25s\n", pc->people[pos].name,
pc->people[pos].age,
pc->people[pos].sex,
pc->people[pos].tele,
pc->people[pos].addr);
}
void del_person(contact* pc)
{
assert(pc);
printf("请输入你要删除的联系人:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("你要删除的联系人不存在.\n");
return;
}
for (int i = pos; i < pc->sz - 1; ++i)
{
pc->people[i] = pc->people[i + 1];
}
--pc->sz;
printf("删除成功!\n");
Sleep(1000);
system("cls");
}
void mod_person(contact* pc)
{
printf("请输入你要修改的联系人的名字:>");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = find_by_name(pc, name);
if (-1 == pos)
{
printf("找不到要修改的联系人。\n");
return;
}
printf("请输入联系人的姓名:>");
scanf("%s", pc->people[pos].name);
printf("请输入联系人的性别:>");
scanf("%s", pc->people[pos].sex);
printf("请输入联系人的年龄:>");
scanf("%d", &(pc->people[pos].age));
printf("请输入联系人的地址:>");
scanf("%s", pc->people[pos].addr);
printf("请输入联系人的电话:>");
scanf("%s", pc->people[pos].tele);
qsort(pc->people, pc->sz, sizeof(pc->people[0]), cmp);
printf("修改成功!\n");
Sleep(1000);
system("cls");
}
void empty_person(contact* pc)
{
assert(pc);
if (!pc->sz)
{
printf("无联系人,无需清空。\n");
Sleep(1000);
system("cls");
return;
}
memset(pc, 0, sizeof(contact));
printf("清空联系人成功!\n");
Sleep(1000);
system("cls");
}
text.c
#include"contact.h"
void menu(void)
{
printf("****************************\n");
printf("**** 1.add 2.del ****\n");
printf("**** 3.modify 4.search ****\n");
printf("**** 5.show 6.empty ****\n");
printf("**** 0.exit ****\n");
printf("****************************\n");
}
int main()
{
int input = 0;
//创建通讯录
contact record;
//为使得初始化更灵活,用函数封装
init_contact(&record);
do
{
menu();
printf("请选择你需要做的操作:>");
scanf("%d", &input);
while (getchar() != '\n')
continue;
system("cls");
switch (input)
{
case ADD:
add_person(&record);
break;
case DEL:
del_person(&record);
break;
case MODIFY:
mod_person(&record);
break;
case SEARCH:
search_person(&record);
break;
case SHOW:
show_person(&record);
break;
case EMPTY:
empty_person(&record);
break;
case 0:
printf("退出通讯录");
break;
default:
printf("输入错误,请重新输入。");
break;
}
} while (input);
return 0;
}
11.总结
简单通讯录的实现到这里就基本完成,但是还有很多不足,比如:
说
- 储存的联系人无法保存到硬盘,程序结束运行之后里面储存的联系人都会消失,没有使用价值。
- 另外通讯录大小固定,若所需使用的空间较小就会浪费空间,所需空间大的花放不下,不够灵活。
这些问题将会在之后的博客中解决,敬请期待把!!
这是动态设置空间的通讯录版本的链接,特地前来补上嘻嘻:
动态通讯录实现(C语言)
码字不易,如果觉得博主写的不错还请点赞哦!