说是进阶,其实就是将所学习知识的运用到通讯录中,分别是:
结构体数组
动态内存开辟
通讯录实现文件I/O
结构体数组
(1)首先对功能的实现进行分析:
通讯录能够存储大量联系人的信息,每个联系人都可以用一个结构体来创建,首先我们想到了结构体数组。但是这样创建会出现一个问题,我们不知道这个结构体数组已经录入了多少联系人,不便于此通讯录状态的判断和增加、删除联系人操作的实现
为了解决这个问题,我们再创建一个结构体,创建好的结构体数组是该结构体的成员,再用一个计数器来记录结构体中联系人的数量,这样便解决了上面所述的问题。
另外此结构体在传参的过程中采用了传址:
直接原因:在后期函数的功能实现中要对结构体中的成员进行操作,如果传值搞不定
根本原因:结构体对象较大,在如果采用传值的方式,参数压栈的系统开销比较大,影响性能
所以结构体传参一般都采用传址的方式
(2)功能的实现:
程序的整体框架是一个do-while结构,打印出一个菜单,选择相应的操作在do-while循环内用switch语句实现分支,将每一个switch下的功能封装成一个函数
结构体(通讯录)在传参的过程中传的是地址,所以在每一个函数中都用一个结构体指针来接收,为了逐渐培养写出鲁棒性代码的素养,在每一个需要访问未知地址的函数内先用
assert
来断言指针的有效性创建了一个结构体,想要使用这个结构体首先想到的是初始化,初始化结构体分别对结构体内的每一个成员进行初始化。有两个成员:(1)一个整形(2)一个结构体数组。整形的初始化很容易,而结构体数组的初始化又需要初始化数组中的每一个元素(每一个数组元素都是一个结构体,即每一个联系人的信息),我们可以拿到这块内存的起始地址,也知道每个结构体的大小,所以用memset函数来设置内存
memset(pc->data, 0, MAX*sizeof(peo_infor));
//pc是结构体指针,指向创建了MAX个peo_infor结构体大小的数组
memset(pc->data, 0, sizeof(pc->data));
对实现的功能进行分析,需要完成增、删、查、改、清空、排序和打印通讯录,而删除、查找、修改三个功能的实现都需要在已有的通讯录中查找指定的联系人,所以在实现其他功能之前应该先完成该函数的设计,以备后需。确定一个人在不在通讯录中,通过名字的循环查找便能完成,
但需要注意函数的返回值,在找到之后将此联系人出现在通讯录中的位置记录下来,通过
return进行返回,因为删除、查找、修改三个功能不仅需要将此联系人找到,还要进行相应的操作
下面称该函数为find函数
添加联系人的操作即录入此联系人的所有信息,录入结束后将记录该通讯录中人数的
sz+1
即可,但需要在一开始就做一个判断,判断该通讯录是否已满,满了就提示下,退出即可
printf("请输入姓名:>");
scanf("%s", (*((pc->data)+(pc->sz))).name);
*((pc->data)+(pc->sz))
:pc->data
结构体数组的首元素地址,pc->sz
一个整形,记录结构体数组的偏移量,*((pc->data)+(pc->sz))
找到指定的结构体(指定的联系人)。当然以下的书写形式更方便
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
删除联系人先用find函数进行判断,判断该联系人在不在通讯录中,不在做相应提示并退出,如果在将此联系人后面的数据整体向前平移,便覆盖了此联系人,实现了删除目的
查找联系人便比较简单了,用find函数直接做判断,不存在作相应提示并退出,存在打印此联系人信息
修改联系人也是基于find函数先做判断,不存在作相应提示并退出,若存在,则用类似于实现通讯录整体框架的思想在do-while语句内做switch选择,选择需要修改联系人的信息项,并重置此信息项(即通过scanf重新赋值)
以名字排序整体是用冒泡排序实现的,用strcmp函数对两联系人名字的拼音按照字符串类型进行比较,需要交换时借助于一个结构体临时变量进行交换
打印联系人以sz为循环判断的临界值,循环遍历整个结构体数组,并对每一个结构体中的每一个成员进行打印
清空通讯录控制整个结构体数组相应操作是借助另一个结构体成员sz完成的,只需要将sz置为0,即实现了整个通讯录的“清空”
动态内存开辟
利用结构体数组实现通讯录,一上来就创建了很大的一块内存用于存放结构体数组,所以不妨将结构体数组设置成动态开辟的形式以节约空间
(1)定义通讯录时的差异
设计通讯录的时候,在此结构体中将结构体数组的成员改成了结构体指针,动态内存开辟需要一个初始的空间大小,给定了初始大小如果联系人个数已满,则又需要一个记录结构体数组上限的变量来判断是否扩容,同时记录通讯录中联系人个数的变量也是不可或缺的
(2)初始化时的差异
在初始化这个结构体时,两个整形变量很容易初始化,结构体指针如果用malloc预开辟空间,需要在开辟完成后再调用memset进行初始化,所以我们不妨用calloc来预开辟空间
peo_infor *tmp = 0;
tmp = (peo_infor *)calloc(pc->capacity, sizeof(peo_infor));
if (!tmp)
{
printf("预开辟空间失败!\n");
}
else
{
pc->data = tmp;
}
需要注意的是在赋值之前同malloc函数一样,要对返回指针的有效性做出判断后再赋值使用
(3)添加联系人时的差异
添加联系人时要注意先要判断当前结构体数组的大小与sz的大小,如果相等就需要扩容,扩容使用realloc函数进行,同样要对返回的值进行判断后再使用,还需注意的是realloc需要的空间大小是在原有大小的基础上加上需要扩容的大小。扩容成功后重置结构体数组的空间上限与结构体指针的起始地址,接下来就按照之前的方式添加即可
(4)程序结束时动态内存开辟的释放
malloc/calloc与free要成对出现,因为忘记释放不再使用的动态开辟的空间会造成内存泄漏,所以要使用free进行动态开辟空间的释放
此处最好将程序结束前的操作封装成函数,以体现代码整体思路的清晰化、模块化
通讯录实现文件I/O
通讯录在每次结束程序后所记录的联系人都会销毁,如果在程序结束之前先将记录的所有信息写入到文件中保存,下次启动程序的时候再在初始化的时候将文件中的信息加载到当前通讯录中即可实现联系人的保存
首先想到的是在动态添加联系人的时候要比较当前通讯录人数和通讯录容量,以判断需不需要扩容,所以有必要将此判断功能封装成一个函数,因为在加载文件信息的时候也需要判断是否要扩容的问题(这点自己在实现动态内存开辟的时候没有考虑到)
所以让通讯录实现退出后保存联系人(实现文件I/O),只需要再实现两个函数:(1)保存通讯录信息到文件(2)从文件中加载处信息到当下通讯录
- 保存通讯录
在实现功能之前需要对fopen函数作相应了解,因为不管是保存还是加载通讯录都需要使用fopen打开一个文件
而将信息写入文件还需要另一个函数,fwrite
目标流即输入流,也就是我们要写入的文件
在使用此函数时我们采用一次读取一个联系人(结构体)的方式进行,用一个for循环实现
数据写入完成后记得关闭文件、指针置空
- 加载通讯录
前半部分打开文件的操作与保存通讯录相同,但实现加载需要使用fread函数
利用一个循环同fwrite一样每次读取一个结构体的数据,我们在调用fread函数后不知道是否成功读取到了信息,所以在循环条件的判断地方利用fread返回值进行判断,每次读取一个,读取成功返回1,失败返回0,失败的时候也是读取数据结束的时候,此时跳出循环
所以这里需要一个结构体的临时变量来临时存储读到的信息,如果循环判断为真,再录入通讯录
至此正文已经结束,以下是最终版代码,如有兴趣,请继续浏览!若有纰漏还望指出,不胜感激!
contact.h
#ifndef __CONTACT_H__
#define __CONTACT_H__
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 20
#define ADDR_MAX 25
#define CAPACITY 3//默认容量
#define ADD_CAP 2//每次扩容容量
typedef struct peo_infor
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char addr[ADDR_MAX];
}peo_infor;
typedef struct contact
{
peo_infor *data;
int sz;
int capacity;//容量上限
}contact, *pContact;
void init_contact(pContact pc);
void add_contact(pContact pc);
void show_contact(const pContact pc);
void del_contact(pContact pc);
void search_contact(const pContact pc);
void modify_contact(pContact pc);
void empty_contact(pContact pc);
void sort_contact(pContact pc);
void save_contact(pContact pc);
void load_contact(pContact pc);
void destroy_contact(pContact pc);
#endif//__CONTACT_H__
contact.c
#include "contact.h"
static int is_expend(pContact pc)
{
peo_infor *tmp = 0;
assert(pc);
if (pc->sz==pc->capacity)
{
tmp = (peo_infor *)realloc(pc->data, (pc->capacity+ADD_CAP)*sizeof(peo_infor));
if (!tmp)
{
printf("扩容失败!\n");
return 0;
}
else
{
pc->data = tmp;
pc->capacity += ADD_CAP;
printf("扩容成功!\n");
return 1;
}
}
return 1;
}
void init_contact(pContact pc)
{
peo_infor *tmp = 0;
assert(pc);
pc->sz = 0;
pc->capacity = CAPACITY;
tmp = (peo_infor *)calloc(pc->capacity, sizeof(peo_infor));
if (!tmp)
{
printf("预开辟空间失败!\n");
exit(EXIT_FAILURE);
}
else
{
pc->data = tmp;
}
load_contact(pc);
}
void add_contact(pContact pc)
{
int judge = 0;
assert(pc);
judge = is_expend(pc);
if (!judge)
{
return;
}
printf("请输入姓名:>");
scanf("%s", (*((pc->data)+(pc->sz))).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");
}
void show_contact(const pContact pc)
{
int i = 0;
assert(pc);
printf("%-5s\t%-3s\t%-3s\t%-15s\t%-10s\t\n",
"姓名", "年龄", "性别", "电话", "住址");
for (i=0; i<pc->sz; i++)
{
printf("%-5s\t%-3d\t%-3s\t%-15s\t%-10s\t\n"
, pc->data[i].name
, pc->data[i].age
, pc->data[i].sex
, pc->data[i].tele
, pc->data[i].addr
);
}
}
static int find_data(pContact pc, char *p_name)
{
int i = 0;
assert(pc);
for (i=0; i<pc->sz; i++)
{
if (!strcmp(pc->data[i].name, p_name))
{
return i;
}
}
return -1;
}
void del_contact(pContact pc)
{
char name[NAME_MAX] = {0};
int i = 0;
int pos = 0;
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空,无法删除!\n");
return;
}
printf("请输入需要删除人的名字:>");
scanf("%s", name);
pos = find_data(pc, name);
if (pos == -1)
{
printf("所要删除的人不存在\n");
return;
}
else
{
for (i=pos; i<pc->sz-1; i++)
{
pc->data[i] = pc->data[i+1];
}
pc->sz--;
printf("删除成功!\n");
}
}
void search_contact(const pContact pc)
{
char name[NAME_MAX] = {0};
int pos = 0;
assert(pc);
printf("请输入需要查找人的名字:>");
scanf("%s", name);
pos = find_data(pc, name);
if (pos == -1)
{
printf("所要查找的人不存在\n");
return;
}
else
{
printf("%-5s\t%-3s\t%-3s\t%-15s\t%-10s\t\n",
"姓名", "年龄", "性别", "电话", "住址");
printf("%-5s\t%-3d\t%-3s\t%-15s\t%-10s\t\n"
, pc->data[pos].name
, pc->data[pos].age
, pc->data[pos].sex
, pc->data[pos].tele
, pc->data[pos].addr
);
}
}
enum Informatiom
{
EXIT,
NAME,
AGE,
SEX,
TELE,
ADDR
};
static void menu()
{
printf("****————————————****\n");
printf("****| 1.name | 2.age |****\n");
printf("****| 3.sex | 4.tele |****\n");
printf("****| 5.addr | 0.exit |****\n");
printf("****————————————****\n");
}
void modify_contact(pContact pc)
{
int input = 0;
char name[NAME_MAX] = {0};
int pos = 0;
assert(pc);
printf("请输入需要修改人的名字:>");
scanf("%s", name);
pos = find_data(pc, name);
if (pos == -1)
{
printf("所要修改的人不存在\n");
return;
}
else
{
do
{
menu();
printf("请选择此人需要修改的信息项:>");
scanf("%d", &input);
switch(input)
{
case NAME:
printf("请重置姓名:>");
scanf("%s", pc->data[pos].name);
break;
case AGE:
printf("请重置年龄:>");
scanf("%d", &(pc->data[pos].age));
break;
case SEX:
printf("请重置性别:>");
scanf("%s", pc->data[pos].sex);
break;
case TELE:
printf("请重置电话:>");
scanf("%s", pc->data[pos].tele);
break;
case ADDR:
printf("请重置住址:>");
scanf("%s", pc->data[pos].addr);
break;
case EXIT:
printf("操作成功!\n");
break;
default:
printf("输入有误,请再次选择!\n");
break;
}
}while (input);
}
}
void sort_contact(pContact pc)
{
int i = 0;
int j = 0;
assert(pc);
for (i=0; i<pc->sz-1; i++)
{
for (j=0; j<pc->sz-1-i; j++)
{
if (strcmp(pc->data[j].name, pc->data[j+1].name)>0)
{
peo_infor tmp = pc->data[j];
pc->data[j] = pc->data[j+1];
pc->data[j+1] = tmp;
}
}
}
printf("排序成功!\n");
}
void empty_contact(pContact pc)
{
assert(pc);
pc->sz = 0;
printf("已清空!\n");
}
void save_contact(pContact pc)
{
int i = 0;
FILE *pf = fopen("contact.data.txt", "wb");
assert(pc);
if (!pf)
{
perror("open file for save");
exit(EXIT_FAILURE);
}
for (i=0; i<pc->sz; i++)
{
fwrite(pc->data+i, sizeof(peo_infor), 1, pf);
}
fclose(pf);
pf = NULL;
}
void load_contact(pContact pc)
{
peo_infor tmp = {0};
int i = 0;
FILE *pf = fopen("contact.data.txt", "rb");
assert(pc);
if (!pf)
{
perror("open file for load");
exit(EXIT_FAILURE);
}
while (fread(&tmp, sizeof(peo_infor), 1, pf))
{
if (!is_expend(pc))
{
exit(EXIT_FAILURE);
}
pc->data[pc->sz] = tmp;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
void destroy_contact(pContact pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
test.c
#include "contact.h"
void menu()
{
printf("****————————————****\n");
printf("****| 1.add | 2.del |****\n");
printf("****| 3.search | 4.modify|****\n");
printf("****| 5.sort | 6.show |****\n");
printf("****| 7.empty | 0.exit |****\n");
printf("****————————————****\n");
}
enum option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
SHOW,
EMPTY
};
void test()
{
int input = 0;
contact con;
init_contact(&con);
do
{
menu();
printf("欢迎使用通讯录,请选择操作:>");
scanf("%d", &input);
switch(input)
{
case ADD:
add_contact(&con);
break;
case DEL:
del_contact(&con);
break;
case SEARCH:
search_contact(&con);
break;
case MODIFY:
modify_contact(&con);
break;
case SORT:
sort_contact(&con);
break;
case SHOW:
show_contact(&con);
break;
case EMPTY:
empty_contact(&con);
break;
case EXIT:
save_contact(&con);
destroy_contact(&con);
printf("程序退出\n");
break;
default:
printf("输入有误,请重输!\n");
break;
}
}while (input);
}
int main()
{
test();
return 0;
}