目录
今天分享用C语言如何做出一个通讯录,我会尽可能详细的描述出思考的过程,这个思考的过程可能也能用于解决其他的一些问题
写代码之前的一些思考
回忆一下我的高中时期,在语文考试写作文时,我文笔很差,写出来的东西也没有逻辑,总是想到什么就写什么,分数也非常低,为了提高写作的水平,我观察学习了高分同学的作文,我发现了一个共同点,就是他们文章的结构非常清晰。在写议论文时,他们总是会采用如下的结构
他们会在第一段清楚交代自己的中心论点,在之后的段落里分点论证,运用不同的论证方法,条例清晰,逻辑清楚,在加上一些不错的文笔,分数自然很高。
现在我们想用C语言做一个通讯录,借鉴上面的结构,在写代码之前我觉得也要想好一个大致的结构,这样敲代码时就不会一头雾水,云里雾里。
1.通讯录当然要存放很多联系人,而联系人又有姓名,电话,性别,年龄,住址等不同类型的信息需要被存储,因此我在这里想到定义一个“联系人”类型的结构体来存放不同类型的信息
2.通讯录常见的功能有新建联系人,删除联系人,查找联系人,打印联系人列表功能,这些功能的实现我们放在函数中来实现
3.主函数中,我在通讯录里可能要反复实现某些操作,因此需要用到循环
现在写出了一个简单的结构,具体的细节我们在敲代码时优化
模板构建
为了增加代码的可读性,也为了使整个代码的脉络更加清晰,我选择新建一个头文件(contact.h)和一个源文件(contact.c),在contact.h文件中书写类型的定义和函数的声明,在contact.c文件中书写函数的实现,再把这两个文件都引用在通讯录的测试文件(test.c)中
现在我们来敲代码, 根据我们一开始梳理的结构,我们需要创建一个“联系人”类型的结构体,contact.h的文件就是我们用来定义新类型的,因此我们就在这个文件内定义结构体,在别的文件中如果我们想使用“联系人”类型,只需要引用头文件即可
这样一个联系人类型的结构体就创建成功了,我们再来优化一下
试想一下,当我们想要修改其中的成员变量中数组的大小时,使用宏定义是否更简单呢
使用宏定义的好处就是,想要改变数组的大小时,只需要在宏定义改变一个数值就能改变所有使用到该符号的数值,非常方便
然后我们回到主函数,对于一个程序来说,我们最好为用户提供一个菜单,放到函数中实现,比如选1新建联系人,选2删除联系人。在实际中,我们可能不会只操作一次,因此还要用到循环
这样用户在输入1-5的数字时,就能反复实现用户想要执行的功能,输入0时退出程序
我们在菜单中使用了6个数字来对应功能,这是为了方便用户使用,接着要根据用户选择的数字具体进入到对应的这些功能,我们要用到switch语句
do
{
printf("请选择:\n");
menu();
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
default:
break;
}
} while (input);
这里还可以优化,就是使用枚举
void menu()
{
printf("*****1.add 2.del*****\n");
printf("*****3.print 4.search**\n");
printf("*****5.modify 0.exit***\n");
}
enum Option
{
EXIT, //默认为0,然后递增1
ADD, //1
DEL, //2
PRINT, //3
SEARCH, //4
MODIFY //5
};
do
{
printf("请选择:\n");
menu();
scanf("%d", &input);
switch (input)
{
case EXIT: //退出
break;
case ADD: //新建联系人
break;
case DEL: //删除联系人
break;
case PRINT: //打印联系人列表
break;
case SEARCH: //查询联系人
break;
case MODIFY: //修改联系人信息
break;
default: //输入错误
break;
}
} while (input);
这样做的好处是提高代码的可读性,见名知要义,方便使用(枚举常量最好用大写方便和后面区分)
然后,想要把联系人这些信息存起来,就要利用之前定义的联系人类型创建出一个通讯录(别忘了引用头文件#include “contact.h”,这里只能用双引号,不能使用尖括号)
为了方便改动通讯录的大小,在这里使用宏定义是一个很好的方法
另外,在之后的操作中会频繁使用到data这个结构体数组
在敲代码的时候,要时刻想着,我这样写是否合理。假如我现在要将1个联系人的信息存入data数组,那么存放的位置是数组的什么位置呢?再想一下,假如我现在写了一个新建联系人的函数,又应该把什么样的实参传给形参呢?把这两个问题结合一下我们就知道,不仅要把通讯录传入函数,也应该把通讯录中的联系人人数也传入函数,这样就知道了新建的联系人信息存储在数组的哪个位置,而为了方便书写,我们再在contact.h中定义一个通讯录类型的结构体,成员变量有联系人类型的结构体数组,还有一个整型变量用来告诉我们通讯录目前的人数
相应的,要把MAX的宏定义放到contact.h的文件中 ,上述代码就创建了一个通讯录,存放了联系人信息和当前通讯录的人数
为了方便操作,在这里我们再使用一个重命名
typedef struct Contact //把struct Contact重命名为Contact
{
PeoInfo data[MAX]; //存放联系人信息
int count; //记录当前通讯录人数
}Contact;
接着拿起这个结构体在主函数中创建出一个通讯录
功能的实现
接下来,我们就要分别实现退出,新建、删除、查找联系人以及修改联系人信息,打印成员列表的功能,意味着我们要把这些功能放到不同的函数中来实现。
通讯录的初始化
一开始我们要写一个初始化通讯录的函数,这个函数要在contact.h文件中声明,在contact.c文件中实现
为什么传地址呢?第一个原因是比起传值,传地址的效率更高。第二个原因是形参的改变不会影响实参。
对count这个整型变量初始化很简单,那么怎么对data这个数组初始化,使数组内的所有元素均为0呢,这里就要用到memset
void* memset(void* destination, int c, size_t count1)
//这个函数会对目标的字节进行操作
//传参时,第一个参数是目标的地址,第二个参数是初始化的内容,第三个参数是要操作的字节数
//memset(pc->data,0,sizeof(pc->data));
//这句代码会把data数组的所有元素初始化为0
初始化之后,我们就依次来实现新建、删除等函数,这些函数都会在contact.h文件中声明,在contact.c文件中实现,在test.c文件中被调用
新建联系人
打印联系人
查找联系人
删除和修改联系人
这两个功能我会一起描述,为什么呢?因为无论是删除,还是修改,首先你肯定要知道删除谁,修改谁,因此我们还要写一个函数用来查找删除或修改人的信息存放在数组的哪个位置
然后在写删除函数和修改函数时,我们就要在函数内部调用这个查找函数
删除函数
这个for循环直接使后面的数据覆盖掉前面的数据,进而实现了对指定联系人的删除
修改函数
现在我们就完成了这个通讯录,整体的代码如下
contact.h文件
#pragma once
#define Max_name 20
#define Max_sex 10
#define Max_tele 15
#define Max_addr 30
#define MAX 1000
//类型的定义
typedef struct PeoInfo //使用typedef把struct PeoInfo重命名为PeoInfo
{
char name[Max_name];
char sex[Max_sex];
int age;
char tele[Max_tele];
char addr[Max_addr];
}PeoInfo;
typedef struct Contact //把struct Contact重命名为Contact
{
PeoInfo data[MAX]; //存放联系人信息
int count; //记录当前通讯录人数
}Contact;
//初始化通讯录声明
void first(Contact* pc);
//新建联系人声明
void AddContact(Contact* pc);
//打印联系人声明
void PrintContact(Contact* pc);
//查找联系人声明
void SearchContact(Contact* pc);
//查找函数声明
int find(Contact* pc, char name[]);
//删除联系人声明
void DelContact(Contact* pc);
//修改联系人声明
void ModContact(Contact* pc);
contact.c文件
#include "contact.h"
#include <string.h>
//通讯录初始化实现
void first(Contact* pc)
{
pc->count = 0;
memset(pc->data, 0, sizeof(pc->data));
}
//新建联系人实现
void AddContact(Contact* pc)
{
if (pc->count == MAX)
{
printf("通讯录已满,无法添加\n");
return; //这个函数不需要返回值,这里的return是结束函数的作用
}
//通讯录未满,增加一个人的信息
printf("请输入名字:\n");
scanf("%s", pc->data[pc->count].name); //因为name是数组名,所以这里不用取地址“&”
printf("请输入性别:\n");
scanf("%s", pc->data[pc->count].sex);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->count].age));
printf("请输入电话:\n");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->count].addr);
(pc->count)++;
}
//打印联系人实现
void PrintContact(Contact* pc)
{
int i = 0;
if (pc->count == 0)
{
printf("无法打印,通讯录中没有联系人\n");
return;
}
//打印标题
printf("姓名\t性别\t年龄\t电话\t地址\n");
//打印数据
for(i = 0;i < pc->count;i++)
{
printf("%-5s\t%-5s\t%-5d\t%-5s\t%-5s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tele, pc->data[i].addr);
}
}
//查找联系人实现
void SearchContact(Contact* pc)
{
int i = 0;
char NAME[MAX];
if (pc->count == 0)
{
printf("通讯录中无联系人,无法查找\n");
return;
}
printf("请输入您要查找的联系人姓名:\n");
scanf("%s", NAME);
for (i = 0; i < pc->count; i++)
{
if (strcmp(NAME,pc->data[i].name) == 0)//字符串的比较
{
printf("姓名\t性别\t年龄\t电话\t地址\n");
printf("%-5s\t%-5s\t%-5d\t%-5s\t%-5s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age, pc->data[i].tele, pc->data[i].addr);
return;
}
}
printf("通讯录中无此联系人,查找失败\n");
}
//查找函数实现
int find(Contact* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
return i;//如果找到了,返回数组的下标
}
return -1;//没找到
}
//删除联系人实现
void DelContact(Contact* pc)
{
int i = 0;
char Name[MAX];
printf("请输入您要删除的联系人姓名:\n");
scanf("%s", Name);
int pos = find(pc,Name);
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
else
for (i = pos;i<pc->count-1;i++)
{
pc->data[pos] = pc->data[pos + 1];
}
(pc->count)--;
}
//修改联系人实现
void ModContact(Contact* pc)
{
printf("请输入您要修改的联系人信息的姓名:\n");
char name2[MAX];
scanf("%s", name2);
int pos = find(pc, name2);
if (pos == -1)
{
printf("要修改的联系人不存在\n");
return;
}
printf("请输入新的联系人姓名:\n");
scanf("%s", pc->data[pos].name);
printf("请输入新的联系人性别:\n");
scanf("%s", pc->data[pos].sex);
printf("请输入新的联系人年龄:\n");
scanf("%d", &pc->data[pos].age); //结构体中只有age不是数组,因此在scanf的位置要取地址
printf("请输入新的联系人电话:\n");
scanf("%s", pc->data[pos].tele);
printf("请输入新的联系人地址:\n");
scanf("%s", pc->data[pos].addr);
}
test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "contact.h"
void menu() //菜单
{
printf("*****1.add 2.del*****\n");
printf("*****3.print 4.search**\n");
printf("*****5.modify 0.exit***\n");
}
enum Option
{
EXIT, //默认为0,然后递增1
ADD, //1
DEL, //2
PRINT, //3
SEARCH, //4
MODIFY //5
};
int main()
{
int input = 0;
Contact con; //通讯录
//初始化通讯录
first(&con);
do
{
printf("请选择:\n");
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出成功\n");
break;
case ADD:
AddContact(&con); //新建联系人
break;
case DEL:
DelContact(&con);
break;
case PRINT:
PrintContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModContact(&con);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
这个通讯录仍然存在问题,比如如果有重名的人该怎么办,又或者是开辟的空间超出了栈区的范围的问题,我会在学习完动态内存开辟后,优化我的代码,希望大家能指出我的代码中的错误或者可以优化的地方,谢谢你的观看!