建立文件
新建三个文件:
contact.h:函数声明
contact.c:函数实现
main.c:函数调用
通讯录设定(非链表)
通讯录:
1、通讯录中能够存放n个人的信息,每个人的信息:名字 + 年龄 + 性别 + 电话 + 地址
2、能够对通讯录中人的信息进行增删查改
3、初始化时,动态开辟一块空间,当空间不够用时,再进行扩容
4、当通讯录初始化的时候,加载文件的信息到通讯录;当通讯录退出的时候,把信息写入文件
定义一个结构体,将人的信息放进去
contact.h中写入:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.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;
当我们对通讯录的内容进行增删查改时,仅知道人的信息是不够的,还需要知道当前通讯录中存放的人的信息的个数以及通讯录的最大容量
这里我们再定义一个结构体,里面存放开辟空间的起始位置、有效信息的个数和最大容量
contact.h中写入:
typedef struct Contact
{
PeoInfo* data;//结构体指针,指向动态开辟空间的起始位置
int sz;//记录当前通讯录中有效信息的个数
int capacity;//记录当前通讯录的最大容量
}Contact;
链表是通过末结点的空指针防止越界,此例中是通过控制sz
防止越界(当访问到 sz-1 之后的时候就停止访问,作用类似数组下标)
函数调用的实现
主函数依旧使用do-while、switch语句,实现重复使用和选项操作
流程:
进入程序 --> 初始化通讯录(第一个函数) --> 进入循环 --> 打印菜单(第二个函数) --> 用户进行选择 --> 进入switch -->
case 1:增加联系人信息(第三个函数)
case 2:删除联系人信息(第四个函数)
case 3:查找联系人信息(第五个函数)
case 4:修改联系人信息(第六个函数)
case 5:打印通讯录内容(第七个函数)
case 0:(退出程序)保存通讯录内容(第八个函数),销毁通讯录-free释放空间(第九个函数)
进行函数声明(由于要对通讯录的内容进行修改,函数的形参设为结构体指针,实现传址调用)
contact.h中写入:
void InitContact(Contact* pc);//初始化通讯录
void menu();//菜单
void ADDContact(Contact* pc);//增
void DelContact(Contact* pc);//删
void SearchContact(Contact* pc);//查
void ModifyContact(Contact* pc);//改
void PrintContact(const Contact* pc);//打印(加const可防止通讯录内容被修改)
void SaveContact(Contact* pc);//保存通讯录信息到文件
void DestoryContact(Contact* pc);//销毁通讯录
完成主函数
main.c中写入:
#include "contact.h"
int main()
{
int input = 0;
Contact con;//创建通讯录
InitContact(&con);//初始化通讯录
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
ADDContact(&con);
break;
case 2:
DelContact(&con);
break;
case 3:
SearchContact(&con);
break;
case 4:
ModifyContact(&con);
break;
case 5:
PrintContact(&con);
break;
case 0:
SaveContact(&con);//保存信息到文件
DestoryContact(&con);//销毁通讯录
printf("bye bye!\n");
break;
default:
printf("Seleect error, Please Select Again!\n");
break;
}
} while (input);
return 0;
}
这里我们为了各个case更加直观,可以创建枚举常量
contact.h中写入:
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
PRINT
};
然后替换到case中:
int main()
{
int input = 0;
Contact con;//创建通讯录
InitContact(&con);//初始化通讯录
do
{
menu();
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 PRINT:
PrintContact(&con);
break;
case EXIT:
SaveContact(&con);//保存信息到文件
DestoryContact(&con);//销毁通讯录
printf("bye bye!\n");
break;
default:
printf("Seleect error, Please Select Again!\n");
break;
}
} while (input);
return 0;
}
主函数完成
InitContact 的实现
InitContact 的实现
工作:
1、在堆区上申请一块连续的空间,并将地址交给 data
2、sz = 0
3、capacity 初始化为当前的最大容量
4、加载文件信息到通讯录
设定初始开辟空间的大小
contact.h中写入:
#define DEAULT_SZ 3
完成初始化函数
contact.c中写入:
void InitContact(Contact* pc)
{
pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));//注1
//在堆区开辟空间,空间开辟成功,返回空间起始位置的指针
if (pc->data == NULL)//空间开辟失败,返回空指针
{
perror("InitContact");//报错
return;//结束
}
//空间开辟成功
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
LoadContact(pc);//加载文件信息到通讯录
}
注1:malloc 函数来自 <stdlib.h> and <malloc.h>
函数声明为:void* malloc(size_t size);
在堆区上开辟一块 size 字节大小的空间
data 的类型为PeoInfo*
,malloc 的返回类型为void*
,在传值给 data 之前先进行强制类型转换
使用 realloc 此类函数时,需要引用头文件
contact.h中写入:
#include <stdlib.h>
这里衍生出了一个函数 - LoadContact(衍生一)
LoadContact的实现
先进行声明
contact.h中写入:
void LoadContact(Contact* pc);
函数实现
contact.c 中写入:
void LoadContact(Contact* pc)
{
FILE* pf = fopen("contact.dat", "r");//注2
if (pf == NULL)
{
perror("LoadContact");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))//注3
{
CheckContact(pc);//检查是否需要扩容
pc->data[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
注2:fopen 来自 <stdio.h>
函数声明为:FILE* fopen(const char* filename, const char* mode);
有两个参数,第一个参数是文件路径,第二个参数是文件操作模式,fopen 会返回一个文件指针(可以通过文件指针对该文件进行读取和写入操作)
LoadContact 中的FILE* pf = fopen("contact.dat", "r")
表示以文件 contact.dat 为源进行读取,返回的文件指针由 pf 接收注3:fread 来自 <stdio.h>
函数声明为:size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
有四个参数,分别表示数据的存储位置、元素大小、元素数目、流(文件指针)
每次从pf中读取一个 sizeof(PeoInfo) 大小的数据到 tmp 中,数据读完后 fread 会返回0,循环终止
又衍生出了一个函数 - CheckContact(衍生二)
增加联系人之前,需要检查是否进行空间扩容(初始化时通讯录只能存储三个联系人,当增加第四个联系人时就需要扩容),定义函数CheckContact()
CheckContact的实现
工作:检查空间容量是否已满,如果是,则需进行扩容
思路:
1、使用 if 判断
2、使用 realloc 函数进行扩容
3、扩容成功后,更新 data 和 capacity 的值
函数声明
contact.h中写入:
void CheckContact(Contact* pc);
设定每次增容的大小
contact.h中写入:
#define INC_SZ 2
函数实现
contact.c中写入:
void CheckContact(Contact* pc)
{
if (pc->capacity == pc->sz)//容量已到最大,需要进行扩容
{
PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
if (ptr != NULL)//扩容成功
{
pc->data = ptr;
pc->capacity += INC_SZ;
printf("增容成功\n");
}
else//扩容失败
{
perror("ADDContact");
printf("增加联系人失败\n");
return;
}
}
//如果容量没有达到最大,不需要进行任何操作
}
menu的实现
contact.c中写入:
void menu()
{
printf("***************************************\n");
printf("********** 1.add 2.del **********\n");
printf("********** 3.search 4.modify **********\n");
printf("********** 5.print 0.exit **********\n");
printf("***************************************\n");
printf("Please Select:");
}
ADDContact的实现
使用 scanf 录入数据
contact.c中写入:
void ADDContact(Contact* pc)
{
//进行增容
CheckContact(pc);
//添加个人信息
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");
}
DelContact的实现
DelContact的实现
1、检查通讯录是否为空,若为空,就不需要删除
2、找到需要删除的联系人的位置,将其后面的数据都往前挪一下,直接覆盖掉
函数实现
contact.c中写入:
void DelContact(Contact* pc)
{
if (pc->sz == 0)
printf("通讯录为空,无需删除\n");
char name[MAX_NAME] = { 0 };
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--;//最后一个元素没法删除,但在此处会被抹掉;假如通讯录中有3个数据,sz-1后最后一个元素不会被访问;如果新增加一个元素,最后一个数据会被覆盖掉
printf("删除成功\n");
}
这里又衍生出了一个新的函数 - FindByName(衍生三)
FindByName的实现
工作:
1、按名查找联系人的位置,并返回其对应的序数
2、若不存在,返回-1
思路:遍历
函数声明
contact.h中写入:
int FindByName(Contact* pc, char name[]);
函数实现
contact.c中写入:
int FindByName(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;//找不到
}
使用 strcmp 需要引用头文件:
contact.h中写入:
#include <string.h>
SearchContact的实现
思路:
使用 FindByName 函数找到联系人,打印信息
函数实现
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("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");
int i = 0;
printf("%-20s\t%-5s\t%-5d\t%-12s\t%-20s\n",
pc->data[pos].name,
pc->data[pos].sex,
pc->data[pos].age,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
ModifyContact的实现
思路:
使用 FindByName 函数找到联系人,scanf 录入数据
函数实现
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);
printf("请输入性别:");
scanf("%s", pc->data[pos].sex);
printf("请输入年龄:");
scanf("%d", &pc->data[pos].age);
printf("请输入电话:");
scanf("%s", pc->data[pos].tele);
printf("请输入住址:");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
}
PrintContact的实现
printf 打印,注意格式对齐
函数实现
contact.c中写入:
void PrintContact(const Contact* pc)
{
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");
int i = 0;
for (i = 0; i < pc->sz; i++)
printf("%-20s\t%-5s\t%-5d\t%-12s\t%-20s\n",
pc->data[i].name,
pc->data[i].sex,
pc->data[i].age,
pc->data[i].tele,
pc->data[i].addr);
}
SaveContact的实现
函数实现
contact.c中写入:
void SaveContact(Contact* pc)
{
FILE* pf = fopen("contact.dat", "w");
if (pf == NULL)
{
perror("DestoryContact");
return;
}
//保存
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);//注3
}
//关闭
fclose(pf);
pf = NULL;
}
注3:fwrite 来自 <stdio.h>
函数声明:size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
四个参数,分别代表数据源、元素大小、元素数目、写入目的地
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
表示从 data+i 中将1个大小为 sizeof(PeoInfo) 的数据写入 pf 中
DestoryContact的实现
工作:将在堆区开辟的空间释放掉,然后把 data 、sz 和 capacity 置空
函数实现
contact.c中写入:
void DestoryContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}