目录
注
要求
思路
前期的准备
1.设置类型存储通讯录内的数据
2.规划流程
实现
创建通讯录
初始化通讯录部分
用户选择部分
ADDContact函数部分(增加联系人)
PRINTContact函数部分(打印联系人信息)
FindByName函数部分(通过名字查找对象)
DELContact函数部分(删除联系人的信息)
SEARCHContact函数部分(查找指定联系人)
MODIFYContact函数部分(修改指定联系人)
SORTContact函数部分(排序联系人信息)
优化
1.如果出现重名的联系人该怎么办?
2.把数据移动到堆上
源代码
test.c
contact.h
contact.c
注
本笔记参考B站up鹏哥C语言
要求
1. 通讯录能够存放1000个人的信息
每个人的信息:
名字+年龄+性别+电话+地址
2. 增加人的信息
3. 删除指定人的信息
4. 修改指定人的信息
5. 查找指定人的信息
6. 排序通讯录的信息
思路
1. 通讯录能够存放1000个人的信息
每个人的信息:
名字+年龄+性别+电话+地址
2. 增加人的信息
3. 删除指定人的信息
4. 修改指定人的信息
5. 查找指定人的信息
6. 排序通讯录的信息
前期的准备
1.设置类型存储通讯录内的数据
在 contact.h 内部(此处代码仅供参考)
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
//类型的定义
typedef struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
int age;
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
通过定义常量的方式方便后期的调试。
typedef 定义函数,方便之后使用。
2.规划流程
实现
创建通讯录
需要:
- 创建通讯录本体
- 需要知道通讯录中当前总共有几个元素(设置一个变量sz负责计数)
PeoInfo data[MAX]; int sz = 0;
但是在封装函数时会发现,如果使用sz计数,在传参时每次都要传入两个参数。这时候就会思考是否可以合并 PeoInfo 和 sz 这两个部分?
所以再封装一个结构体(MAX已提前定义):
//通讯录
typedef struct Contact
{
PeoInfo data[MAX];//存储添加进来的人的信息
int sz;//记录当前通讯录中有效信息的个数
}Contact;
此时也改变 test.c 内的结构体
Contact内部
初始化通讯录部分
认为,刚刚创建好的通讯录内部应该没有数据,所以通讯录内部的 结构体PeoInfo 和 整型sz 应该被初始化。
首先在 test.c 内部写入函数 InitContact
InitContact(&con); //选择传入 con 的地址,可以改动 con 内部的内容
再在 contact.h 内部声明函数
void InitContact(Contact* pc);
最后在 contact.c 内部首先该函数(需要包含头文件 contact.h )
memset() - 内存设置函数,可以设置一块连续的内存。把指定空间的每个字节的内容初始化成指定的值(以字节为单位)。
void InitContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
//这种写法适用于较为复杂的初始化。
为什么可以直接使用 sizeof 计算字节个数?
分析:指针 pc->data 找到的是 data 这个数组的数组名,相当于将数组名单独放入 sizeof 内部,此时计算的是data这个数组的总大小。
用户选择部分
通过枚举类型的使用方便分支语句的选择,逻辑清晰
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRI
};
可对应switch语句(用户选择)
switch (input)
{
case ADD://增加信息
break;
case DEL://删除信息
break;
case SEARCH://查找信息
break;
case MODIFY://修改信息
break;
case SORT://排序
break;
case PRINT://打印信息
break;
case EXIT://退出
break;
default:
break;
}
ADDContact函数部分(增加联系人)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
分析:
- 当通讯录内没有信息时, sz = 0 ,此时把联系人的信息放入 data[0] 内部;
- 如果有一个联系人,则 sz = 1 ,此时把联系人的信息放入 data[1] 内部;
- ……
由此可以发现新增加的联系人的信息应该被放入 data[sz] 的内部(放入后 sz++)。
另一方面,当通讯录满了之后就不可以放入数据了。
void ADDContact(Contact* pc)
{
//处理通讯录满了的情况
if (pc->sz == MAX)
{
printf("通讯录已满,无法添加n");
return;
}
//增加一个人的信息
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");
}
PRINTContact函数部分(打印联系人信息)
首先在 test.c 内部写入函数 PRINTCotact
PRINTContact(&con);//传入地址
这里选择传入地址,不是为了更改 con 内部的内容,而是因为结构体传地址效率会更高。
声明函数(contact.h)
void PRINTContact(const Contact* pc);
因为并改变 con 内容,所以使用 const 修饰变量。
实现函数(contact.c)
要求
- 先打印标题
//先设计好要打印的标题格式(左对齐,长度不够的部分会用空格替代) printf("%-20st%-5st%-5st%-12st%-20sn", "名字", "年龄", "性别", "电话", "地址");- 再打印数据
void PRINTContact(const Contact* pc)
{
int i = 0;
//打印标题
printf("%-20st%-5st%-5st%-12st%-20sn", "名字", "年龄", "性别", "电话", "地址");
//打印数据
for ( i = 0; i < pc->sz; i++)
{
printf("%-20st%-5dt%-5st%-12st%-20sn",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
FindByName函数部分(通过名字查找对象)
DELContact函数、SEARCHContact函数 和 MODIFYContact函数 都需要查找对象,所以统一使用函数FindByName进行查找。
需要:
- 如果找到 - 返回查找到的对象的下标;
- 如果没找到 - 返回 -1 。
该函数在 contact.c 内部被使用
int pos = FindByName(pc, name);//传入通讯录和要查找的对象的名字
所以也在 contact.c 内部创建:
static int FindByName(Contact* pc, char name[])//此处name是字符串
{
int i = 0;
for ( i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)//找到了
return i;
}
return -1;//找不到
}
依旧是遍历通过通讯录的方式找到目标。同时,因为其他源文件内不需要使用该函数,所以加上 static 保护函数。
DELContact函数部分(删除联系人的信息)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
分析:
- 通讯录内部的信息为空时,不用删除。
- 要删除对象,首先对象需要存在,所以先要查找。接下来:
如果对象不存在,不用删除;
如果对象存在,执行删除操作。
要删除对象,只需要把要删除的对象后面的所有对象往前移动一个元素就可以了。如:
void DELContact(Contact* pc)
{
char name[MAX_NAME];
//检测通讯录是否为空
if (pc->sz == 0)
{
printf("通讯录为空,无需删除n");
return;
}
printf("请输入要删除联系人的名字:");
scanf("%s", name);
//1.查找删除的对象
//判断:对象是否存在
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的联系人不存在n");
return;
}
//2.对象存在,执行删除操作
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功n");
}
为什么此处的 for (i = pos; i < pc->sz - 1; i++) 内部是 sz - 1 呢?
但是当 i + 1 取到5时,访问的是这个数组的第6个元素,删除对象后元素都往前移动一个元素,本就无法访问下标为 i + 1 的那个元素。
SEARCHContact函数部分(查找指定联系人)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
大部分代码和前面有重复,这里直接上代码
void SEARCHContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要查找的联系人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在n");
return;
}
else
{
printf("%-20st%-5dt%-5st%-12st%-20sn",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
//打印部分和之前实现的PRINTContact相类似,但是没有循环
}
}
MODIFYContact函数部分(修改指定联系人)
修改信息需要传输地址,在 test.c 中
MODIFYContact(&con);//传入地址
声明函数 contact.h
void MODIFYContact(Contact* pc);//与之前的函数类似
实现函数 contact.c
void MODIFYContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人的名字:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在n");
return;
}
else
{
printf("请输入名字:");
scanf("%s", pc->data[pos].name);//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函数部分(排序联系人信息)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)
复习 strcmp函数 的返回值
int strcmp ( const char * str1, const char * str2 );
- 当 str1 指向的字符串的对应字符的ASCII值 < str2 指向的字符串的时,返回一个小于0的值;
- 当 str1 指向的字符串和 str2 指向的字符串一致时,返回 0 ;
- 当 str1 指向的字符串的对应字符的ASCII值 > str2 指向的字符串的时,返回一个大于0的值。
首先是检测通讯录是否为空。
其次,通过冒泡排序把姓名下ASCII值较小的联系人排到前面。
void SORTContact(Contact* pc)
{
if (pc->data[0].name == 0)
{
printf("通讯录为空,无法排序n");
return;
}
int i = 0;
int j = 0;
for (i = 0; i < pc->sz; i++)
{
for (j = 0; j < pc->sz - 1; j++)
{
PeoInfo tmp[] = { 0 };
if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
{
tmp[0] = pc->data[j];
pc->data[j] = pc->data[j + 1];
pc->data[j + 1] = tmp[0];
}
}
}
printf("排序成功n");
}
优化
1.如果出现重名的联系人该怎么办?
一种解决方法:
多列出一列行号,通过行号来区分不同的联系人。会需要处理重名的联系人的函数只有DELContact函数和MODIFYContact函数,这里只举例DELContact函数。
- 如果需要删除多个联系人,就需要循环了,因为进入时就需要执行一次,所以使用了do循环。
- 按照上面的想法,就需要多打印一行行号,这时会发现需求的功能和SEARCHContact函数类似,所以直接改造SEARCHContact函数。
- 因为之前定义的变量pos刚好可以指向行号,所以使用该变量书写行号。
这就是第一个问题,如何改变变量pos?
pos = FindByName(pc, name, pos); //…… pos++;
这里从函数FindByName入手,因为这样可以筛出每一个同名的联系人。
FindByName函数只改变了循环条件
for (; i < pc->sz; i++)
接下来就是在SEARCHContact函数内部增加一行打印行号即可。
- 而DELContact函数需要的就是按照用户输入的行号来删除联系人信息。
这里还有一个问题:如果用户只要求删除一个联系人,这种情况要怎么办?
上代码:
do { //…… pos = FindByName(pc, name, 0); if (pos == -1)//检测是否还有要求的联系人信息存在 { printf("修改完毕n"); return; } printf("是否继续修改(Y/N):"); scanf("%c", &ch); getchar(); printf("退出成功n"); } while (ch != 'N');代码分为两部分:
1.检测联系人信息是否依然存在;
2.询问客户是否依旧修改。
2.把数据移动到堆上
为什么要把数据移动到堆上呢?
- 因为可能一次性开辟了过大的空间,这些空间可能无法被用完:一开始用户可能只有10个联系人,之后才会慢慢增加,可能最后增加到100个联系人就是极限了。这种情况下空间就被浪费了。
- 而另一边,可能1000个位置不够用,这怎么办?
这就需要动态内存开辟:当需要的时候再增加空间,使得使用更加高效。
源代码
test.c
#define _CRT_SECURE_NO_WARNINGS
//1. 通讯录能够存放1000个人的信息
// 每个人的信息:
// 名字 + 年龄 + 性别 + 电话 + 地址
//2. 增加人的信息
//3. 删除指定人的信息
//4. 修改指定人的信息
//5. 查找指定人的信息
//6. 排序通讯录的信息
#include
#include"contact.h"
void menu()
{
printf("************************************n");
printf("***** 1. add 2. del *****n");
printf("***** 3. search 4. modify *****n");
printf("***** 5. sort 6. print *****n");
printf("***** 0. exit *****n");
printf("************************************n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main()
{
int input = 0;
char name[MAX_NAME] = { 0 };
//创建通讯录
Contact con;
//初始化通讯录的函数
InitContact(&con);//传入 con 的地址
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case ADD://增加信息
ADDContact(&con);//传入地址
break;
case DEL://删除信息
DELContact(&con);
break;
case SEARCH://查找信息
printf("请输入要查找的联系人的名字:");
scanf("%s", name);
SEARCHContact(&con, name);
break;
case MODIFY://修改信息
printf("请输入要修改的联系人的名字:");
scanf("%s", name);
MODIFYContact(&con, name);
break;
case SORT://排序
SORTContact(&con);
break;
case PRINT://打印信息
PRINTContact(&con);
break;
case EXIT://退出
printf("退出通讯录n");
break;
default:
printf("选择错误,请重新选择n");
break;
}
} while (input);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS //1. 通讯录能够存放1000个人的信息 // 每个人的信息: // 名字 + 年龄 + 性别 + 电话 + 地址 //2. 增加人的信息 //3. 删除指定人的信息 //4. 修改指定人的信息 //5. 查找指定人的信息 //6. 排序通讯录的信息 #include#include"contact.h" void menu() { printf("************************************n"); printf("***** 1. add 2. del *****n"); printf("***** 3. search 4. modify *****n"); printf("***** 5. sort 6. print *****n"); printf("***** 0. exit *****n"); printf("************************************n"); } enum Option { EXIT, ADD, DEL, SEARCH, MODIFY, SORT, PRINT }; int main() { int input = 0; char name[MAX_NAME] = { 0 }; //创建通讯录 Contact con; //初始化通讯录的函数 InitContact(&con);//传入 con 的地址 do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case ADD://增加信息 ADDContact(&con);//传入地址 break; case DEL://删除信息 DELContact(&con); break; case SEARCH://查找信息 printf("请输入要查找的联系人的名字:"); scanf("%s", name); SEARCHContact(&con, name); break; case MODIFY://修改信息 printf("请输入要修改的联系人的名字:"); scanf("%s", name); MODIFYContact(&con, name); break; case SORT://排序 SORTContact(&con); break; case PRINT://打印信息 PRINTContact(&con); break; case EXIT://退出 printf("退出通讯录n"); break; default: printf("选择错误,请重新选择n"); break; } } while (input); return 0; }



