文章目录
初级通讯录 – 利用数组来存储数据
1.通讯录中大致的一个人的信息
我们利用结构体创建一个PePInfo用来记录一个人的基本信息
typedef struct PePInfo //记录人的信息.
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
int phonum[MAX_PHONUM];
char addr[MAX_ADDR];
}PePInfo;
有名字,年龄,性别,电话号码,住址
我用的是define定义的常变量为方便后续要更改不会整体更改
2. 通讯录中需要实现的功能和排版
我们通讯录需要实现的有,增加联系人,修改,删除,查看单独的联系人,排序,查看所有联系人且我们选择和三子棋一样的布局方式,有菜单有选择
利用switch 中的case语句来对应到每一个功能
3.由于通讯录中不能只有一个人的信息我们选择用数组来存储
创建一个data数组来存放这里的人的信息data数组的类型我们声明为一个新的结构体变量叫做content,但是我们光有数据存储的地方是不够的,因为倘若我们不清楚这个里面存储了多少个人的信息,我们要去判断这个通讯里,也就是数组是否存储满了,我们还需要引入一个新的变量pepnum来记录这个data数组的存储情况,这个pepnum与date数组的关系就好似函数与自变量,都是牵动的关系,所以我们将他们放在同一个结构体content中.
这里和第一点一样我们都是采用了常变量的方式去声明这里的大小,为了方便后续的调整
4.利用content创建成员变量去穿插到我们的文件中
利用content去创建一个变量con,我们在实现各种功能的时候都将其封装成了一个个的独立函数,穿插起来就要利用到指针,在结构体传参时我们通常会使用传址调用因为形参是实参的一份临时拷贝,所以结构体传参不适用传值,因为开辟的空间可能会很大,当然还是看具体的情况而定,我们这里传参传我们的con地址
并且在我的.h的头文件中去声明
在我们的content.c中去实现每个函数的定义,这里封装了三个文件,分别是test.c
content.h content.c第一个代表逻辑测试,第二个代表函数声明,第三个代表函数实现
5.当我们在实现各种函数时候的发现
我们发现在删除,修改,查看等地方都会经历一个查找元素的过程,我们就决定将这个过程再封装成一个函数,我们命名为Find_by_name我们通过名字去查找(因为我们这个是简易的通讯录我们就过滤了重名的情况)
int FindbyName(content* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->pepnum; i++)
{
if (strcmp(pc->date[i].name, name) == 0)//说明找到了
{
return i;
break;
}
}
return -1;
}
剩下的函数就可以依次实现了,这个排序的函数可以参考之前写过的qsort的使用
qsort的使用
动态内存开辟
1.malloc()函数
malloc函数的详情
我这里写的均不严谨,待会儿会在动态内存开辟的注意事项中提到
这里的size就是你需要多大的空间单位为字节
了解malloc函数的功能:在内存的堆区上开辟一块儿新的空间并且返回其起始地址,内存中存放的为随机值
int main()
{
int arr[10] = { 0 };
int* pc = (int*)malloc(40);
return;
}
以往我们说需要一个很大的空间来存储数据我们一般使用的是数组来存放相同类型的元素,但是现在我们可以通过一个指针来维护我们的空间,这里的pc指针变量仍然存储的是这40个字节空间的起始地址,往后我们开辟空间就多了一种方式
2.calloc()函数
num 为你想要开辟的多大空间
size为这个空间中每个元素的大小
了解malloc函数的功能:在内存的堆区上开辟一块儿新的空间并且返回其起始地址,但是它与malloc的情况不一样,它会自动的将我们所开辟的空间上的所有值全部初始化为0
3.realloc()函数
realloc函数的详情
realloc函数属于一种追加的函数,它是在你原有的空间下增加空间或者缩小空间,如果当前空间没有足以让它增加的地方时它会在堆区上重新寻找一块儿空间去存放数据,会把旧地址的内容全部copy到新的地址上并且会返回这个新的空间的起始地址
我们可以看见这两个地方的起始地址完全不同了这就是realloc
4.动态内存开辟的注意事项-使用上述函数的注意事项
我刚刚写的函数都有问题,只是需要方便观察我就随性写了,真正使用这三个函数的时候必须要牢记:
1.使用之前先检查-assert
2.使用之后要释放-free
3.最后将其制空-NULL置为空指针
具体化就是:
int main()
{
int* pc = (int*)malloc(40);
if (pc == NULL)
{
perror("malloc");
return 0;//因为开辟失败了
}
printf("%p\n", pc);
int* tmp = NULL;//找一个中间变量
tmp = pc;//将pc的地址先交给tmp让tmp去使用realloc因为如果开辟失败你原先旧地值的所有数据也会不存在
tmp = realloc(pc, 1000);
if (tmp == NULL)
{
perror("realloc");
return 0;
}
pc = tmp;//重新交给我们的pc指针去维护这一块空间
printf("%p", pc);
free(pc);//因为我们不再使用tmp所以无需将其释放,而且你释放pc也相当于把tmp释放了
pc = NULL;
tmp = NULL;
return;
}
这才是一个较为完整的写法,运行起来
如果不按照上述的操作去进行的话,会发生什么后果呢?
第一种:不检查就直接使用其指针会导致使用空指针,而空指针对其进行解引用我们会发现报错
这个报错是访问内存的时候出的问题这个属于越界访问内存会直接导致程序崩溃
第二种:不使用free进行释放,这个会导致内存的泄露,因为使用完的空间迟迟不还给os(操作系统)再下次使用的时候数据仍然存放在此,会导致内存的泄露
第三种:释放后不置为空指针当你对其释放过后却没有置为空指针时,它就已经无指向性,而无指向性的指针我们称之为野指针,而野指针是不能再程序中运行的也会导致程序报错崩溃.
1通过动态内存开辟改造通讯录
改造的方向
1. 使用动态内存的开辟来实现数据的存储
实现使用动态内存开辟的时候我们就不再使用数组而是使用指针来控制了
对此我们再进行内存的初始化时我们就用到了动态内存的开辟
void Initcontent(content* pc)
{
assert(pc);
pc->date = (PePInfo*)malloc(DEFAULT_PEPNUM * sizeof(PePInfo));
if (pc->date == NULL)
{
perror("Initcontent");
return;
}
pc->pepnum = 0;
pc->capacity = DEFAULT_PEPNUM;
}
这里的DEFAULT_PEPNUM指的是默认的大小也是常变量,我默认里面先开辟3个人的空间如果不够我们在利用下述的增容来实现。
2.实现可变的数据存储 —自动增容
自动增容指的是当我们这里的数据存储已经达到了该内存申请的最大空间的时候我们就需要对其进行再一次的增加空间,用的是我们刚讲的realloc函数进行增加空间
,那我们就有另一个问题了,我们是不是还需要一个变量来记录当前的data中的最大容量呢?我们不知道容量的话我们什么时候才开始进行增容呢?
所以我们就创建了一个新的变量叫做capacity
当我们的pepnum和capacity一样大的时候我们就要考虑增容的问题了,pepnum是我们当前data中所已经存放了的人的信息。我们将其过程封装为一个新的函数叫做checkcapacity用于检测是否需要增容
int Checkcapacity(content* pc)
{
if (pc->pepnum == pc->capacity)
{
PePInfo* ptr = (PePInfo*)realloc(pc->date, (pc->capacity + INC_CAPACITY) * sizeof(PePInfo));
if (ptr == NULL)
{
perror(Checkcapacity);
return 0;
}
else
{
pc->date = ptr;
pc->capacity = pc->capacity + INC_CAPACITY;
printf("增容成功\n");
return 1;
}
}
return 1;
}
这下我们就实现了动态的通讯录版本,当然不要忘记了最后还是需要回收已经开辟好的内存空间,全部代码我放在了我的gitee中
大家还得注意这个数据的英文是data,我写成了date但是我也懒得改了,将就看啦
如果有写错的地方欢迎大家来指出,感谢支持!!
全部代码点击这里