一、题目要求
实现一个通讯录 ,里面存放100个人的信息,完成通讯录的基本操作功能:增删查改联系人
二、解题思路
(1)通讯录存放100个人的信息?
创建一个数组作为通讯录,100个元素,每个元素里面存放每个人的信息,所以数组里面的每个元素应该是结构体类型。
(2)每个人的信息应该包括哪些?
结构体里面应包含名字,性别,年龄,电话,地址
(3)通讯录应该具备哪些功能?
a.增加联系人
b.删除联系人
c.查找联系人
d.修改联系人
e.显示联系人
f.排序联系人信息(按照年龄或者名字)
三、模块划分
建立三个文件:
test.c 用于测试通讯录的相关功能
contsct.c 通讯录的实现模块(用函数实现功能)
contact.h 声明(函数的声明)
四、代码实现
test.c文件
c
#define _CRT_SECURE_NO_WARNINGS 1
//C语言实现通讯录--静态版
//菜单
#include "contact.h"
void menu() {
printf("****************************************************\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...语句中选择功能时,case 1,2,3...这样的选项不能让代码阅读者清晰地联想到各个数字代表实现什么功能
但是使用枚举,在case语句中用case 1代表增加联系人的时候就可写成case add
当枚举中的选项和菜单上的数字匹配上之后,在case语句中想实现哪个功能,写哪个选项就可以了
这样的话,case里面的选项和我们想实现的功能的意思就关联起来了
*/
enum Option {
Exit,
add,
del,
search,
modify,
show,
sort
};
int main() {
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 show:
{
ShowContact(&con);
break;
}
case sort:
{
SortContact(&con);
break;
}
case Exit:
{
printf("退出通讯录\n");
break;
}
default:
{
printf("选择错误\n");
break;
}
}
} while (input);
return 0;
}
contact.c文件
c
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
//以后涉及到变化,就在这里修改就可以
//这里不能写成枚举,因为枚举常量必须是同一类型的枚举的可能取值,而这里定义的是变量的长度,枚举选项的取值是递增往下走的,对于这里是自己指定的长度,没必要定义成枚举类型
//初始化通讯录--应传址
void InitContact(Contact* pc) {//修改结构体里面的内容,传址
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));//pc所指向的data数组这块空间全改为0,1个字节1个字节地改变,数组总共有sizeof(pc-data)个字节
/*
注意初始化pc所指向的data数组时,不能写成pc->data=0;
因为pc里放的数组是连续的空间,data数组名是地址,是个常量的值,不能这样直接改成0,这样修改的是地址而不是里面的值
明确目的:想把pc所指向通讯录的data数组所有元素改为0,改的是一片空间,应该用memset函数
*/
}
/*
memset(内存块的指针,内存块后面num个字节要设置的内容,num)
memset 内存设置函数
*/
/*
错误的初始化通讯录--传值
void InitContact(Contact c){
pc.sz=0;
//...
}
这里的结构体传参,传的是结构体变量时,
函数内部的修改不会影响到通讯录结构体con,因为con作为结构体对象,当进行值传递的时候,c是con的一份临时拷贝,修改不会影响con
*/
//增加指定联系人
void AddContact(Contact* pc) {
if (pc->sz == MAX) {
printf("通讯录已满,无法添加\n");
return;//如果通讯录已满,直接返回,不返回的话继续指向下面代码会造成bug
}
//添加个人信息时是在通讯录的结构体数组data中添加,数组的每个元素都是PeoInfo类型
printf("请输入名字:");
scanf("%s", pc->data[pc->sz].name);//注意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 DelContact(Contact* pc) {
// if (pc->sz == 0) {
// printf("通讯录为空,无法删除\n");
// return;
// }
// //1.根据要删除的人的名字找到在通讯录中的下标
// char name[MAX_NAME] = { 0 };
// printf("请输入要删除的人的名字:");
// scanf("%s", name);
// int i = 0; //i用来遍历通讯录中个人信息的数组
// int pos = 0;//使用pos指向要删除的人的下标
// for (i = 0; i < pc->sz; i++) {
// if (0 == strcmp(pc->data[i].name, name)) {
// pos = i;
// break;
// //这里考虑的是不重名的情况,找到了就直接把要删除下标给pos
// }
// }
// if (i == pc -> sz) {//说明全部遍历完数组没找到退出来的
// printf("要删除的人不存在\n");
// return;
// }
// /*
// 如果用pos下标来怎么说明全部遍历完数组没找到退出来的情况呢?
// 应该在函数最开始将pos=-1;
// 注意不能将pos初始值置为0,因为可能要删除的人的下标是0
// if(pos==-1){//如果pos为-1,说明刚刚遍历的过程没有将pos的值改变,即全部遍历完数组没找到退出来的情况
// printf("要删除的人不存在\n");
// return;
// }
// */
// //2.删除pos下标的数据
// for (i = 0; i < pc->sz - 1; i++) {
// pc->data[i] = pc->data[i + 1];
// //这里采用的是覆盖删除,后一个数据赋值到前一个数据,覆盖数组的长度为pc->sz-1
// }
// pc->sz--; //删除完数组元素的个数-1
// printf("删除成功\n");
//}
//显示联系人信息
void ShowContact(Contact* pc) {
int i = 0;
//打印标题
//在%后面加上负号表示左对齐的方式,在%s的s前面加上数字可以限制打印出来最多几个字节
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++) {
printf("%-10s %-4d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
}
//实际上,删查改联系人都需要先在通讯录中查找
//为了减少代码的冗余度,可以封装一个查找的函数FindByName
//封装的这个函数前面加static,这个函数只能在自己所在的.c文件中使用,其他的源文件看不见该函数,对它加以保护
//根据名字在通讯录中查找下标的函数
static int FindByName(const Contact* pc, char name[]) {
int i = 0;
for (i = 0; i < pc->sz; i++) {
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
//找不到直接返回-1
/*
不要写成
if(i==pc->sz){
return -1;
}
因为写成这样,if进去,条件成立才有返回,不成立无返回
编译器在编译时发现这样的代码考虑不周全,有些情况是存在返回值的,就会发出警告
所以,严谨的情况下,直接返回既清晰又严谨
*/
}
//改进后的删除联系人函数
void DelContact(Contact* pc) {
if (pc->sz == 0) {
printf("通讯录为空,无法删除\n");
return;
}
//1.根据要删除的人的名字找到在通讯录中的下标
char name[MAX_NAME] = { 0 };
printf("请输入要删除的人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1) {
printf("要删除的人不存在\n");
return;
}
//2.删除pos位置上的数据
int i = 0;
for (i = pos; i < pc->sz; i++) {
pc->data[i] = pc->data[i + 1];//从pos下标处开始覆盖
}
pc->sz--;
printf("删除成功\n");
}
//查找联系人
void SearchContact(const Contact* pc) {//查找不需要修改通讯录,使用const加以保护,防止被修改
char name[MAX_NAME] = { 0 };
printf("请输入要查找的人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1) {
printf("要查找的人不存在\n");
return;
}
//找到的话直接打印这个联系人的信息
printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-4d %-5s %-12s %-30s\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) {
char name[MAX_NAME] = { 0 };
printf("请输入要修改的人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if(pos == -1){
printf("要修改的人不存在\n");
return;
}
//修改:把要修改人下标为pos的信息全都再录入一遍
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 cmp_by_name(const void* e1, const void* e2) {//e1,e2指针各自指向一个人的信息
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void SortContact(Contact* pc) {
//按照名字来排序
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
printf("排序成功\n");
//调用显示联系人函数ShowContact,自动打印一下排序后的结果
ShowContact(pc);
}
contact.h文件
c
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
//结构体存放每个联系人的信息
typedef struct PeoInfo { //typedef:给已定义的变量类型起个别名,这里的作用是给struct Peoinfo起个别名PeoInfo
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//结构体存放通讯录
typedef struct Contact {
PeoInfo data[MAX]; //结构体数组存放每个联系人的信息
int sz; //记录通讯录中的有效信息
}Contact, * pContact;//*pContact意思是将结构体指针struct Contact*重命名为pContact
//初始化通讯录--传址
void InitContact(Contact* pc);
//增加指定联系人
void AddContact(Contact* pc);
//删除指定联系人
void DelContact(Contact* pc);
/*
传址可以写成void ...Contact(pContact pc);
这里通过pContact定义出来的指针也是结构体指针
*/
//显示联系人信息
void ShowContact(Contact* pc);
//查找联系人
void SearchContact(const Contact* pc);
//修改联系人
void ModifyContact(Contact* pc);
//排序联系人
void SortContact(Contact* pc);
五、运行结果
六、总结
静态版本的通讯录:
缺点1 通讯录的大小是固定的--->100个元素,空间大小不够灵活
✨解决办法:动态内存分配
缺点2 数据不能永久保存
之前通讯录中的信息都是保存在内存中的,程序退出,内存就回收了,下一次重新运行程序,内存重新分配,之前的数据就不见了。
✨解决办法:存入文件中-->文件操作