目录
前言
无论是写三子棋,还是扫雷,亦或是现在的通讯录,无非就是想好要实现的功能,用基本的语法去实现,并没有涉及难点。唯一令人发难的是整个程序的框架和要实现的功能,厘清了这些,你就成功了一半。
文章没有细讲各个函数的实现细节,需要看代码多体会。
现在,我们就开始吧。
通讯录功能简介
首先一个通讯录,我们要有联系人,并且能实现
- 增加联系人的信息
- 删除联系人的信息
- 修改联系人的信息
- 查找联系人
- 通过姓名来排序
- 退出时将通讯录人的信息存放到文件中,并在每次打开通讯录程序的时候自动读取文件已有的数据
所以要实现的功能就明确了,下一步就要梳理一下程序的逻辑和框架。
通讯录实现步骤 - 函数设计
创建联系人
首先需要创建通讯录,那么就要有联系人,联系人的信息包括
- 姓名
- 性别
- 年龄
- 联系方式
- 地址
#define NameSize 10
#define GenderSize 7
#define TeleSize 13
#define AddrSize 30
typedef struct PeoInfo
{
//姓名
char name[NameSize];
//性别
char gender[GenderSize];
//年龄
int age;
//联系电话
char tele[TeleSize];
//地址
char addr[AddrSize];
}PeoInfo;
创建通讯录
有了联系人,我们就可以创建通讯录。而为了实现动态版本的通讯录,那么通讯录要能同时存放当前通讯录中联系人的数量和能容纳联系人的最大数量。所以一个通讯录要包含三个信息:
- 联系人
- 当前联系人的数量
- 最大容纳量
typedef struct Contact
{
//指向存放联系人的一块空间的首地址
PeoInfo* PeoInfo;
//记录当前通讯录中联系人的数量
int sz;
//记录当前通讯录的最大容量
int capacity;
}Contact;
//创建通讯录Con
Contact Con;
判断通讯录是否需要扩容
我们创建好了通讯录,需要对它初始化,但这里就有一个问题。
初始化的时候我需要先给通讯录分配一块空间,然后从文件中读取初始数据,如果在读取的过程中通讯录满了,那就需要扩容,所以我们要先封装一个函数用来判断通讯录是否需要扩容:
//初始联系人的数量
#define InitSize 3
//每次扩容的数量
#define AddSize 2
//函数声明 - 检查是否需要增容
void IsFull(Contact* pc);
void IsFull(Contact* pc)
{
if (pc->capacity == pc->sz)
{
PeoInfo* tmp = ((PeoInfo*)realloc(pc->PeoInfo, sizeof(PeoInfo) * (pc->capacity + AddSize)));
if (tmp == NULL)
{
perror("IsFull");
return;
}
pc->PeoInfo = tmp;
pc->capacity += AddSize;
printf("增容成功\n");
}
}
从文件中读取数据
初始化的时候需要从文件中读取数据,所以我们封装一个 ReadData 函数来帮我们完成这部分操作。在读取数据的过程中就会涉及通讯录需不需要扩容的问题,所以上面封装的判断扩容函数就派上用场了:
//函数声明 - 从文件中读取数据
void ReadData(Contact* pc);
void ReadData(Contact* pc)
{
//打开文件
FILE* pf = fopen("AddressBook.txt", "r");
if (pf == NULL)
{
perror("ReadData");
return;
}
//读取文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//判断是否需要增容
IsFull(pc);
pc->PeoInfo[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
printf("数据已成功读取\n");
}
初始化通讯录
上面的准备工作完成,我们就可以对通讯录进行初始化了。
初始化要做四个工作:
- 分配一块空间存放联系人信息,空间初始大小为
sizeof(PeoInfo) * InitSize
,
这块空间交给指针PeoInfo
维护; - 将
capacity
置为InitSize
; - 将
sz
置为0
; - 从文件中读取数据,直接用上面封装的函数完成;
//函数声明 - 初始化通讯录
void InitContact(Contact* pc);
void InitContact(Contact* pc)
{
pc->capacity = InitSize;
pc->sz = 0;
pc->PeoInfo = (PeoInfo*)malloc(sizeof(PeoInfo) * InitSize);
if (pc->PeoInfo == NULL)
{
perror("InitContact");
return;
}
//从文件中读取数据
ReadData(pc);
}
打印通讯录
通讯录创建并初始化好之后你得能让我看到啊,所以先封装一个打印函数 Print ,每个联系人信息的打印通过 for 循环实现,循环次数就是当前通讯录中联系人的数量 sz :
//函数声明 - 打印通讯录
void Print(Contact* pc);
void Print(Contact* pc)
{
//打印标题
printf("%-6s\t%-6s\t%-6s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "联系方式", "地址");
//打印联系人信息
for (int i = 0; i < pc->sz; i++)
{
printf("%-6s\t%-6s\t%-6d\t%-12s\t%-20s\n",
pc->PeoInfo[i].name,
pc->PeoInfo[i].gender,
pc->PeoInfo[i].age,
pc->PeoInfo[i].tele,
pc->PeoInfo[i].addr);
}
}
效果如下:
增加联系人
增加联系人无非就是输入联系人的各种信息,但是有一个细节,就是在每次增加之前都需要判断一下存放联系人的那块空间是否满了,这又用到了上面封装的 IsFull 函数:
//函数声明 - 增加联系人
void Add(Contact* pc);
void Add(Contact* pc)
{
//检查是否需要扩容
IsFull(pc);
//输入联系人信息
printf("请输入名字:>");
scanf("%s", pc->PeoInfo[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->PeoInfo[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->PeoInfo[pc->sz].gender);
printf("请输入电话:>");
scanf("%s", pc->PeoInfo[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->PeoInfo[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
通过姓名查找联系人的位置
下一步我们要实现的是删除联系人,但我要删除哪个联系人呢?
这就需要设计一个函数 SearchByName 能通过联系人的姓名找出联系人的位置并返回。
如果找到了就返回联系人的位置,找不到就返回 -1 :
//函数声明 - 通过姓名查找联系人的位置
int SearchByName(Contact* pc, char* name);
int SearchByName(Contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
if (strcmp(pc->PeoInfo[i].name, name) == 0)
return i;
return -1;
}
删除联系人
操作者输入一个联系人的姓名,然后查找,找不到就提示无需删除,找到了则将联系人信息从后往前覆盖,以做到删除的效果:
//函数声明 - 删除联系人
void Delete(Contact* pc);
void Delete(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要删除的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要删除的联系人不存在\n");
return;
}
for (int i = pos; i < pc->sz - 1; i++)
pc->PeoInfo[i] = pc->PeoInfo[i + 1];
pc->sz--;
printf("删除成功\n");
}
查找联系人
通过用户输入的联系人的姓名查找联系人的信息并打印,这又用到了上面封装的函数 SearchByName :
//函数声明 - 查找联系人
void Search(Contact* pc);
//查找联系人
void Search(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无法查找\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要查找的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要删除的联系人不存在\n");
return;
}
//打印联系人的信息
printf("%-6s\t%-6s\t%-6s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "联系方式", "地址");
printf("%-6s\t%-6s\t%-6d\t%-12s\t%-20s\n",
pc->PeoInfo[pos].name,
pc->PeoInfo[pos].gender,
pc->PeoInfo[pos].age,
pc->PeoInfo[pos].tele,
pc->PeoInfo[pos].addr);
}
修改联系人信息
修改的思路和删除的思路一样,都是先通过姓名找到联系人的位置,只不过这次要重新输入联系人信息:
//函数声明 - 修改联系人的信息
void Modify(Contact* pc);
void Modify(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无法修改\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要修改的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要修改的联系人不存在\n");
return;
}
//输入联系人信息
printf("请输入新信息:\n");
printf("请输入名字:>");
scanf("%s", pc->PeoInfo[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->PeoInfo[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->PeoInfo[pc->sz].gender);
printf("请输入电话:>");
scanf("%s", pc->PeoInfo[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->PeoInfo[pc->sz].addr);
printf("修改成功\n");
}
按照姓名首字母进行排序
这里的排序本质上是结构体排序,通过结构体(联系人信息)的一个成员(姓名)的大小关系来进行排序。
这里用选择排序完成,其中姓名大小要用 strcmp 函数判断:
//函数声明 - 给通讯录排序
void Sort(Contact* pc);
void Sort(Contact* pc)
{
for(int i=0; i<pc->sz-1; i++)
for (int j = i + 1; j < pc->sz; j++)
{
PeoInfo tmp = { 0 };
if (strcmp(pc->PeoInfo[i].name, pc->PeoInfo[j].name) > 0)
{
tmp = pc->PeoInfo[i];
pc->PeoInfo[i] = pc->PeoInfo[j];
pc->PeoInfo[j] = tmp;
}
}
printf("排序成功\n");
}
将数据存放到文件中
我们要求每次退出通讯录时要把输入的联系人信息存放到文件中,以便下次打开程序时此前数据能得以保留,所以我们还需要封装一个函数 StoreData 来实现这部分功能 :
//函数声明 - 将数据存放到文件中
void StoreData(Contact* pc);
void StoreData(Contact* pc)
{
//打开文件
FILE* pf = fopen("AddressBook.txt", "w");
if (pf == NULL)
{
perror("StoreData");
return;
}
//存放数据
for (int i = 0; i < pc->sz; i++)
fwrite(pc->PeoInfo + i, sizeof(PeoInfo), 1, pf);
printf("数据存储成功\n");
//关闭文件
fclose(pf);
pf = NULL;
}
主体框架 - main函数
现在我们已经设计好了各个函数,下面就实现主函数。
在主函数中我们要打印菜单,提示操作者要进行哪一步操作,这里用一个 menu 函数实现打印:
void menu()
{
printf("**********************\n");
printf("**** 0.Exit ****\n");
printf("**** 1.Add ****\n");
printf("**** 2.Delete ****\n");
printf("**** 3.Search ****\n");
printf("**** 4.Modify ****\n");
printf("**** 5.Sort ****\n");
printf("**** 6.Print ****\n");
printf("**********************\n");
}
用一个整型变量 input 接收用户的输入值,用 switch 语句来判断,其中 case 的常量表达式用枚举类型来代替单薄的数字,以提高代码的可读性:
enum Option
{
EXIT, //0
ADD, //1
DELETE,//2
SEARCH,//3
MODIFY,//4
SORT, //5
PRINT //6
};
此外,还有一个很重要的点就是当退出程序时,要及时释放掉此前存放联系人时分配的空间,这一步要在存放完数据之后进行。
这样 main 函数就出来了:
int main()
{
//创建通讯录
Contact Con;
//初始化通讯录
InitContact(&Con);
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
StoreData(&Con);
free(Con.PeoInfo);
Con.sz = 0;
Con.capacity = 0;
printf("数据销毁成功\n");
printf("退出成功\n");
break;
case ADD:
Add(&Con);
break;
case DELETE:
Delete(&Con);
break;
case SEARCH:
Search(&Con);
break;
case MODIFY:
Modify(&Con);
break;
case SORT:
Sort(&Con);
break;
case PRINT:
Print(&Con);
break;
default:
printf("输入错误,请重新输入:\n");
break;
}
} while (input);
return 0;
}
完整代码展示
contact.h
把函数和符号的声明、头文件引用、结构体、函数和符号的声明,放在头文件 contact.h 中:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#define InitSize 3
#define AddSize 2
#define NameSize 10
#define GenderSize 7
#define TeleSize 13
#define AddrSize 30
typedef struct PeoInfo
{
char name[NameSize];
char gender[GenderSize];
int age;
char tele[TeleSize];
char addr[AddrSize];
}PeoInfo;
typedef struct Contact
{
PeoInfo* PeoInfo; //存放联系人
int sz; //记录当前通讯录中联系人的数量
int capacity; //记录当前通讯录的最大容量
}Contact;
//检查是否需要增容
void IsFull(Contact* pc);
//从文件中读取数据
void ReadData(Contact* pc);
//初始化通讯录
void InitContact(Contact* pc);
//存放数据
void StoreData(Contact* pc);
//销毁数据
void DestroyData(Contact* pc);
//增加联系人
void Add(Contact* pc);
//通过姓名查找联系人的位置
int SearchByName(Contact* pc, char* name);
//删除联系人
void Delete(Contact* pc);
//查找联系人
void Search(Contact* pc);
//修改联系人的信息
void Modify(Contact* pc);
//给通讯录排序
void Sort(Contact* pc);
//打印通讯录
void Print(Contact* pc);
contact.c
把各个函数的实现放在源文件 contact.c 中:
#include "contact.h"
//检查是否需要扩容
void IsFull(Contact* pc)
{
if (pc->capacity == pc->sz)
{
PeoInfo* tmp = ((PeoInfo*)realloc(pc->PeoInfo, sizeof(PeoInfo) * (pc->capacity + AddSize)));
if (tmp == NULL)
{
perror("IsFull");
return;
}
pc->PeoInfo = tmp;
pc->capacity += AddSize;
printf("增容成功\n");
}
}
//从文件中读取数据
void ReadData(Contact* pc)
{
//打开文件
FILE* pf = fopen("AddressBook.txt", "r");
if (pf == NULL)
{
perror("ReadData");
return;
}
//读取文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//判断是否需要增容
IsFull(pc);
pc->PeoInfo[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
printf("数据已成功读取\n");
}
//初始化通讯录
void InitContact(Contact* pc)
{
pc->capacity = InitSize;
pc->sz = 0;
pc->PeoInfo = (PeoInfo*)malloc(sizeof(PeoInfo) * InitSize);
if (pc->PeoInfo == NULL)
{
perror("InitContact");
return;
}
//从文件中读取数据
ReadData(pc);
}
//将数据存放到文件中
void StoreData(Contact* pc)
{
//打开文件
FILE* pf = fopen("AddressBook.txt", "w");
if (pf == NULL)
{
perror("StoreData");
return;
}
//存放数据
for (int i = 0; i < pc->sz; i++)
fwrite(pc->PeoInfo + i, sizeof(PeoInfo), 1, pf);
printf("数据存储成功\n");
//关闭文件
fclose(pf);
pf = NULL;
}
//销毁数据
void DestroyData(Contact* pc)
{
free(pc->PeoInfo);
pc->sz = 0;
pc->capacity = 0;
printf("数据销毁成功\n");
}
//增加联系人
void Add(Contact* pc)
{
//检查是否需要扩容
IsFull(pc);
//输入联系人信息
printf("请输入名字:>");
scanf("%s", pc->PeoInfo[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->PeoInfo[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->PeoInfo[pc->sz].gender);
printf("请输入电话:>");
scanf("%s", pc->PeoInfo[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->PeoInfo[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
//通过姓名查找联系人的位置
int SearchByName(Contact* pc, char* name)
{
for (int i = 0; i < pc->sz; i++)
if (strcmp(pc->PeoInfo[i].name, name) == 0)
return i;
return -1;
}
//删除联系人
void Delete(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要删除的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要删除的联系人不存在\n");
return;
}
for (int i = pos; i < pc->sz - 1; i++)
pc->PeoInfo[i] = pc->PeoInfo[i + 1];
pc->sz--;
printf("删除成功\n");
}
//查找联系人
void Search(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无法查找\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要查找的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要删除的联系人不存在\n");
return;
}
//打印联系人的信息
printf("%-6s\t%-6s\t%-6s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "联系方式", "地址");
printf("%-6s\t%-6s\t%-6d\t%-12s\t%-20s\n",
pc->PeoInfo[pos].name,
pc->PeoInfo[pos].gender,
pc->PeoInfo[pos].age,
pc->PeoInfo[pos].tele,
pc->PeoInfo[pos].addr);
}
//修改联系人的信息
void Modify(Contact* pc)
{
//找出联系人的位置
if (pc->sz == 0)
{
printf("通讯录为空,无法修改\n");
return;
}
char name[NameSize] = { 0 };
printf("请输入要修改的联系人:\n");
scanf("%s", name);
int pos = SearchByName(pc, name);
if (pos == -1)
{
printf("要修改的联系人不存在\n");
return;
}
//输入联系人信息
printf("请输入新信息:\n");
printf("请输入名字:>");
scanf("%s", pc->PeoInfo[pc->sz].name);
printf("请输入年龄:>");
scanf("%d", &(pc->PeoInfo[pc->sz].age));
printf("请输入性别:>");
scanf("%s", pc->PeoInfo[pc->sz].gender);
printf("请输入电话:>");
scanf("%s", pc->PeoInfo[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->PeoInfo[pc->sz].addr);
printf("修改成功\n");
}
//给通讯录排序
void Sort(Contact* pc)
{
for(int i=0; i<pc->sz-1; i++)
for (int j = i + 1; j < pc->sz; j++)
{
PeoInfo tmp = { 0 };
if (strcmp(pc->PeoInfo[i].name, pc->PeoInfo[j].name) > 0)
{
tmp = pc->PeoInfo[i];
pc->PeoInfo[i] = pc->PeoInfo[j];
pc->PeoInfo[j] = tmp;
}
}
printf("排序成功\n");
}
//打印通讯录
void Print(Contact* pc)
{
//打印标题
printf("%-6s\t%-6s\t%-6s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "联系方式", "地址");
//打印联系人信息
for (int i = 0; i < pc->sz; i++)
{
printf("%-6s\t%-6s\t%-6d\t%-12s\t%-20s\n",
pc->PeoInfo[i].name,
pc->PeoInfo[i].gender,
pc->PeoInfo[i].age,
pc->PeoInfo[i].tele,
pc->PeoInfo[i].addr);
}
}
test.c
将主体框架放在源文件 test.c 中:
#include "contact.h"
void menu()
{
printf("**********************\n");
printf("**** 0.Exit ****\n");
printf("**** 1.Add ****\n");
printf("**** 2.Delete ****\n");
printf("**** 3.Search ****\n");
printf("**** 4.Modify ****\n");
printf("**** 5.Sort ****\n");
printf("**** 6.Print ****\n");
printf("**********************\n");
}
enum Option
{
EXIT,
ADD,
DELETE,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main()
{
//创建通讯录
Contact Con;
//初始化通讯录
InitContact(&Con);
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case EXIT:
StoreData(&Con);
free(Con.PeoInfo);
Con.sz = 0;
Con.capacity = 0;
printf("数据销毁成功\n");
printf("退出成功\n");
break;
case ADD:
Add(&Con);
break;
case DELETE:
Delete(&Con);
break;
case SEARCH:
Search(&Con);
break;
case MODIFY:
Modify(&Con);
break;
case SORT:
Sort(&Con);
break;
case PRINT:
Print(&Con);
break;
default:
printf("输入错误,请重新输入:\n");
break;
}
} while (input);
return 0;
}