前言:
在之前的学习中我们已经了解了结构体的一些知识,有了之前的知识的储备,在这里我们就可以尝试通讯录的实现。
目录
- 问题描述
- 基本流程
- 前期的准备工作
- 实现过程
- 第一阶段
- 第二阶段
- 第三阶段
- 1.增加联系人
- 2.删除联系人
- 3.打印通讯录
- 4.查找指定联系人
- 5.修改联系人
- 6.排序
- 7.销毁通讯录
- 代码以及总结
- test.c代码
- contact.c代码
- contact.h代码
问题描述
用c语言来进行通讯录的基本实现。
基本流程
实现通讯录的基本流程分为以下几步:
1,首先通讯录是用来存放联系人的信息,一个人信息有很多,例如(姓名,性别,年龄等),所以这里使用我们之前学过的结构体来存放联系人的信息。
2,一个通讯录所包含的联系人肯定不止一个,所以需要一个数组来存放,因为创建了联系人结构体的类型,所以需要使用相同结构体类型的数组来存放多个人的信息。(这里采用了动态内存的方式来存储联系人,传回来的为动态内存的首地址,也可以将其看为一个结构体类型的数组来使用)
3,通讯录的主要功能:增删查改联系人,打印通讯录,排序以及退出通讯录等功能。
增加联系人:往数组里面放元素。
删除联系人:找到联系人,将需要删除的联系人后面的联系人前移一位实现覆盖删除。
查找联系人:一般按名字查找。
修改联系人:找到联系人的位置,在相同位置重新录入信息实现覆盖修改。
打印通讯录:打印数组,把我们录入的展示出来。
联系人排序:选择用名字排序还是年龄排序或者其他信息排序,可以使用qsort函数来实现(这个函数在下期我会拿出来给大家细讲)。
退出:当我们完成我们想要的工作后便退出通讯录
4,使用动态内存开辟的方法来储存联系人,节约内存。
5,使用文件操作来保存信息和写入信息,使系统运行时自动载入已保存的信息。
前期的准备工作
首先我们还是在一个项目中创建三个文件,两个源文件,一个文件存放主函数,一个文件来存放通讯录的实现函数,还有一个头文件来声明函数。
实现过程
第一阶段
首先我们还是先搭建主体的框架,写一个菜单来实现相应的功能:
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");printf("**************************************\n");
}
上面主函数内部的分支循环 case 中我们输入的字符是为了便于理解,但是这样是没办法实现最开始的以数字来进行菜单选项的初衷的,所以我们需要枚举这些选择功能来达到这样的一个效果:
enum option
{ADD,DEL,SEARCH,MODIFY,SHOW,SORT,EXIT
};
写好之后我们可以看出结果如下:
菜单写出来之后,自然而然我们要让玩家能够根据信息输入相应的数字进行游戏,效果图如下:
do{menu();printf("请选择数字->");scanf("%d", &input);switch (input){case 1://cese ADD:AddContact(&per);break;case 2://cese DEL:DelContact(&per);break;case 3://cese SEARCH:SearchContact(&per);break;case 4://cese MODIFY:ModifyContact(&per);break;case 5://cese SHOW:ShowContact(&per);break;case 6://cese SORT:SortContact(&per);break;case 0://cese EXIT:DestroyContact(&per);break;default:printf("选择错误,请重新输入\n");break;}} while (input);
到此我们前期的框架基本上已经搭建好了。紧接着就是建立我们的通讯录了,对我们想要的功能进行依次的实现。
第二阶段
第一步:我们需要定义联系人的一些属性,定义了一个结构体PeoInfo,简单分析不难发现,这个结构体表示的就是一个人的信息。姓名、电话、地址、年龄、性别这几条信息存放在一个字符数组中:
struct PeoInfo
{char name[MAX_NAME];char sex[MAX_SEX];char tele[MAX_TELE];int age;char addr[MAX_ADDR];
};
第二步:就是定义通讯录的内容,而在这一步为了高效的实现的日常生活动态增长的一个情况,我们使用动态增长的一个版本。结构体有三个成员,一个是结构体数组,数组中的每个元素就是上面创建的结构体。还有一个元素是整型变量sz。上一个结构体代表的是一个人的信息,最后就是满足动态增长而设置的判断,而通讯录的目的就是把多个人的信息存放在一起,所以这个结构体Contact所代表的的就是我们的通讯录:
struct Contact
{struct PeoInfo* data;//int sz; //int capacity; //当前的容量
};
紧接着就是为了实现我们上面结构体内部的这些内容,我们需要进行一些声明:
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 100#define DEFAULT_SZ 3
#define INC_SZ 2
到此对通讯录的建立就完成了,接下来我们要做的便是关键一步对通讯录进行相应的初始化操作以及功能实现
第三阶段
首先进行的是初始化操作(后面都以动态增长为例):
初始化函数InitContact,初始化通讯录,使用malloc(动态内存开辟函数)开辟动态内存来存放联系人的信息,sz为有效联系人个数,capacity为通讯录的最大容量
void InitContact(struct Contact* pc)
{assert(pc); //断言pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ *sizeof(struct PeoInfo));if (pc->data == NULL){printf("InitContact fail");return;}pc->sz = 0;pc->capacity = DEFAULT_SZ;}
接下来就是实现相应功能的功能函数了。
1.增加联系人
//动态增长联系人
void AddContact(struct Contact* pc)
{assert(pc);if (0 == check_capacity(pc)){return;}//增加人的信息printf("请输入名字:>");scanf("%s", pc->data[pc->sz].name);printf("请输入性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入年龄:>");scanf("%d", &(pc->data[pc->sz].age));printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);printf("请输入地址:>");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("成功增加联系人\n");
}
添加联系人就是往开辟好的内存里面存放联系人的结构体信息,在每次添加之前需要先判断通讯录是否已经满了,若是满了需要增容,使用realloc来重新分配动态内存,这里每次满了之后默认增加两个联系人的空间,成功增加联系人之后总数加1,sz+1。检查容量函数如下:
int check_capacity(struct Contact* pc)
{if (pc->sz == pc->capacity){struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(struct PeoInfo));if (ptr != NULL){pc->data = ptr;pc->capacity += INC_SZ;printf("成功增容");return 1;}else{printf("AddContact fail");return 0;}}elsereturn 1;
}
2.删除联系人
删除之前需要找到联系人的位置,因为这个函数我们不用让外面看到,所以用static来修饰这个函数,这样的话这个函数就只能在该源文件内部使用。该函数接收两个参数,一个是通讯录结构体,一个是要查找的成员名称。这里通过名字查找代码如下:
static int FindByName(const struct Contact* pc, char name[])
{for(int i = 0; i < pc->sz; i++){if (0 == strcmp(pc->data[i].name, name)){return i;}}return -1;
}
查找的过程采用遍历的方式,让通讯录中的每一个成员的名字来和要查找的成员名字来作比较,这里用到字符串比较函数strcmp,当这两个字符串相等时,该函数返回值为0,第一个大于第二个返回一个大于0的值,小于则返回一个小于0的值。当判断函数的返回值等于0时,说明找到了要查找的元素,紧接着返回该元素的下标。如果找不到返回-1即可,在之前我们也已经介绍过这个函数。
主体函数如下:
//删除指定联系人
void DelContact(struct Contact* pc)
{char name[MAX_NAME];printf("请输入删除的人的姓名:->");scanf("%s", name);//判断输入是否合理int ret=FindByName(pc, name);if (ret == -1)printf("删除的人不存在,请重新输入\n");else{int j = 0;for (j = ret; j < pc->sz - 1; j++){pc->data[j] = pc->data[j + 1];}pc->sz--;printf("删除成功\n");}
}
删除联系人就是找到需要删除的联系人,然后将后面的联系人都往前移动一位,这里最后一位联系人可能会被后面空白覆盖,所以判断循环的条件改为 j<sz-1,pc->data[i] = pc->data[i + 1];此时j+1最大值位sz-1就是最后一个联系人的位置,当sz-1覆盖到sz-2时,原位置上的数据还是存在的,但是已经不是有效的联系人了,打印时不打印就可以,移动完成之后,总数减1即sz-1。
3.打印通讯录
打印的时候我们可以估算一下各个信息的长度,然后打印。%10s的意思就是打印的空间占10个字符型空间。由于通讯录中可能有多条信息,所以打印的时候采用一个循环来打印,打印的次数就是有效信息数sz的值。
//打印通讯录
void ShowContact(const struct Contact* pc)
{printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "地址");for (int i = 0; i < pc->sz; i++){printf("%-20s\t%-5s\t%-5d\t%-12s\t%-30s\n", pc->data[i].name,pc->data[i].sex,pc->data[i].age,pc->data[i].tele,pc->data[i].addr);}
}
4.查找指定联系人
先输入要查找人的姓名,然后调用查找函数,观察返回值,如果找不到,就打印查找的人不存在。如果找到,返回这个人的下标,我们就可以通过这个人的下标来把这个人的信息展示出来。
//查找指定联系人
void SearchContact(const struct Contact* pc)
{char name[MAX_NAME];printf("请输入查找的人的姓名:->");scanf("%s", name);//判断输入信息是否存在int ret = FindByName(pc, name);if (ret == -1)printf("查找的人不存在,请重新输入\n");else{printf("%-20s\t%-10s\t%-20s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-20s\t%-10s\t%-20s\t%-12s\t%-30s\n", pc->data[ret].name,pc->data[ret].sex,pc->data[ret].age,pc->data[ret].tele,pc->data[ret].addr);}
}
5.修改联系人
这一步是先进行查找操作,先找到联系人,然后在这个联系人的位置重新录入一遍信息即可,代码如下:
//修改联系人
void ModifyContact(struct Contact* pc)
{char name[MAX_NAME];printf("请输入修改的人的姓名:->");scanf("%s", name);//判断输入信息是否存在int ret = FindByName(pc, name);if (ret == -1)printf("修改的人不存在,请重新输入\n");else{printf("请输入名字:>");scanf("%s", pc->data[ret].name);printf("请输入性别:>");scanf("%s", pc->data[ret].sex);printf("请输入年龄:>");scanf("%d", &(pc->data[ret].age));printf("请输入电话:>");scanf("%s", pc->data[ret].tele);printf("请输入地址:>");scanf("%s", pc->data[ret].addr);printf("修改成功\n");}
}
6.排序
我们要根据成员的某一个信息来排序,我们可以使用姓名和年龄来排序,这里我们使用qsort函数来进行相关操作:
//通过名字进行排序
int CmpByName(const void* n1, const void* n2)
{return (((struct PeoInfo*)n1)->name - ((struct PeoInfo*)n2)->name);}
//通过年龄进行排序
int CmpByAge(const void* age1, const void* age2)
{return (((struct PeoInfo*)age1)->age - ((struct PeoInfo*)age2)->age);}//排序
void SortContact(struct Contact* pc)
{//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByName);qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);}
7.销毁通讯录
使用完毕之后,应该销毁动态内存开辟的空间,防止动态内存,代码如下:
//销毁
void DestroyContact(struct Contact* pc)
{free(pc->data);pc->data=NULL;pc->capacity = 0;pc->sz = 0;
}
代码以及总结
test.c代码
#define _CRT_SECURE_NO_WARNINGS 1
#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");printf("**************************************\n");
}enum option
{ADD,DEL,SEARCH,MODIFY,SHOW,SORT,EXIT
};int main()
{int input = 0;//建立通讯录struct Contact per;//初始化通讯录InitContact(&per);do{menu();printf("请选择数字->");scanf("%d", &input);switch (input){case 1://cese ADD:AddContact(&per);break;case 2://cese DEL:DelContact(&per);break;case 3://cese SEARCH:SearchContact(&per);break;case 4://cese MODIFY:ModifyContact(&per);break;case 5://cese SHOW:ShowContact(&per);break;case 6://cese SORT:SortContact(&per);break;case 0://cese EXIT:DestroyContact(&per);break;default:printf("选择错误,请重新输入\n");break;}} while (input);return 0;
}
contact.c代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"//初始化通讯录
//void InitContact(struct Contact* pc)
//{
// assert(pc);
// pc->sz =0
// memset(pc->data, 0, MAX * sizeof(struct PeoInfo));
//
//}
//动态的初始化版本
void InitContact(struct Contact* pc)
{assert(pc);pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ *sizeof(struct PeoInfo));if (pc->data == NULL){printf("InitContact fail");return;}pc->sz = 0;pc->capacity = DEFAULT_SZ;}
//静态增加联系人
//void AddContact(struct Contact* pc)
//{
// assert(pc);
//
// if (pc->sz == MAX)
// {
// printf("通讯录已满,无法添加数据\n");
// return;
// }
//
// //增加人的信息
// printf("请输入名字:>");
// scanf("%s", pc->data[pc->sz].name);
// printf("请输入性别:>");
// scanf("%s", pc->data[pc->sz].sex);
// printf("请输入年龄:>");
// scanf("%d", &(pc->data[pc->sz].age));
// printf("请输入电话:>");
// scanf("%s", pc->data[pc->sz].tele);
// printf("请输入地址:>");
// scanf("%s", pc->data[pc->sz].addr);
//
// pc->sz++;
// printf("成功增加联系人\n");
//}int check_capacity(struct Contact* pc)
{if (pc->sz == pc->capacity){struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(struct PeoInfo));if (ptr != NULL){pc->data = ptr;pc->capacity += INC_SZ;printf("成功增容");return 1;}else{printf("AddContact fail");return 0;}}elsereturn 1;
}
//动态增长联系人
void AddContact(struct Contact* pc)
{assert(pc);if (0 == check_capacity(pc)){return;}//增加人的信息printf("请输入名字:>");scanf("%s", pc->data[pc->sz].name);printf("请输入性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入年龄:>");scanf("%d", &(pc->data[pc->sz].age));printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);printf("请输入地址:>");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("成功增加联系人\n");
}//销毁
void DestroyContact(struct Contact* pc)
{free(pc->data);pc->data=NULL;pc->capacity = 0;pc->sz = 0;
}//打印通讯录
void ShowContact(const struct Contact* pc)
{printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "地址");for (int i = 0; i < pc->sz; i++){printf("%-20s\t%-5s\t%-5d\t%-12s\t%-30s\n", pc->data[i].name,pc->data[i].sex,pc->data[i].age,pc->data[i].tele,pc->data[i].addr);}
}static int FindByName(const struct Contact* pc, char name[])
{for(int i = 0; i < pc->sz; i++){if (0 == strcmp(pc->data[i].name, name)){return i;}}return -1;
}//删除指定联系人
void DelContact(struct Contact* pc)
{char name[MAX_NAME];printf("请输入删除的人的姓名:->");scanf("%s", name);//判断输入是否合理int ret=FindByName(pc, name);if (ret == -1)printf("删除的人不存在,请重新输入\n");else{int j = 0;for (j = ret; j < pc->sz - 1; j++){pc->data[j] = pc->data[j + 1];}pc->sz--;printf("删除成功\n");}
}//查找指定联系人
void SearchContact(const struct Contact* pc)
{char name[MAX_NAME];printf("请输入查找的人的姓名:->");scanf("%s", name);//判断输入信息是否存在int ret = FindByName(pc, name);if (ret == -1)printf("查找的人不存在,请重新输入\n");else{printf("%-20s\t%-10s\t%-20s\t%-12s\t%-30s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-20s\t%-10s\t%-20s\t%-12s\t%-30s\n", pc->data[ret].name,pc->data[ret].sex,pc->data[ret].age,pc->data[ret].tele,pc->data[ret].addr);}
}//修改联系人
void ModifyContact(struct Contact* pc)
{char name[MAX_NAME];printf("请输入修改的人的姓名:->");scanf("%s", name);//判断输入信息是否存在int ret = FindByName(pc, name);if (ret == -1)printf("修改的人不存在,请重新输入\n");else{printf("请输入名字:>");scanf("%s", pc->data[ret].name);printf("请输入性别:>");scanf("%s", pc->data[ret].sex);printf("请输入年龄:>");scanf("%d", &(pc->data[ret].age));printf("请输入电话:>");scanf("%s", pc->data[ret].tele);printf("请输入地址:>");scanf("%s", pc->data[ret].addr);printf("修改成功\n");}
}//通过名字进行排序
int CmpByName(const void* n1, const void* n2)
{return (((struct PeoInfo*)n1)->name - ((struct PeoInfo*)n2)->name);}
//通过年龄进行排序
int CmpByAge(const void* age1, const void* age2)
{return (((struct PeoInfo*)age1)->age - ((struct PeoInfo*)age2)->age);}//排序
void SortContact(struct Contact* pc)
{//qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByName);qsort(pc->data, pc->sz, sizeof(struct PeoInfo), CmpByAge);}
contact.h代码
#pragma once
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 100#define DEFAULT_SZ 3
#define INC_SZ 2struct PeoInfo
{char name[MAX_NAME];char sex[MAX_SEX];char tele[MAX_TELE];int age;char addr[MAX_ADDR];
};//静态增长版本
//struct Contact
//{
// struct PeoInfo data[MAX];
// int sz;
//};//动态增长版本
struct Contact
{struct PeoInfo* data;//int sz; //int capacity; //当前的容量
};//初始化通讯录
void InitContact(struct Contact* pc);//增加联系人
void AddContact(struct Contact* pc);//显示通讯录的信息
void ShowContact(const struct Contact* pc);//删除联系人
void DelContact(struct Contact* pc);//查找联系人
void SearchContact(const struct Contact* pc);//修改联系人
void ModifyContact(struct Contact* pc);//排序
void SortContact(struct Contact* pc);//销毁
void DestroyContact(struct Contact* pc);
总结:
以上就是关于通讯录的全部知识与介绍,大家最重要的是把整体思路捋清楚,有个大概的方向,然后慢慢去实现。