“我若成佛,天下无魔;我若成魔,佛奈我何。”
“小爷是魔,那又如何?”
下面我和一起来攻克通讯录的难关!!
明确通讯录的基本结构
实现一个通讯录:
人的信息:
名字+年龄+性别+电话+地址
实现通讯录的基本步骤
假设
1.存放100个人的信息
2.增加联系人
3.删除指定联系人
4.查找联系人
5.修改联系人
6.排序
7.显示联系人
首先我们要先创建三个文件
三个文件的功能如下:
- test.c -> 测试功能
- contact.c->通讯录相关的实现
- contact.h->通讯录相关的声明
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.shor显示 6.sort排序 ********\n");
printf("********** 0.exit退出 ********\n");
printf("****************************************************\n");
}
int main()
{
//使用do while结构打印一个菜单
int input = 0;
//创建通讯录
Contact con;//通讯录(这个con就是通讯录)
//初始化通讯录
InitContact(&con);//要用指针接收
//这里为什么要传地址(有两点原因)
//1.结构体传参传地址更加准确
//2.要修改这个通讯录就必须传的是地址(参考修改一个整数)
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
//由于枚举常量实际上就是整数,因此可以直接将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 PRINT:
PrintContact(&con);//打印,不会改变con
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
contact.h(类型的声明)
1.用结构体定义人的信息
//类型的声明
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
2.封装一个通讯录
在contact.h中声明函数
void InitContact(Contact* pc);//前面取的是con的地址 所以用pc指针接收
在contact.c中实现此函数
typedef struct Contact
{
PeoInfo data[MAX];//存放添加进来的人的信息,假设开辟100个空间
int sz;//记录当前通讯录中有效信息的个数
}Contact;
这里说明通讯录中既有人的信息,又有人的个数
3.初始化通讯录
void InitContact(Contact* pc)
{
pc->sz = 0;
//memset();
memset(pc->data, 0, sizeof(pc->data));//将pc指向的数组初始化为0
//也就是将开辟的100个空间全部都初始化为0
}
补充一个知识:memset 的使用
memset用于将一段内存区域的每个字节都设置为指定的值
void *memset(void *s, int c, size_t n);
- s:指向要
填充
的内存块的指针
。 - c:要
设置的值
,该值以 int 类型传递,但在填充内存时会被转换为 unsigned char 类型。(例如初始化为几) - n:要填充的
字节数
。
返回值指向内存块s
的指针
。
注意:
在contact.h中引入头文件#include<string.h>
4.增加联系人到通讯录中
声明:
void AddContact(Contact* pc);
实现:
void AddContact(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
//增加一个人的信息
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
//这里指的是将一个人的信息存放到date数组下标为sz的数组里面(因为刚开始pc指向的是date数组)
//数组名不需要取地址
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
//这里的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");
}
- 这里详细解释一下
pc->data[pc->sz].name
的意思
pc->data[pc->sz].name: pc
是指向Contact
结构体的指针
,pc->data
访问的是Contact
结构体中的data
数组,pc->sz
表示当前已存储的联系人数量
,pc->data[pc->sz]
就是data
数组中第pc->sz
个元素(即当前要添加的联系人信息),pc->data[pc->sz].name
则是该联系人的姓名数组
。由于数组名本身就代表数组首元素的地址,所以不需要取地址符 &
。
- pc->sz++ 又是什么意思呢
pc->sz++
是一个自增操作,它的作用是将Contact
结构体中的sz
成员的值加 1
。在添加新联系人信息时,sz
表示当前已存储的联系人数量。当成功添加一个新联系人后,已存储的联系人数量就会增加 1
,所以需要将sz
的值加 1
。这样,下次再添加联系人时,就会将新联系人的信息存储到 data 数组的下一个位置。
5.打印联系人信息
声明:
void ShorContact(const Contact* pc);
实现:
void ShorContact(const Contact* pc)
{
assert(pc);//断言一下,以防空指针
int i = 0;
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
//加负号表示左对齐
for (i = 0; i < pc->sz; i++)
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
注意:
- pc->sz 表示当前
Contact
结构体中已经存储的联系人数量
- for 循环的条件
i < pc->sz
确保循环会遍历data
数组中所有
已存储的联系人信息。每次循环结束后,i
的值会加1
,直到i
等于pc->sz
时,循环停止 - 这里的格式化字符串
"%20s\t%3d\t%5s\t%12s\t%30s\n"
详细解释如下: -
- %20s:表示以字符串形式输出,并且该字符串的最小宽度为 20 个字符。如果实际字符串长度小于 20,则会在左边补齐空格,用于输出联系人的姓名。
-
- %5d:表示以十进制整数形式输出,最小宽度为 5 个字符,用于输出联系人的年龄。
-
- %5s:表示以字符串形式输出,最小宽度为 5 个字符,用于输出联系人的性别。
-
- %12s:表示以字符串形式输出,最小宽度为 12 个字符,用于输出联系人的电话号码。
-
- %30s:表示以字符串形式输出,最小宽度为 30 个字符,用于输出联系人的地址。
-
- \t 是制表符,用于在输出中分隔不同的信息字段,使输出更加整齐。
-
- \n 是换行符,用于在输出完一个联系人的信息后换行,方便查看。
最后再详细说明一下作用:
pc->data
等价于(*pc).data
,表示访问pc
所指向的Contact
结构体中的data
数组。data[i]
表示data
数组中的第i
个元素,由于data
数组的元素类型是PeoInfo
,所以 data[i] 就是一个 PeoInfo 结构体变量,代表第i
个联系人的详细信息。
6.删除联系人的信息
声明:
void DelContact(Contact* pc);
实现:
static int FindByName(Contact* pc, char name[])
{
assert(pc);
int i = 0;//i 将作为循环计数器,用于访问 pc->data 数组中的每个元素
for (i = 0; i < pc->sz; i++)//遍历通讯录中的所有联系人
{
if (0 == strcmp(pc->data[i].name, name))//比较当前联系人的名字与要查找的名字是否相同
{
return i;//返回下标
}
}
return -1;//没找到该联系人就返回-1
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };//定义一个字符数组 name,用于存储用户输入的要删除的联系人名字
assert(pc);//断言 确保传入的 Contact* pc 指针是有效的
int i = 0;
if (pc->sz == 0)//检查通讯录是否为空
{
printf("通讯录为空,没有信息可以删除\n");
return;
}
printf("请输入要删除人的名字:>");
scanf("%s", name);
//删除
//1.查找
int pos = FindByName(pc, name);
//调用 FindByName 函数,在 Contact 结构体中查找指定姓名的联系人,并将返回的下标存储在 pos 变量中
if (pos == -1)//检查 pos 是否为 -1。如果为 -1,说明未找到要删除的联系人
{
printf("要删除的人不存在\n");
return;
}
//2.删除
for (i = pos; i < pc->sz - 1; i++)
//当找到要删除的联系人后,需要将该联系人后面的所有联系人信息依次向前移动一位,覆盖掉要删除的联系人信息。
{
pc->data[i] = pc->data[i + 1];
//从 pos 位置开始,将 data 数组中 i + 1 位置的元素赋值给 i 位置,直到遍历到倒数第二个元素
}
pc->sz--;
//删除联系人后,已存储的联系人数量减少 1,因此将 pc->sz 的值减 1
printf("删除成功\n");
}
对上面这段代码进行详细分析:
- 函数 FindByName功能概述:
FindByName
函数的主要功能是在Contact
结构体所存储的联系人信息中,根据输入的姓名
查找对应的联系人,并返回
该联系人在data
数组中的下标
。如果未找到
匹配的联系人,则返回-1
。 - DelContact 函数
DelContact
函数的主要功能是从Contact
结构体所存储的联系人信息中删除
指定姓名的联系人。
注意:
这里for (i = pos; i < pc->sz - 1; i++)为什么要sz-1?
原因:
pc->sz
表示当前通讯录中联系人的数量
,因此最后一个联系人的索引
是 pc->sz - 1(也就是下标)如果不减一的话就会造成越界访问
7.查找指定联系人
声明:
void SearchContact(Contact* pc);
实现:
void SearchContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };//MAX_NAME 是一个常量,表示名字的最大长度并将数组初始化为全 0
printf("请输入要查找人的名字:>");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
//FindByName 函数会返回该联系人在 data 数组中的下标,如果未找到则返回 -1。将返回值存储在 pos 变量中
if (pos == -1)
{
printf("要查找的人不存在\n");
return;//找不到就返回
}
//2.打印
else
{
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
//找到了就打印
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
上述代码的整体基本逻辑如下:
-
输入名字:
提示用户输入要查找的联系人名字,并将其存储到name
数组中。 -
查找联系人:
调用FindByName
函数,根据名字在通讯录中查找联系人。
如果未找到,输出提示信息并结束函数。 -
打印联系人信息:
如果找到联系人,格式化输出其详细信息(名字、年龄、性别、电话、地址)。
8.修改指定联系人
声明:
void ModifyContact(Contact* pc);
实现:
void ModifyContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要修改人的名字:>");
scanf("%s", name);
//1.查找
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要修改的人不存在\n");
return;//找不到就返回
}
printf("要修改人的信息已查找到,接下来请开始修改\n");
//2.修改
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");
}
基本逻辑
-
输入名字:
提示用户输入要修改的联系人名字,并将其存储到 name 数组中。 -
查找联系人:
调用 FindByName 函数,根据名字在通讯录中查找联系人。
如果未找到,输出提示信息并结束函数。 -
修改联系人信息:
如果找到联系人,提示用户依次输入新的名字、年龄、性别、电话和地址。
将用户输入的信息更新到通讯录中。 -
提示修改成功:
输出提示信息,表示联系人信息已成功修改。
9.排序通讯录的信息(可以按照名字排序也可以按照年龄来排序)
这里举的例子是按照名字来排序
int cmp_peo_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
//将 void* 指针转换为 PeoInfo* 类型,然后访问 name 字段
}
//按照名字来排序
void SortContact(Contact* pc)//Contact* pc: 指向通讯录结构体的指针,包含联系人数据
{
assert(pc);
qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_peo_by_name);
//pc->data: 指向联系人数组的起始地址。
//pc->sz: 联系人数组中元素的数量。
//sizeof(PeoInfo): 每个联系人结构体的大小。
//cmp_peo_by_name: 比较函数,用于定义排序规则。
printf("排序成功\n");
}
补充知识
1.qsort 函数
qsort 是 C 标准库(<stdlib.h>)中提供的通用排序函数,用于对数组进行排序。
- 函数原型:
void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
-
参数:
-
- base: 指向数组的起始地址。
-
- nmemb: 数组中元素的数量。
-
- size: 每个元素的大小(以字节为单位)。
-
- compar: 比较函数,用于定义排序规则。
-
比较函数:
比较函数接收两个 const void* 类型的参数,返回一个整数: -
- 负数:第一个参数小于第二个参数。
-
- 0:两个参数相等。
-
- 正数:第一个参数大于第二个参数。
strcmp 函数
strcmp 是 C 标准库(<string.h>)中提供的字符串比较函数,用于比较两个字符串的字典序。
- 函数原型:
int strcmp(const char* str1, const char* str2);
-
参数:
-
- str1: 第一个字符串。
-
- str2: 第二个字符串。
-
返回值:
-
- 负数:str1 小于 str2。
-
- 0:str1 等于 str2。
-
- 正数:str1 大于 str2。
-
比较规则:
-
- 按字符的 ASCII 值逐个比较。
-
- 比较过程在遇到第一个不相等的字符或字符串结束符 \0 时停止。
10.contact.h的整体声明如下:
#pragma once
#include<string.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#define MAX 100
# define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
//类型的声明
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];//存放添加进来的人的信息,假设开辟100个空间
int sz;//记录当前通讯录中有效信息的个数
}Contact;
//初始化通讯录
void InitContact(Contact* pc);//前面取的是con的地址 所以用pc指针接收
//增加联系人
void AddContact(Contact* pc);
//打印联系人信息
void ShowContact(const Contact* pc);
//删除联系人的信息
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录的信息
void SortContact(Contact* pc);
最后大体的整合一下就完成啦!
注意:
本章只是对通讯录的静态表示方式噢~