域名可以自己注册吗,大连网站流量优化定制,启动网站集约化建设,坡头手机网站建设引言
动态内存管理的函数有#xff1a;malloc,calloc,ralloc,free,本文讲解动态内存函数和使用#xff0c;如何进行动态内存管理,实现通讯录联系人容量的动态化#xff0c;对常见动态内存错误进行总结。 ✨ 猪巴戒#xff1a;个人主页✨ 所属专栏#xff1a;《C语言进阶》…引言
动态内存管理的函数有malloc,calloc,ralloc,free,本文讲解动态内存函数和使用如何进行动态内存管理,实现通讯录联系人容量的动态化对常见动态内存错误进行总结。 ✨ 猪巴戒个人主页✨ 所属专栏《C语言进阶》 跟着猪巴戒一起学习C语言 目录
引言
为什么存在动态内存分配
malloc
动态内存空间位置 内存泄漏问题
free
calloc
realloc
realloc如何开辟动态内存空间
动态版本的通讯录
常见动态内存错误
1.对NULL空指针进行解引用
2.对动态开辟空间的越界访问
3.对非动态开辟的内存使用free释放
4.使用free释放动态开辟内存的一部分
5.对同一块动态内存多次释放
6.动态开辟内存忘记释放内存释放
动态通讯录的实现 为什么存在动态内存分配
int main()
{int arr[20] {0};//整形数组开辟20个元素就是80个字节。return 0;
}
1.空间开辟大小是固定的
2.数组在申明的时候必须指定数组的长度它所需要的内存在编译时分配。
如果我们开辟的空间不够那么进行修改会比较麻烦。如果开辟的空间较大那么所占据的空间又会很大。那么有没有一种方法可以用多少空间就开辟多大的内存呢
这个时候就有了动态内存开辟。 malloc
动态内存函数的头文件 stdlib.h
void* malloc (size_t size) 申请内存块
size
就是我们要申请的字节大小。当然我们的内存是有限的不是想要申请多少内存就可以申请多少内存。
返回值
如果开辟成功则返回一个指向开辟好空间的指针。如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。返回值的类型是void*所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。
开创的空间放回地址是void*类型使用的时候记得要强制类型转换。
所以我们在使用malloc函数时要检测返回值p是否是空指针。
#include string.h
#include errno.h
#include stdlib.h
#includestdio.h
int main()
{int arr[10] { 0 };//动态内存开辟int* p (int*)malloc(40);if (p NULL) //检测返回的p是不是空指针。{printf(%s\n, strerror(errno));return 1;}//使用动态内存int i 0;for (i 0; i 10; i){*(p i) i;}for (i 0; i 10; i){printf(%d , *(p i));}//释放free(p); //常常搭配malloc、calloc、realloc使用后面会讲到p NULL;return 0;
} 动态内存空间位置 动态内存函数是在堆区开辟内存空间的。我们一般的局部变量、形式参数都是存放在栈区。 内存泄漏问题
什么是内存泄漏
我们创建的局部变量数据会在函数结束时释放。
动态内存空间存放临时使用的数据这些数据不必等到函数结束时释放而是需要时随时开辟不需要时随时释放。动态开辟的内存使用完是要进行释放的如果不对内存进行释放那么开辟的动态内存就会被之前的数据占据这部分的内存就无法使用相当于丢失了内存。因此我们把这类问题叫做内存泄漏。 下面的代码如果吧进行释放那么就会一直占据内存空间。 通过释放动态内存内存可以被重新调用 最后我们加了一步p NULLp的内存空间已经被释放了还给操作系统了但是p还是原来的地址我们通过p就会使用到一个已经释放的内存这就会导致野指针问题。为了避免这种情况我们将p的地址去掉就可以了。 free
void free (void* ptr)
free函数是专门用来释放动态开辟的内存。
如果参数ptr指向的空间不是动态开辟的那free函数的行为是未定义的。如果参数ptr 是NULL指针则函数什么事都不做。
free函数只能释放动态内存开辟的空间如果释放其他空间就会报错。
#include stdlib.h
int main()
{int a 0;int* p a;free(p);p NULL;return 0;
} calloc
void* calloc (size_t num, size_t size) num
开辟空间元素的个数
size
空间中每个元素的大小。 函数的功能为num个大小为size 的元素开辟一块空间并且把空间的每个字节初始化为0calloc和malloc的区别在于calloc在放回地址之前把申请的空间的每个字节初始化为全0。 下面的例子就是解释
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{int* p (int*)calloc(10, sizeof(int));if (p NULL){printf(%s\n, strerror(errno));}//打印int i 0;for (i 0; i 10; i){printf(%d , *(p i));}//释放free(p);p NULL;return 0;
} realloc
void* realloc (void* ptr, size_t size) ptr
要调整的空间的起始位置。
size
realloc函数的出现让动态内存管理更加灵活。有时我们发现过去申请的空间太小了有时候我们又会觉得申请的空间豁达了那为了合理的内存我们一定对内存的大小做灵活的调整。那realloc函数可以做到对动态开辟内存大小的调整。
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{int* p (int*)malloc(40);if (p NULL){printf(%s\n, strerror(errno));}//使用int i 0;for (i 0; i 10; i){*(p i) i 1;}//扩容int* ptr realloc(p, 80);if (ptr ! NULL) //这里不直接将新的地址赋给p是因为realloc有可能开辟失败返回空指针。后面详细讲{p ptr;}for(i0;i10;i){printf(%d , *(p i));}//释放free(p);p NULL;return 0;
} realloc如何开辟动态内存空间
realloc在调整内存空间的是存在两种情况。
第一种情况原空间之后有足够大的空间。当要进行调整的内存空间后面有多余的40个字节空间那么就可以直接开辟向后面开辟40个字节的空间然后放回起始位置的地址这里指的是0的地址。 第二种情况原空间之后没有足够大的空间。要调整的内存空间后面不足以存放40个字节的空间那么就要重新找到一个新的地址可以存放80个字节开辟开辟成功后返回起始位置的地址。
注意如果需要开辟的空间过大是会开辟失败的开辟失败realloc返回空指针所以要检查空指针。
这里把realloc开辟的空间换成8000来实现第二种情况。 动态版本的通讯录
动态
要实现通讯录容量的动态化要实现两个功能
1.通讯录默认能存放3个人的信息
2.如果空间不够了就增加空间每次增加2个人的空间
静态
原来的通讯录的信息由结构体组成的数组来存放信息设置的是100人的信息。
1.当没有100人的信息时会造成空间上的浪费。
2.当超过100人的信息时又无法自动扩容如果想要扩容要手动改变最大存放的空间。
原通讯录的代码
有兴趣可以学习通讯录的实现http://t.csdnimg.cn/UbT9I 存放数据的改变
首先把存放联系人信息的结构体给大家看看
typedef struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
}PeoInfo;
原来存放联系人的信息是通过数组data[100],这样的数组的空间是固定的是一开始设置的最大容量。
typedef struct Contact
{PeoInfo data[MAX];int count;//记录当前通讯录中实际人数的个数
}Contact;
不以数组的形式进行存放以数组改成地址但是地址也可以像数组一样访问数据。data是联系人的起始地址如果想要访问第二个人就是*(data1),等于data[1]。
count是当前使用了多少个联系人的空间.
capacity记录的是现在存放联系人的容量是多大。因为当每次容量不够时就增加空间每次增加2个人的空间。也就是每次count capacity的时候capacity就要加2.
typedef struct Contact
{PeoInfo* data;int count;//记录当前通讯录中实际人数的个数int capacity;
}Contact;
1.初始化的改变
assert函数用来检验空指针如果为空指针就会报错。
参数pc是创立的struct Contact结构体变量的地址这里是传址调用作用就是改变原来的数据。
原通讯录是存放100个联系人的数组将100个联系人的数据初始化为0。
void InitContact(Contact* pc)
{assert(pc);pc-count 0;memset(pc-data, 0, sizeof(pc-data));
}
动态通讯录要实现开辟3个联系人的空间并将它们进行初始化。 既要开辟空间又要进行初始化我们想到calloc函数。
malloc是单纯地开辟空间realloc是既开辟空间并进行初始化。malloc和realloc地区别就在于是否对开辟的空间初始化。
开辟3个联系人空间calloc进行开辟将地址传给pc-data.将记录联系人的容量传给capacity.
int InitContact(Contact* pc)
{assert(pc);pc-count 0;pc-data (int*) calloc(3, sizeof(PeoInfo));if (pc NULL){printf(InitContact::%s\n, strerror(errno));return 1;}pc-capacity 3;return 0;
} 2.增加联系人
参数pc是创立的struct Contact结构体变量的地址这里是传址调用作用就是改变原来的数据。
动态内存管理通过pc-data[count]可以进行数据的输入。最重要的是实现通讯录容量的动态化。
count表示已经使用的联系人数量capacity表示联系人的总容量。
当count capacity时就要动态开辟内存对容量进行增容。
CheckCapacity为自定义增容函数我们要实现增容的功能。
realloc重新开辟内存块实现内存的动态化。realloc返回的起始地址不能直接传给data因为动态内存的开辟有可能失败失败传回空指针。
capacity增加2最后提示增容成功。
void CheckCapacity(Contact* pc)
{if (pc-count pc-capacity);{PeoInfo* ptr (PeoInfo*)realloc(pc-data, (pc-capacity 2) * sizeof(PeoInfo));if (ptr NULL){printf(AddContact::%s\n, strerror(errno));return 1;}else{pc-data ptr;pc-capacity 2;printf(增容成功\n);}}
}
void AddContact(Contact* pc)
{assert(pc);//增容CheckCapacity(pc);printf(请输入名字》);scanf(%s, pc-data[pc-count].name);printf(请输入年龄》);scanf(%d, (pc-data[pc-count].age));printf(请输入性别);scanf(%s, pc-data[pc-count].sex);printf(请输入电话);scanf(%s, pc-data[pc-count].tele);printf(请输入地址);scanf(%s, pc-data[pc-count].addr);pc-count;printf(增加成功\n);
}
将整体的代码呈现在文章末尾。 常见动态内存错误 1.对NULL空指针进行解引用
如果开辟的空间过大malloc有可能开辟失败开辟失败就会返回空指针。如果直接对p进行解引用就会产生问题。
#include stdlib.h
int main()
{int* p (int*)malloc(40);*p 20;return 0;
}
正确解决方法
在开辟动态内存后对p进行检验是否为空指针。
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{int* p (int*)malloc(40);if (p NULL){printf(%s\n, strerror(errno));return 1;}*p 20;free(p);p NULL;return 0;
} 2.对动态开辟空间的越界访问
我们只开辟了10个字节的空间但是访问从0到1010算进去的话就是11个元素这里访问越界了就会出问题。
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{int* p (int*)malloc(40);if (p NULL){printf(%s\n, strerror(errno));return 1;}//使用int i 0;for (i 0; i 10;i){p[i] i;}free(p);p NULL;return 0;
}
正确解决方法
一定要注意我们开辟的空间是否和访问的空间是一样的。
#include stdio.h
#include stdlib.h
#include string.h
#include errno.h
int main()
{int* p (int*)malloc(40);if (p NULL){printf(%s\n, strerror(errno));return 1;}//使用int i 0;for (i 0; i 10;i){p[i] i;}free(p);p NULL;return 0;
} 3.对非动态开辟的内存使用free释放
free只能够释放动态开辟的内存不能够随意去使用函数。
#include stdlib.h
int main()
{int a 10;int* p a;//....free(p);p NULL;return 0;
} 4.使用free释放动态开辟内存的一部分
free做不到释放动态内存的一部分如果要释放要将整个动态内存进行释放。
#include stdlib.h
int main()
{int* p (int*)malloc(40);if (p NULL){return 1;}int i 0;for (i 0; i 10; i){*p i;p;}free(p);p NULL;return 0;
}
正确解决方法
不改变p的位置对p进行释放。使用p的话通过
*(pi) i; 5.对同一块动态内存多次释放
free的二次使用第二次使用的动态内存空间已经还给操作系统了但是还能对p进行操作就是野指针问题。
#include stdlib.h
int main()
{int* p (int*)malloc(40);free(p);//....free(p);return 0;
}
正确解决方法
避免free的二次使用或者将p转化为空指针。
#include stdlib.h
int main()
{int* p (int*)malloc(40);free(p);p NULL;free(p);return 0;
} 6.动态开辟内存忘记释放内存释放
看看下面的例子
如果flag 5的话那么后面free函数就会跳过动态开辟的内存就不能释放。
#include stdio.h
#include stdlib.h
void test()
{int* p (int*)malloc(100);int flag 0;scanf(%d, flag);if (flag 5){return;}free(p);p NULL;
}
int main()
{test();return 0;
}
动态通讯录的实现 contact.h
头文件用来对函数的声明
#pragma once#include string.h
#include stdio.h
#include assert.h
#include stdlib.h#define DEFAULT_SZ 3
#define INC_SZ 2
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30//类型的声明
typedef struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
}PeoInfo;typedef struct Contact
{PeoInfo* data;int count;//记录当前通讯录中实际人数的个数int capacity;
}Contact;//初始化通讯录
int InitContact(Contact* pc);//销毁通讯录
void DestroyContact(Contact* pc);//增加联系人到通讯录
void AddContact(Contact* pc);//打印通讯录中信息
void ShowContact(const Contact* pc);//删除联系人的信息
void DelContact(Contact* pc);//查找指定联系人
void SeachContact(Contact* pc);//修改指定联系人
void ModifyContact(Contact* pc);//排序通讯录中的内容
//按照名字来排序
void SortContact(Contact* pc); contact.c
源文件函数的实现和定义
#define _CRT_SECURE_NO_WARNINGS#include contact.h//动态版本
int InitContact(Contact* pc)
{assert(pc);pc-count 0;pc-data (int*) calloc(DEFAULT_SZ, sizeof(PeoInfo));if (pc NULL){printf(InitContact::%s\n, strerror(errno));return 1;}pc-capacity DEFAULT_SZ;return 0;
}void DestroyContact(Contact* pc)
{assert(pc);free(pc-data);pc-data NULL;
}void CheckCapacity(Contact* pc)
{if (pc-count pc-capacity);{PeoInfo* ptr (PeoInfo*)realloc(pc-data, (pc-capacity INC_SZ) * sizeof(PeoInfo));if (ptr NULL){printf(AddContact::%s\n, strerror(errno));return 1;}else{pc-data ptr;pc-capacity INC_SZ;printf(增容成功\n);}}
}
void AddContact(Contact* pc)
{assert(pc);//增容CheckCapacity(pc);printf(请输入名字》);scanf(%s, pc-data[pc-count].name);printf(请输入年龄》);scanf(%d, (pc-data[pc-count].age));printf(请输入性别);scanf(%s, pc-data[pc-count].sex);printf(请输入电话);scanf(%s, pc-data[pc-count].tele);printf(请输入地址);scanf(%s, pc-data[pc-count].addr);pc-count;printf(增加成功\n);
}void ShowContact(const Contact* pc)
{assert(pc);int i 0;//一个汉字是两个字符printf(%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n, 名字, 年龄, 性别, 电话, 地址);for (i 0; i pc-count; i){printf(%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n, pc-data[i].name,pc-data[i].age,pc-data[i].sex,pc-data[i].tele,pc-data[i].addr);}
}static int FindByName(Contact* pc, char name[])
{assert(pc);int i 0;for (i 0; i pc-count; i){if (0 strcmp(pc-data[i].name, name)){return i;}}return -1;
}void DelContact(Contact* pc)
{char name[MAX_NAME] { 0 };assert(pc);int i 0;if (pc-count 0){printf(通讯录为空没有信息可以删除\n);return;}printf(请输入要删除人的名字);scanf(%s, name);//删除//1.查找int pos FindByName(pc, name);if (pos -1){printf(要删除的人不存在\n);return;}//2.删除for (i pos; i pc-count; i){pc-data[i] pc-data[i 1];}pc-count--;
}void SeachContact(Contact* pc)
{assert(pc);char name[MAX_NAME] { 0 };printf(请输入需要查找的联系人的名字:);scanf(%s, name);//1.查找int pos FindByName(pc, name);if (pos -1){printf(要查找的人不存在\n);return;}//2.打印printf(%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n, 名字, 年龄, 性别, 电话, 地址);printf(%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n, pc-data[pos].name,pc-data[pos].age,pc-data[pos].sex,pc-data[pos].tele,pc-data[pos].addr);}void ModifyContact(Contact* pc)
{assert(pc);char name[MAX_NAME] { 0 };printf(请输入需要查找的联系人的名字:);scanf(%s, name);//1.查找int pos FindByName(pc, name);if (pos -1){printf(要查找的人不存在\n);return;}printf(要修改人的信息已经找到接下来进行修改\n);//2.修改printf(请输入名字》);scanf(%s, pc-data[pos].name);printf(请输入年龄》);scanf(%d, (pc-data[pos].age));printf(请输入性别);scanf(%s, pc-data[pos].sex);printf(请输入电话);scanf(%s, pc-data[pos].tele);printf(请输入地址);scanf(%s, pc-data[pos].addr);printf(修改成功\n);
}int cmp_peo_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)-name, ((PeoInfo*)e2)-name);
}
//按照名字来排序
void SortContact(Contact* pc)
{assert(pc);qsort(pc-data, pc-count, sizeof(PeoInfo), cmp_peo_by_name);printf(排序成功\n);
}
test.c
通讯录主脉络
#define _CRT_SECURE_NO_WARNINGS#include contact.hvoid menu()
{printf(**********************************************\n);printf(******** 1.add 2.del ********\n);printf(******** 3.search 4.modify ********\n);printf(******** 5.show 6.sort ********\n);printf(******** 0.exit ********\n);printf(**********************************************\n);}
int main()
{int input 0;Contact con;//初始化通讯录:模块化初始化InitContact(con);//只能传地址进行修改do{menu();printf(请选择》);scanf(%d, input);switch (input){case 1:AddContact(con);break;case 2:DelContact(con);break;case 3:SeachContact(con);break;case 4:ModifyContact(con);break;case 5:ShowContact(con);break;case 6:SortContact(con);break;case 0:DestroyContact(con);printf(退出通讯录\n);break;default:printf(选择错误\n);}} while (input);return 0;
}