换了个新键盘,敲字不吵人,同时手感超级好,所以我打算速速更新一篇博客!
(其实就是通讯录的代码敲完了想趁热打铁)
以下是正文部分
既然我们要实现一个通讯录,那么通讯录的功能我们势必要了解。根据我们现在学习C语言的水平,我们现在可以用我们的编译器写出带有以下功能的通讯录
增加联系人、删除联系人、查询特定联系人、修改联系人信息、排序通讯录
既然我们了解了通讯录的功能,现在我们就可以开始着手实现我们的通讯录。还是跟我们之前能够编写的大项目一致,我们用一个Contact.h来包含所有头文件和函数声明,用Contact.c来编写通讯录内所有的自定义函数,用test.c来测试我们的代码是否能运行。
通讯录在使用的时候势必得有一个菜单供用户使用,根据我们之前编写三子棋和扫雷的经验,菜单那就得是我们经典的组合。
菜单栏
经典的组合,经典的味道。
void menu() {
printf("********* Welcome ***************\n");
printf("*****1.add 2.del*************\n");
printf("*****3.search 4.modify**********\n");
printf("*****5.show 6.sort************\n");
printf("*****0.exit **************************\n");
}
既然我们的菜单栏成功的能打印出来,还是依据我们扫雷代码的经验,接下来就需要用Switch-case语句来实现用户可以通过输入对应数字来启用对应功能,所以,我们的main函数里就可以开始编写第一个小部分。
enum Option {
Exit,
Add,
Del,
Search,
Modify,
Show,
Sort
};
int main()
{
int input = 0;
do{
menu();
printf("Please choose :>");
scanf("%d", &input);
switch (input) {
case Add:
break;
case Del:
break;
case Search:
break;
case Modify:
break;
case Show:
break;
case Sort:
break;
case Exit:
break;
default:
printf("错误!请重新输入。\n");
}
} while (input);
return 0;
}
可以看到我的代码并没有使用case1 case2,而是使用了枚举常量来代替case后面的数字,这样的好处有很多,首先我们在编写通讯录的代码有很多个功能需要去实现,这些功能都是需要去通过自定义函数去实现的,后期我们编写完一个功能之后可能会忘记对应数字代表着什么功能,而枚举常量既能满足菜单中的数字和case后的单词实现一一对应,更重要的是使用枚举常量极大地增加了代码的可读性。
那么,我们就需要来开始思考如何来存储我们对应联系人的信息了。
联系人信息存储-自定义数据类型
通讯录,最起码需要包括联系人的名字和联系电话,但是为了信息完善性(刁难你们),通讯录还需要能存储联系人的年龄、性别、家庭住址。但是我们需要记住,通讯录的最小单位可不是电话,而是人,所以,这个时候我们自己定义一个结构体,结构体成员就是我们需要存储的信息,这样我们就能来实现数据的存放。
struct PeoInfo {
char name[20];
int age;
char sex[5];
char tele[11];
char addr[25];
};
这样我们的结构体就创建出来了,但是这个代码并不好。
第一点,这段结构体里包含的元素非常的多,而且结构里大部分元素都是数组,数组长度如果后期需要对数组长度进行加大后续肯定是不方便的,所以利用宏定义是更好的方法。
第二点,我们之前说了,通讯录里的最小单位是人,假设这个通讯录能存放100个人,如果我将PeoInfo这个结构体定义成一个类型,那么我用自己创造的类型来创建一个100个元素的数组是不是就真正的达到了存放数据的目的。
//宏定义 将数组内容中的数字用宏进行定义 这样在后期进行替换修改宏就可以
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 11
#define MAX_ADDR 20
typedef struct PeoInfo {
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
完美!
但是我们还是有一个问题,虽然自定义类型解决了,但是我们还需要一个变量sz来表示我们当前通讯录里存放了几个联系人,如果我们不把他们整合到一起,后续传参可能会造成不便,所以,我们将之前提到的PeoInfo data[MAX]数组和sz整合到一个类型里,后续通过这个类型的指针来访问这个里面的元素,是不是就解决问题了!
所以,完整的自定义类型代码应该是这样的。
//宏定义 将数组内容中的数字用宏进行定义 这样在后期进行替换修改宏就可以
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 11
#define MAX_ADDR 20
typedef struct PeoInfo {
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//这里的Contact就是我们的通讯录 这里将PeoInfo定义成一种类型就可以用一个数组来表达一个通讯录
//data数组里的每一个元素就是一个人的信息 sz表示目前通讯录里有几个数据
typedef struct Contact {
PeoInfo data[MAX];
int sz;
}Contact;
这里的Contact结构体就是我们通讯录的核心内容,最最最最关键。
第一个函数:增加联系人和初始化通讯录
既然我们已经把通讯录的核心部分搭建好了,现在,test.c里面就需要来用到我们自己创建的类型了,同时我们可以开始来编写第一个功能--添加联系人。
enum Option {
Exit,
Add,
Del,
Search,
Modify,
Show,
Sort
};
int main()
{
int input = 0;
Contact con;
do{
menu();
printf("Please choose :>");
scanf("%d", &input);
switch (input) {
case Add:
break;
case Del:
break;
case Search:
break;
case Modify:
break;
case Show:
break;
case Sort:
break;
case Exit:
break;
default:
printf("错误!请重新输入。\n");
}
} while (input);
return 0;
}
现在,我们就来着手写增加联系人函数的功能。
增加联系人其实很简单,访问Contact结构体里的data数组里对应的内容,用scanf就完事了,但是要注意如何利用结构体指针访问结构体元素和直接访问结构体元素的方法,不要写乱了。
void Addcontact (Contact* pc)
{
if (pc->sz == 100) {
printf("通讯录已满,无法添加联系人信息。");
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是一个随机值,这个随机值就不能保证我们的代码正常运行,所以,对于通讯录进行主观操作之前,我们需要对通讯录做一个初始化。
void IniContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
后面在运行代码,我们就会发现代码能够正常运行了。
第二个函数:打印联系人信息
为啥这个函数我们第二个实现显示功能呢,因为打印联系人这个功能能很好地帮助我们检测后续功能代码写得是否正常。
打印联系人信息还是比较容易实现的。
void Showcontact(const Contact* pc)
{
printf("%-10s %-4s %-4s %-11s %-5s\n", "姓名", "年龄","性别", "电话号码", "家庭地址");
for (int i = 0; i < pc->sz; i++) {
printf("%-10s %-4d %-4s %-11s %-5s\n", pc->data[i].name, pc->data[i].age,
pc->data[i].sex,pc->data[i].tele, pc->data[i].addr);
}
}
先打印一个标头,这样能够让用户知道依次显示的内容是什么,再打印。
第三个函数:删除联系人信息&查找联系人信息
为什么我现在将这两个函数放在一起讲,因为他们两有一个共同之处。
删除联系人,需要找到对应联系人的信息,再进行删除;查找联系人也需要找对应联系人的信息,既然这两个函数有着高度的重合,那我们就可以将“查找特定联系人”这一个功能抽象出来,然后返回我们这个联系人在数组里的下表,是不是就可以了!
所以,我们来编写一下这个函数。
//为什么要把查找名字的功能单独拿出来编写 因为通讯录中的查找功能和删除功能都需要查找是否有这个联系人
//将这个功能单独拿出来可以有效地减少代码冗余
//至于加上static关键字主要是这个只是从两个函数功能中抽象出来的 其他的文件中不需要用到这 所以加上static进行保护
static int FindByName(const Contact* pc, char name[MAX_NAME])
{
for (int i = 0; i < pc->sz; i++) {
if (0 == strcmp(pc->data[i].name, name)) {
return i;
}
}
return -1;
}
(这个return -1的位置值得思考)
以下是删除和查找联系人的函数,这里头注释很重要。
void Delcontact(Contact* pc)
{
char name[MAX_NAME];
//检查通讯录是否为空
if (pc->sz == 0) {
printf("通讯录为空,无法删除\n");
return;
}
printf("请输入你要删除联系人的姓名:");
scanf("%s", name);
//删除
// 1.查找是否存在要删除的联系人
int pos = FindByName(pc, name);
if (pos == -1) {
printf("通讯录中没有此联系人");
}
// 2.进行数据覆盖,删除的本质就是后面的数据覆盖前面的数据 注意循环的的次数
for (int i = pos; i < pc->sz - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
void Searchcontact(const Contact* pc)
{
//检查通讯录是否为空
if (pc->sz == 0) {
printf("通讯录为空,无法查询\n");
return;
}
printf("请输入你要查询联系人的姓名:");
char name[MAX_NAME];
scanf("%s", name);
//查询
int pos = FindByName(pc, name);
if (pos == -1) {
printf("通讯录中没有此联系人\n");
return;
}
else
printf("%-10s %-4d %-4s %-11s %-5s\n", pc->data[pos].name, pc->data[pos].age,
pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
}
第四个函数:修改联系人
这个也挺简单的,还是利用我们的FindByName函数找到特定联系人,我们在修改就完了,这个修改完全可以复制粘贴Addcontact函数里的代码。
void Modifycontact(Contact* pc)
{
if (pc->sz == 0) {
printf("通讯录为空,无法修改\n");
return;
}
char name[MAX_NAME];
printf("请输入你要修改联系人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1) {
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].tele);
printf("请输入家庭地址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功!\n");
}
第五个函数:排序
这个也是咱们最后需要实现的内容,排序联系人。
我们按照名字排序,那么很简单,我们利用qsort函数,只需要自己编写一个排序函数,就可以了。
int SortByName(const void* p1, const void* p2) {
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
void Sortcontact(Contact* pc)
{
if (pc->sz == 0) {
printf("通讯录为空,无法排序\n");
return;
}
qsort(pc->data, pc->sz, sizeof(PeoInfo), SortByName);
printf("排序成功~\n");
}
这里的SortByName函数里的强制类型转换是非常重要的!
那么,我们的函数都写完了,接下来我们,上代码!
Contact.c
//此文件用于编写静态通讯录内的函数
#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"
void IniContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
void Addcontact (Contact* pc)
{
if (pc->sz == 100) {
printf("通讯录已满,无法添加联系人信息。");
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");
}
void Showcontact(const Contact* pc)
{
printf("%-10s %-4s %-4s %-11s %-5s\n", "姓名", "年龄","性别", "电话号码", "家庭地址");
for (int i = 0; i < pc->sz; i++) {
printf("%-10s %-4d %-4s %-11s %-5s\n", pc->data[i].name, pc->data[i].age,
pc->data[i].sex,pc->data[i].tele, pc->data[i].addr);
}
}
//为什么要把查找名字的功能单独拿出来编写 因为通讯录中的查找功能和删除功能都需要查找是否有这个联系人
//将这个功能单独拿出来可以有效地减少代码冗余
//至于加上static关键字主要是这个只是从两个函数功能中抽象出来的 其他的文件中不需要用到这 所以加上static进行保护
static int FindByName(const Contact* pc, char name[MAX_NAME])
{
for (int i = 0; i < pc->sz; i++) {
if (0 == strcmp(pc->data[i].name, name)) {
return i;
}
}
return -1;
}
void Delcontact(Contact* pc)
{
char name[MAX_NAME];
//检查通讯录是否为空
if (pc->sz == 0) {
printf("通讯录为空,无法删除\n");
return;
}
printf("请输入你要删除联系人的姓名:");
scanf("%s", name);
//删除
// 1.查找是否存在要删除的联系人
int pos = FindByName(pc, name);
if (pos == -1) {
printf("通讯录中没有此联系人");
}
// 2.进行数据覆盖,删除的本质就是后面的数据覆盖前面的数据 注意循环的的次数
for (int i = pos; i < pc->sz - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
void Searchcontact(const Contact* pc)
{
//检查通讯录是否为空
if (pc->sz == 0) {
printf("通讯录为空,无法查询\n");
return;
}
printf("请输入你要查询联系人的姓名:");
char name[MAX_NAME];
scanf("%s", name);
//查询
int pos = FindByName(pc, name);
if (pos == -1) {
printf("通讯录中没有此联系人\n");
return;
}
else
printf("%-10s %-4d %-4s %-11s %-5s\n", pc->data[pos].name, pc->data[pos].age,
pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
}
void Modifycontact(Contact* pc)
{
if (pc->sz == 0) {
printf("通讯录为空,无法修改\n");
return;
}
char name[MAX_NAME];
printf("请输入你要修改联系人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1) {
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].tele);
printf("请输入家庭地址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功!\n");
}
int SortByName(const void* p1, const void* p2) {
return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}
void Sortcontact(Contact* pc)
{
if (pc->sz == 0) {
printf("通讯录为空,无法排序\n");
return;
}
qsort(pc->data, pc->sz, sizeof(PeoInfo), SortByName);
printf("排序成功~\n");
}
test.c
//此文件用于测试通讯录的各个模块内容
#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"
void menu() {
printf("********* Welcome ***************\n");
printf("*****1.add 2.del*************\n");
printf("*****3.search 4.modify**********\n");
printf("*****5.show 6.sort************\n");
printf("*****0.exit **************************\n");
}
enum Option {
Exit,
Add,
Del,
Search,
Modify,
Show,
Sort
};
int main()
{
int input = 0;
Contact con;
//初始化通讯录
IniContact(&con);
do{
menu();
printf("Please choose :>");
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 Show:
Showcontact(&con);
break;
case Sort:
Sortcontact(&con);
break;
case Exit:
break;
default:
printf("错误!请重新输入。\n");
}
} while (input);
return 0;
}
Contact.h
//此文件用于声明静态版本通讯录的所有头文件和函数声明
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//宏定义 将数组内容中的数字用宏进行定义 这样在后期进行替换修改宏就可以
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 11
#define MAX_ADDR 20
typedef struct PeoInfo {
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//这里的Contact就是我们的通讯录 这里将PeoInfo定义成一种类型就可以用一个数组来表达一个通讯录
//data数组里的每一个元素就是一个人的信息 sz表示目前通讯录里有几个数据
typedef struct Contact {
PeoInfo data[MAX];
int sz;
}Contact;
//函数声明
//初始化通讯录数据
void IniContact(Contact* pc);
//添加联系人
void Addcontact(Contact* pc);
//显示联系人信息
void Showcontact(const Contact* pc);
//删除联系人
void Delcontact(Contact* pc);
//查找联系人
void Searchcontact(const Contact* pc);
//修改联系人信息
void Modifycontact(Contact* pc);
//分类联系人信息
void Sortcontact(Contact* pc);
如果你觉得这篇文章对你有帮助,请给我点个赞,如果可以给我一个关注,那就更好啦!