前言:内容包括三个模块:测试通讯录模块,声明模块,通讯录实现模块
实现一个通讯录:
1 可以存放100个人的信息
每个人的信息:
名字 性别 年龄 电话 地址
2 增加联系人信息
删除联系人信息
查找联系人信息
修改联系人信息
排序联系人信息
打印联系人信息
由于删除,查找,修改都需要找到被操作联系人的下标,所以我们可以独立写一个FindByName函数,用于返回被操作联系人的下标,让删除,查找,修改函数直接调用FindByName函数
静态版的通讯录(固定的联系人个数)分三个文件实现:
main.c:测试通讯录
contact.c:通讯录的实现(函数功能的具体实现)
contact.h:声明+类型定义
结构体最好传地址,不要选择传值
传址效率高
效果:
静态版整体实现:
main.c
#include"contact.h"
void menu()
{
printf("*************************\n");
printf("**** 1.add 2.del **\n");
printf("**** 3.search 4.modify**\n");
printf("**** 5.sort 6.show **\n");
printf("**** 0.exit **\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
SHOW
};
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 SORT:
SortContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
contact.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#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;
//通讯录
typedef struct Contact
{
PeoInfo data[MAX];
int sz;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//打印联系人信息
void ShowContact(const Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//删除联系人信息
void DelContact(Contact* pc);
//查找联系人信息
void SearchContact(const Contact* pc);
//修改联系人信息
void ModifyContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
contact.c
#include"contact.h"
void InitContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
void ShowContact(const Contact* pc)
{
printf("%-10s %-5s %-5s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-12s %-20s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
}
void AddContact(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满\n");
return;
}
printf("请输入姓名:");
char name[MAX_NAME] = { 0 };
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");
}
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;
}
void DelContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("请输入要删除人的名字:");
char name[MAX_NAME] = { 0 };
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("不存在要删除人的信息\n");
return;
}
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void SearchContact(const Contact* pc)
{
printf("请输入要查找人的名字:");
char name[MAX_NAME] = { 0 };
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("被查找人信息不存在\n");
return;
}
printf("%-10s %-5s %-5s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-5s %-12s %-20s\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)
{
printf("请输入被修改联系人的名字:");
char name[MAX_NAME] = { 0 };
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 cmp_by_name(const void* e1, const void* 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");
}
模块1:测试通讯录 (main.c)
#include"contact.h"
void menu()
{
printf("*************************\n");
printf("**** 1.add 2.del **\n");
printf("**** 3.search 4.modify**\n");
printf("**** 5.sort 6.show **\n");
printf("**** 0.exit **\n");
}
enum Option
{
EXIT, //0
ADD, //1
DEL, //2
SEARCH,//3
MODIFY,//4
SORT, //5
SHOW //6
};
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 SORT:
SortContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
思路:
1 do while循环上来先显示菜单让用户选择功能
2 switch语句根据用户的不同选择实现通讯录的增,删,查,改,排序,打印,退出
这里巧妙使用了enum枚举类型
enum里面的所有成员是未来的所有可能取值,值总0开始以1为增量递增
这样方便我们在设计case语句时可以直接使用功能的名字,而不需要记住每个功能的序号
模块2:声明+类型定义(contact.h)
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#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;
//通讯录
typedef struct Contact
{
PeoInfo data[MAX];
int sz;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//打印联系人信息
void ShowContact(const Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//删除联系人信息
void DelContact(Contact* pc);
//查找联系人信息
void SearchContact(const Contact* pc);
//修改联系人信息
void ModifyContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
在这个模块里,我们定义类型和放置声明
1 需要存储一个人的姓名,年龄,性别,电话,地址作为联系人信息,使用结构体
联系人信息的类型名重命名为PeoInfo
2 通讯录:多个联系人信息(使用数组)+联系人的个数
3 使用#define定义常量,方便以后调整数值,比如以前我要存100个联系人信息,现在我要存200个,那么可以直接将MAX 后面的100改成200,这样一个通讯录就能存下200人了
模块3:通讯录的实现(函数功能的具体实现)
初始化通讯录函数:InitContact
void InitContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
初始时联系人的个数sz为0,所有联系人的信息为0
使用memset函数初始化
memset头文件:
#include <string.h>
结构:
void * memset ( void * ptr, int value, size_t num )
第一个参数:要被初始化的元素的地址
第二个参数:初始化元素的个数
第三个参数:一个元素的大小,单位是字节
由于我们要将所有的联系人信息初始化为0,即将每个元素类型为PeoInfo的数组内容全部初始化为0,所以memset的使用如下:
memset(pc->data, 0, sizeof(pc->data))
pc->data:数组名表示数组首元素的地址
0:初始化的内容是0(元素要被初始化为什么)
sizeof(pc->data):数组的所有字节
sizeof(数组名):计算的是整个数组的大小,单位是字节
显示通讯录:void ShowContact
void ShowContact(const Contact* pc)
{
printf("%-10s %-5s %-5s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
//打印标题
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-5s %-12s %-20s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
}
pc是指向通讯录类型的结构体的指针,此函数只负责打印通讯录中的所有信息,不会更改通讯录中的内容,所以使用const修饰*pc,保护*pc不会被修改
%-20s:占多少宽度可以自己控制,加上负号,表示左对齐
pc->data[i].addr:pc是指向通讯录的指针,pc->data[i]:找到了通讯录中的成员data数组中的第i个元素,数组的每个元素类型是PeoInfo的结构体,使用.操作符就可以找到PeoInfo结构体中的成员:名字 性别 年龄 电话 地址
增加联系人信息:AddContact
void AddContact(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满\n");
return;
}
printf("请输入姓名:");
char name[MAX_NAME] = { 0 };
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");
}
1 判断通讯录是否已满,满了则无法添加,直接返回
2 通讯录未满,添加联系人信息:
sz记录了通讯录中联系人的个数,则添加一个联系人,它的下标是sz
因为现有sz个联系人,则最后一个联系人的下标是sz-1,那么sz为下标的空间就是空的
3 添加成功后,sz++,表示联系人的个数+1了
搜寻指定联系人并返回其下标:FindByName
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;
}
FindByName函数只服务于删除,查找,修改函数,static修饰FindByName函数,可以使得FindByName只能在自己的.c文件内部使用,即只能在contact.c文件内使用
1 遍历所有联系人信息,使用strcmp函数比较名字是否相同
2 若是strcmp的返回值为0,则表示相同,返回下标i
若是整个循环结束后仍未返回有效下标,则说明找不到此人的信息,返回-1
因为有效下标的值不可能为-1,所以返回-1可以表示找不到此人
删除联系人信息: DelContact
void DelContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("请输入要删除人的名字:");
char name[MAX_NAME] = { 0 };
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("不存在要删除人的信息\n");
return;
}
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
1 判断通讯录中联系人是否为空,为空则无需删除,直接返回
2 不为空,则查找到要被删除联系人的下标,使用覆盖法:
用FindByName函数返回联系人的下标:
返回值为-1:被删除联系人的信息不存在
返回其他值:覆盖法,后面的往前移
注意:下标只需要循环到倒数第二个位置就行了
for (i = pos; i < pc->sz - 1; i++)
假设有 1 2 3 4 5,元素个数sz=5,要删除3:
3的位置上覆盖4的值,4的位置上覆盖5的值,删除结束
最后一次覆盖的动作是4的位置上覆盖5的值,即我们最后只需要得到4的下标就行
5的下标:sz-1,4的下标:sz-2
3 删除成功,sz--,联系人个数减少了一个
查找联系人信息:SearchContact
void SearchContact(const Contact* pc)
{
printf("请输入要查找人的名字:");
char name[MAX_NAME] = { 0 };
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("被查找人信息不存在\n");
return;
}
printf("%-10s %-5s %-5s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-5s %-12s %-20s\n", pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
若是查找到了,直接打印此联系人的信息即可,此联系人的下标存储在pos中
修改联系人信息:ModifyContact
void ModifyContact(Contact* pc)
{
printf("请输入被修改联系人的名字:");
char name[MAX_NAME] = { 0 };
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");
}
查找到了直接修改此联系人的信息即可,即对存放此联系人信息的空间重新输入信息
排序联系人信息:SortContact
int cmp_by_name(const void* e1, const void* 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");
}
qsort函数可以排序任意类型的数据
头文件:
#include <stdlib.h>
结构:
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*))
第一个参数:被排序元素的地址
第二个参数:待排序元素的个数
第三个参数:一个待排序元素的大小,单位是字节
第四个参数:自己需要设计的比较函数的地址
比较两个元素的函数遵循以下设计原型:
int compar (const void* p1, const void* p2)
我们按照名字来排序,设计cmp_by_name函数:
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
由于e1和e2指针的类型都是void*,而void*不可直接解引用,++,--操作
所以我们需要将e1和e2强制转成PeoInfo*的指针(比较的两个元素是PeoInfo类型)
但是强制转换又是临时的,故而用结构体指针去访问其成员时,需要将(PeoInfo*)e1整体用括号括起来,才能使用->去访问其成员
比较两个字符串需要使用strcmp函数
动态版通讯录:使用动态开辟的空间存储联系人信息
动态版的通讯录:
1 默认能够存放3个人的信息: #define DEFAULT_SZ 3
2 空间不够若增容,一次增容2个:#define INC_SZ 2
动态版的通讯录是指:存储联系人信息的个数不固定,增添一个开辟一个空间,存储多少个就开辟多少个空间,不会多开辟,也不会少开辟
这里需要使用malloc函数动态开辟内存空间,realloc函数灵活调整由malloc函数动态开辟的空间
动态版通讯录的实现是在静态版的基础上改良:通讯录类型Contact,初始化函数InitContact,增加联系人信息函数AddContact,同时增添销毁动态开辟空间的函数DestroyContact,除此之外与静态版的通讯录并无异同
通讯录类型:Contact
typedef struct Contact
{
PeoInfo*data;
int sz;
int capacity;
}Contact;
成员包括:存储malloc动态开辟空间的起始地址的指针data
有效联系人个数sz
当前通讯录容量capacity
InitContact:
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));//动态开辟空间
if (pc->data == NULL)
{
printf("通讯录初始化失败:%s\n", strerror(errno));//打印错误信息
return;
}
pc->sz = 0;
pc->capacity = DEFAULT_SZ;//默认初始的容量为3
}
AddContact:
//扩容失败返回0
//扩容成功or不需要扩容 返回1
int CheckContact(Contact* pc)
{
if (pc->sz == pc->capacity)//增容
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
//调整data指向的一块空间,大小:原来的空间+每次增容的空间
if (ptr == NULL)//realloc动态开辟空间失败
{
printf("CheckContact:%s\n", strerror(errno));//打印错误信息
return 0;
}
else
{
pc->data = ptr;//将realloc开辟空间的起始地址交给data指针
pc->capacity += INC_SZ;
printf("增容成功,当前容量:%d\n",pc->capacity);
return 1;//扩容成功,返回1
}
}
return 1; //不需要扩容,返回1
}
void AddContact(Contact* pc)
{
int ret = CheckContact(pc);
if (ret == 0)
{
printf("增容失败\n");
return;
}
printf("请输入姓名:");
char name[MAX_NAME] = { 0 };
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");
}
1 在增加联系人信息之前需要判断是否需要增容
2 检查容量函数: CheckContact
扩容失败返回0
不需要扩容 or 扩容成功返回1
若是需要增容却增容失败(CheckContact的返回值为0),则返回
若是不需要增容或者增容成功,则进行下面的添加联系人信息的代码
3 CheckContact增容的实现
realloc调整原来malloc开辟的空间
void* realloc (void* ptr, size_t size)
第一个参数:被调整空间的起始地址
第二个参数:新的空间大小
DestroyContact:销毁动态开辟的空间
void DestroyContact(Contact* pc)
{
free(pc->data);//释放动态开辟的空间
pc->data = NULL;//一定要将指向动态开辟空间的指针置为NULL
pc->capacity = 0;
pc->sz = 0;
printf("释放空间……\n");
}