文章目录
前言
学完结构体后,我们可以通过学过的知识自己来制作一个通讯录。我们在学校中难免会接触到什么什么大作业,什么什么系统,这些都大同小异,这里带着大家进行通讯录的实现。
一、要实现的功能
1.顾名思义,通讯录中要有人的信息,包括:姓名,年龄,性别,地址,电话
2.通讯录中可以存放的人很多(这里按照可以放100个来实现)
4.增加联系人
5.删除指定联系人
6.查找指定联系人的信息
7.修改指定联系人的信息
8.显示所有联系人的信息
等等等
还可以对名字或者年龄进行排序,后续自己想要加什么功能可以自己加。
二、实现通讯录
2.1基本框架
我们先创建文件,分文件编写代码,包括test.c,contact.h,contact.c,分别代表测试通讯录,通讯录的声明,通讯录的实现,
我们希望在test.c 中测试代码,所以主函数设计在test.c中:
#define _CRT_SECURE_NO_WARINGS//VS安全问题
#include<stdio.h>
int main()
{
return 0;
}
我们可以通过一个简单的菜单来显示我们要选的功能,我们还希望完成当前功能之后还可以进行其它的功能,所以这时候就可以通过一个do...while循环来实现。
下面先编写菜单:
cpp
void menu()
{
printf("***************************************\n");
printf("******** 1.Add 2.Delete ********\n");
printf("******** 3.Search 4.Modify ********\n");
printf("******** 5.Show 6.Sort ********\n");
printf("******** 0.Exit ********\n");
printf("***************************************\n");
}
分别用数字代表着增,删,查,改,显示,排序和退出。后续如果相加功能,就可以在这里改就行了。
cpp
int main()
{
int input=0;
do
{
menu();
printf("请选择>>:");
scanf("%d", &input);
} while (input);
return 0;
}
主函数中通过do...while 来实现循环,输入input ,当input 为真的时候进入循环,当input为0的时候也就是对应菜单里的退出,结束循环。
接着我们就可以在里面通过一个switch...case语句,来实现自己的选择:
cpp
int main()
{
int input=0;
do
{
menu();
printf("请选择>>:");
scanf("%d", &input);
switch (input)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
经过测试并没有问题,这样一个简单的框架就出来了,接下来实现功能。
2.2信息
因为**通讯录中要有人的信息,包括:姓名,年龄,性别,地址,电话,**所以可以通过一个结构体来包含这些成员。
在contact.h文件里放入这个结构体,因为这个我们是要一直用的,放在头文件中方便后续的调用。
cpp
//人的信息
typedef struct PeoInfo
{
char name[20];
int age;
char sex[5];
char addr[30];
char tele[12];
}PeoInfo;
但实际上直接定义多少比较不好,以为如果后续的话想要该值的话,那么后面的代码都要改数值,所以可以通过一个define来定义多少,后续直接在这修改就可以了。
cpp
#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 30
#define TELE_MAX 12
结构体就可以改成:
cpp
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char addr[ADDR_MAX];
char tele[TELE_MAX];
}PeoInfo;
这里我们为了存放100个人的信息,就可以在创建通讯录规定,通过在test.c文件中主函数内创建一个结构体数组,数组元素包括100,这样就可以实现了100个人的信息。
cpp
PeoInfo data[MAX];
int input=0;
do
{
...
注意的是,需要包含"contact.h"(这里为什么用双引号,因为这个头文件是我们自己创建的,所以不是<>)头文件才可以使用PeoInfo类型,才可以进行创建。
2.3初始化
这里有一个难点,就是当我们在增加联系人的人,要知道通讯录当前放了多少人了,假设放了一个人,新的联系人我们就应该放在第二个,但这个位置如何寻找是一个问题。所以我们通过一个变量sz代表当前通讯录放了几个人的信息。同样也是在test.c主函数中。
cpp
//创建通讯录
PeoInfo data[MAX];
int sz;
但这样的信息还是比较分散,我们就可以通过一个结构体来包含这两个信息,一个是联系人的数据,一个是联系人放了多少个,所以在contact.h中规定这个结构体:
cpp
typedef struct Contact
{
PeoInfo data[MAX];//存放个人信息的数据
int sz;//当前已经存放的信息个数
}Contact;
这样我们就可以通过一个结构体变量包括两个信息了,主函数中就改成:
cpp
//创建通讯录
//PeoInfo data[100];
//int sz;
Contact con;
int input=0;
...
这里就用一个con变量包含了数据和存放的个数,我们最好初始化我们这个结构体。
cpp
//创建通讯录
//PeoInfo data[100];
//int sz;
Contact con;
//初始化通讯录
InitContact();
通过InitContact函数来初始化,这时候涉及到了结构体的传参,前面我们知道结构体传参最好是传地址,因为形参是对原始数据的一份临时拷贝,通过传地址就可以直接用原数据,大大减少了内存的损耗。
首先要在contact.h头文件中声明函数,因为后续的contact.c文件可以包含这个头文件:
cpp
void InitContact(Contact* pc);//初始化
由于我们传入的是地址,没有返回值,所以传入的就是一个结构体指针。
接着,在contact.c中编写实现初始化的功能:
cpp
#define _CRT_SECURE_NO_WARNINGS//VS安全问题
#include<stdio.h>
#include"contact.h"
#include<string.h>
void InitContact(Contact* pc)//初始化
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
通过包含contact.h,才能使用Contact类型,我们初始化当前没有存放联系人,所以sz=0,同时用memset来设置结构体数组中每一个数据的值为0。
void * memmove ( void * destination, const void * source, size_t num );
那么初始化个字节呢,就可以通过sizeof( )来直接计算,因为这时候pc是结构体的地址,通过结构体的地址来找到所指向对象的data,并且把数组名单独放在sizeof里面,这时候是可以的。
接着就可以进行操作了。
2.4增加联系人
cpp
case 1:
Addcontact(&con);
break;
case 1代表就是增加联系人,这里设计一个Addcontact()函数来实现功能,因为我们还是要修改结构体里面的数据,所以参数还是传入地址。
还是现在头文件中声明:
cpp
void Addcontact(Contact* pc);//增加联系人
在contact.c文件中进行编写:
cpp
void Addcontact(Contact* pc)//增加联系人
{
if (pc->sz == MAX)
{
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].addr);
printf("请输入电话:>>");
scanf("%s", pc->data[pc->sz].tele);
pc->sz++;
}
首先判断人是否满了,满了就返回空,没满就分别写入数据,这里pc是一个结构体,指向data数组,用下标来代表第几个,完了整体也是一个结构体,选择要选择的成员进行输入。
同时增加一个联系人,sz就加上1。
2.5显示联系人
cpp
case 5:
ShowContact(&con);
因为case5就是选择的是显示通讯录信息,所以在这里通过一个函数同样是传入地址,从而实现,也是在头文件中声明函数:
cpp
void ShowContact(const Contact* pc);//显示通讯录
接着再contact.c文件中编写实现的功能:
cpp
//显示通讯录
void ShowContact(const Contact* pc)
{
int i = 0;
for (i = 0; i < pc->sz;i++)
{
printf("%s\t%d\t%s\t%s\t%s\t\n", (pc->data[i]).name,
(pc->data[i]).age,
(pc->data[i]).sex,
(pc->data[i]).addr,
(pc->data[i]).tele);
}
}
这样显示就写完了。可以自己通过测试一下。有时候写的太长,那么输出的东西就不会对齐,所以这时候我们可以通过规定一下固定多长来实现一下对齐。
cpp
printf("%20s\t%4d\t%5s\t%20s\t%12s\t\n", (pc->data[i]).name,
(pc->data[i]).age,
(pc->data[i]).sex,
(pc->data[i]).addr,
(pc->data[i]).tele);
输出就实现对齐了,这时候是右对齐,如果想要左对齐的话,前面加一个负号就可以了:
cpp
printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\t\n", (pc->data[i]).name,
(pc->data[i]).age,
(pc->data[i]).sex,
(pc->data[i]).addr,
(pc->data[i]).tele);
我们想要在上面再实现一个表头信息:
cpp
void ShowContact(Contact* pc)
{
int i = 0;
printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\t\n", "名字", "年龄", "性别", "地址", "电话");
for (i = 0; i < pc->sz;i++)
{
printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\t\n", (pc->data[i]).name,
(pc->data[i]).age,
(pc->data[i]).sex,
(pc->data[i]).addr,
(pc->data[i]).tele);
}
}
注意宽度和输出是一样的,这样就可以显示出来了表头:
2.6删除联系人
这里命名一个叫DelContact的函数来删除指定的联系人,同样的传入的参数还是地址:
cpp
case 2:
DelContact(&con);//删除联系人
头文件中声明函数:
cpp
void DelContact(Contact* pc);//删除联系人
接着再contact.c文件中实现:
cpp
//删除联系人
void DelContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空了,无法继续删除\n");
return;
}
//找到目标人
printf("请输入要删除的人的名字>>:");
scanf("%s",name);
int i = 0;
int del = 0;
for (i = 0; i < pc->sz; i++)
{
if(strcmp(pc->data[i].name, name) == 0)
{
del = i;
break;
}
}
//删除联系人,可以依次往前覆盖,那么前一个就没了
//也可以改变顺序,把最后的挪到删除的地方
for (i = del; i < pc->sz-1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
通过strcmp来确定要删除联系人的地址。当然可以选择自己想要删除的方法,这里就是通过前一个被后一个覆盖的方法来,有利有弊,这里方便理解。
我们通过测试发现没有问题。
因为后续的功能都包括查找,例如查找包括查找,修改也是先查找后再修改,上述的删除也是先查找再删除,所以可以把查找部分的代码封装一个查找函数,方便后期的使用。
cpp
int FindByName(Contact* pc, char name[])
{
int i = 0;
int del = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
如果查找成功,就返回下标,如果没有找到就返回-1。
之前删除的代码就改成:
cpp
/删除联系人
void DelContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空了,无法继续删除\n");
return;
}
//找到目标人
printf("请输入要删除的人的名字>>:");
scanf("%s",name);
int ret=FindByName(pc, name);//查找名字
if (-1 == ret)
{
printf("要删除的人不存在\n");
return;
}//如果为-1就说明没有,如果有往下运行实现删除
int i = 0;
for (i = ret; i < pc->sz-1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
测试一下发现OK。没有问题。
2.7查找
我们查找到后把它打印出来。
还是用一个SearchContact函数来实现这一功能:
cpp
case 3:
SearchContact(&con);//查找联系人
头文件中声明:
cpp
void SearchContact(const Contact* pc);//查找联系人
contact.c文件中编写功能:
cpp
//查找指定人的名字
void SearchContact(const Contact* pc)
{
char name[NAME_MAX];
printf("请输入要查找人的名字>>:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (-1 == pos)
{
printf("要查找的人不存在\n");
return;
}
//打印一个人的信息
printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\t\n", "名字", "年龄", "性别", "地址", "电话");
printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\t\n", (pc->data[pos]).name,
(pc->data[pos]).age,
(pc->data[pos]).sex,
(pc->data[pos]).addr,
(pc->data[pos]).tele);
}
这通过调用之前名字查找的模块还有之前显示联系人里面的代码块实现了查找并显示要查找的那一行。
2.8修改联系人
用一个ModifyContact函数来实现这一功能:
cpp
case 4:
ModifyContact(&con);//修改联系人
头文件中声明:
cpp
void ModifyContact(Contact* pc);//修改联系人
contact.c文件中编写功能:
cpp
//修改联系人
void ModifyContact(Contact* pc)
{
char name[NAME_MAX];
printf("请输入要修改人的名字>>:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (-1 == pos)
{
printf("要修改的人不存在\n");
return;
}
//修改相当于重新录信息
printf("请输入修改后的名字:>>");
scanf("%s", pc->data[pos].name);
printf("请输入修改后的年龄:>>");
scanf("%d", &(pc->data[pos].age));
printf("请输入修改后的性别:>>");
scanf("%s", pc->data[pos].sex);
printf("请输入修改后的地址:>>");
scanf("%s", pc->data[pos].addr);
printf("请输入修改后的电话:>>");
scanf("%s", pc->data[pos].tele);
printf("修改完成!!!\n");
}
用了之前的查找名字的逻辑,重新录入就相当于在目标下标的位置添加联系人。这样就简单的实现了修改。
2.9排序
基于排序我们之前接触过qsort,所以可以自己设计一个排序,实现排序,这里就用SortContact函数来实现:
cpp
case 6:
SortContact(&con);//排序
头文件中声明:
cpp
void SortContact(const Contact* pc);//排序
contact.c文件中编写功能,我们希望可以用不同的方式进行排序,比如按什么排序,是按名字还是年龄,首先写qsort要用到的第四个参数:
cpp
int NameComper(const void* a, const void* b) {
return strcmp(((PeoInfo*)a)->name, ((PeoInfo*)b)->name);
}
int AgeComper(const void* a, const void* b) {
const struct PeoInfo* elem1 = (const struct PeoInfo*)a;
const struct PeoInfo* elem2 = (const struct PeoInfo*)b;
if (elem1->age < elem2->age) {
return -1;
}
else if (elem1->age > elem2->age) {
return 1;
}
else {
return 0;
}
}
分别是名字和年龄的排序方法,这里给的是升序。
接着写函数实现方法,通过switch语句来实现:
cpp
void SortContact(const Contact* pc)
{
int input = 0;
printf("******* 1.name 2.age *********\n");
printf("请选择按什么方式排序>>:");
scanf("%d", &input);
switch (input)
{
case 1:
qsort(pc->data, pc->sz, sizeof(struct PeoInfo), NameComper);
printf("排序成功!!\n");
break;
case 2:
qsort(pc->data, pc->sz, sizeof(struct PeoInfo), AgeComper);
printf("排序成功!!\n");
default:
printf("输入错误\n");
break;
}
}
我们通过运行后发现还可以。
总结
今天主要基于学过的知识完成了一个简单的小通讯录,后续如果想加什么功能可以自己尝试加一下。