//1.Contact.c 实现函数的功能
#include"contact.h"
void InitContact(Contact* ps) //初始化
{
/*
* assert 是C语言处理错误的一种机制,其具有强制性,若不符合,进程crash
* 其包含在 #include <assert.h> 头文件中
* 例如下面的,如果p == NULL,那么此时就相当于assert(0),进程挂掉
* 后面的assert 大致相同,就不一一解释了
*/
assert(ps);
/*
* 函数原型: void *malloc( size_t size );
* size 代表你需要开辟多少个空间,单位为字节
* malloc是C进行动态开辟内存的一个函数
* 如果malloc成功申请空间,那么返回该空间的首地址
* 如果malloc申请失败,则返回NULL
* 注意 malloc返回值的类型是一个 void*,因此往往都需要对返回值进行强转,以便于正常使用
*/
ps->data = (PeoInfo*)malloc(MAX_CONTENT * sizeof(PeoInfo)); //动态开辟
ps->size = 0;
ps->max_content = MAX_CONTENT;
/*
* malloc 是由存在失败的可能性的
* 因此需要检查其是否申请成功
*/
assert(ps->data);
// 每次初始化该顺序表,都会先把文件中的已经存在的信息加载到当前通讯录里
LoadContact(ps);
}
void Check_Content(Contact* ps); //声明一下
void LoadContact(Contact* ps)
{
assert(ps);
ps->size = 0;
PeoInfo tmp = { 0 }; //将从文件读取内容 存放到tmp;
// 1. 以二进制读的方式打开文件
FILE* Pf_Read = fopen("Contact_Data.txt", "rb");
/*
* FILE *fopen( const char *filename, const char *mode );
* filename 是你要打开的文件的文件名,是一个字符串
* mode 即你打开文件的方式 一般有 "w" --- 以文本写打开文件 "r" --- 以文本读打开文件
* "wb" --- 以二进制写的方式打开文件 "rb" --- 以二进制读的方式打开文件
* 同样,打开文件也是存在失败的可能的,因此我们同样需要检查其返回值
* 至于这个返回值,在这里简单理解即可,它是一个文件指针, 本质上这个FILE是一个结构体,有兴趣的可以下去查一下
*/
if (Pf_Read == NULL)
{
printf("LoadContact::%s\n", strerror(errno));
return;
}
else
{
/*2.读取文件 存放到通讯录中 读取的信息 存放到通讯录中
* size_t fread(void* buffer, size_t size, size_t count, FILE * stream);
* 返回值为size_t 真实读到的元素个数
* buffer 即缓冲区,将读取的数据存放进缓冲区内
* size 代表一次读多少个字节
* count 代表 你要每次读多少个size大的数据
* stream 从特定流中读取,在这里就是一个文件流
* 读取多少个?
* 加载的时候 应该考虑 容量的问题
* fread 返回的值是 1 继续读 如果返回值 为 0 退出循环
*/
while (fread(&tmp, sizeof(PeoInfo), 1, Pf_Read))
{
Check_Content(ps); //保证该空间有效
// 读取的数据一次存入当前通讯录中
ps->data[ps->size] = tmp;
ps->size++;
}
}
// 打开文件 一定要 关闭文件
fclose(Pf_Read);
Pf_Read = NULL;
}
void Check_Content(Contact* ps)
{
assert(ps);
// 如果当前数据个数 == 最大数据量
// 进行扩容
if (ps->size == ps->max_content)
{
/*
* 扩容的本质是将顺序表的容量扩容
* 即这个结构的 ps->data 它就是一个顺序表 ,其本质是一个动态的数组
*
* void *realloc( void *memblock, size_t size );
* realloc 是一个可以更改当前开辟空间的容量的,它分为原地扩容和异地扩容
* 就是讲你开辟的这段空间进行一个扩容,如果在内存区域,你这段空间后面有足够的空间给你使用,那么会原地扩容
* 如果没有足够的空间,它会自动将当前空间的内容拷贝到另一段足够扩容后的空间,并将以前的空间释放掉,最后返回新的空间的首地址
* 与malloc类似,它也有可能会失败,因此需要检查返回值
*/
PeoInfo* ps1 = realloc(ps->data, ((ps->max_content) + 3) * sizeof(PeoInfo));
if (ps1 == NULL)
{
printf("增容失败\n");
printf("%s\n", strerror(errno));
}
else
{
printf("增容成功\n");
ps->data = ps1;
ps->max_content += 3;
}
}
}
void AddContact(struct Contact* ps) //增
{
assert(ps);
/*
* Add 就是添加新数据, 自然而然我们需要考虑当前容量是否支持继续增加数据
* 因此在这里需要检查容量,如果容量不够,则扩容
*/
Check_Content(ps);
/*
* 依次进行输入,将值存入顺序表中
*/
printf("请输入名字:\n");
scanf("%s", ps->data[ps->size].name);
printf("请输入年龄:\n");
scanf("%d", &(ps->data[ps->size].age));
printf("请输入性别:\n");
scanf("%s", ps->data[ps->size].sex);
printf("请输入电话:\n");
scanf("%s", ps->data[ps->size].tele);
printf("请输入住址:\n");
scanf("%s", ps->data[ps->size].addr);
/*
* 添加成功后,数据容量需要更新
*/
ps->size++;
printf("添加成功\n");
}
void ShowContact(const struct Contact* ps) //打印
{
assert(ps);
/*
* 如果没有数据,那么直接结束当前函数即可
* 在这里补充一下, 对于非main() return; 是代表着函数的返回值,进程不会退出
* 而对于main()的 return code; 其代表着当前进程的退出码,进程终止后,以便于其父进程判断该子进程是否正常退出
*/
if (ps->size == 0)
{
printf("没有存储信息\n");
return;
}
else
{
int i = 0;
/*
* 解释一下 %-20s 输入这个字符串会一共占用20个位置,并且左对齐
* 其他依次类推
* -t 是一个制表符, 默认情况下 占4个位置
*/
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址"); //用 '-' 对齐
for (i = 0; i < ps->size; i++)
{
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
ps->data[i].name,
ps->data[i].age,
ps->data[i].sex,
ps->data[i].tele,
ps->data[i].addr
);
}
}
}
// static 修饰的函数具有文件作用域,只能在contact.c使用 (内部函数)
static int FindName(const struct Contact* ps, char NAME[MAX_NAME])
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
/*
* int strcmp( const char *string1, const char *string2 );
* strcmp函数可以比较两个字符串是否相同
* 如果相同,返回0
* 不相同, 前者 greater than 后者 返回 > 0
* 前者 less than 后者 返回 < 0
*/
if (0 == strcmp(ps->data[i].name, NAME))
{
return i; //找到了 返回下标
}
}
return -1; //没找到
}
void DelContact(struct Contact* ps) //删
{
assert(ps);
char NAME[MAX_NAME];
//1.删除谁?
printf("请输入所需要删除的名字:>\n");
scanf("%s", NAME);
// 2. 查找这个名字是否存在
int ret = FindName(ps, NAME); //找到了 返回下标, 找不到, 返回-1;
if (ret == -1)
{
printf("没有找到该名字,无法删除\n");
}
//3.删除 本质上是一种覆盖删除
else
{
int j = 0;
//从右向左 依次覆盖,将后面的数据把前面的数据进行依次覆盖
for (j = ret; j < ps->size - 1; j++)
{
ps->data[j] = ps->data[j + 1];
}
// 循环结束,有效数据个数--
ps->size--;
printf("删除成功\n");
}
}
void SearchContact(const struct Contact* ps) //查找
{
assert(ps != NULL);
//1.找谁?
printf("请输入要查找的名字:>\n");
char NAME[MAX_NAME];
scanf_s("%s", NAME);
// 2. 查看该名字是否存在
int ret = FindName(ps, NAME);
if (ret == -1)
printf("没有该名字\n");
else
{
printf("找到了\n");
printf("信息如下:\n");
printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "住址"); //用 '-' 对齐
printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
ps->data[ret].name,
ps->data[ret].age,
ps->data[ret].sex,
ps->data[ret].tele,
ps->data[ret].addr
);
}
}
void ModifyContact(struct Contact* ps) //修改
{
assert(ps);
//1.修改谁?
char NAME[MAX_NAME];
printf("请输入要输入的名字:>\n");
scanf_s("%s", NAME);
// 2. 查找该名字是否存在
int ret = FindName(ps, NAME);
if (ret == 1)
{
printf("要修改的名字不存在\n");
}
else
{
// 对该位置的数据进行重写
printf("请输入名字:\n");
scanf("%s", ps->data[ret].name);
printf("请输入年龄:\n");
scanf("%d", &(ps->data[ret].age));
printf("请输入性别:\n");
scanf("%s", ps->data[ret].sex);
printf("请输入电话:\n");
scanf("%s", ps->data[ret].tele);
printf("请输入住址:\n");
scanf("%s", ps->data[ret].addr);
printf("修改成功\n");
}
}
/*
* cmp_friend_by_age && cmp_friend_by_name
* 是两个比较函数 它们都是以升序进行默认排序的
* 这里最所以用const void* 是因为这个类型可以接受任意类型的指针类型
* 传过来的指针,要通过强制类型转换成我们需要比较的类型,在这里就是一个PeoInfo结构体的指针
*/
int cmp_friend_by_age(const void* p1, const void* p2) //排序年龄
{
assert(p1 && p2);
return ((struct PeoInfo*)p1)->age - ((struct PeoInfo*)p2)->age;
}
//int cmp_friend_by_name(const void* p1, const void* p2)
//{