IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言进阶——动态内存管理 -> 正文阅读

[C++知识库]C语言进阶——动态内存管理

作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:C语言从入门到进阶

语录:Stay hungry stay foolish

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册和我一起刷题吧?

文章目录

1. 为什么存在动态内存

2. 分配动态内存函数的介绍

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

3.2 对动态开辟空间的越界访问

3.3 对非动态开辟内存使用free释放

3.4 使用free释放一块动态开辟内存的一部分

3.5 对同一块动态内存多次释放

3.6 动态开辟内存忘记释放(内存泄漏)

4. 几个经典的笔试题

笔试题1?

笔试题2

笔试题3

笔试题4

5. C/C++程序的内存开辟

6. 动态通讯录


1. 为什么存在动态内存

我们经常用到的开辟内存方式有:

int a = 40;
int arr[40] = {0}; 

?上述开辟内存方式的特点:

1.开辟空间的大小是固定的

2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

有时候我们需要的空间大小是在程序运行时才能知道,上述方式满足不了要求,所以出现了动态内存的开辟

2. 分配动态内存函数的介绍

2.1 malloc

C语言提供了一个动态内存开辟的函数:?

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

如果开辟成功,则返回一个指向开辟好空间的指针

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定

如果参数size为0,malloc的行为是标准是未定义的,取决于编译器

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	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;
	}
	for (i = 0; i < 10; i++)
	{
	printf("%d ", *(p + i));
	}
	printf("\n");

	return 0;
}

?这里没有free,当程序退出的时候, 系统会回收该空间

2.2 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:?

void free (void* ptr);

free函数用来释放动态开辟的内存

如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的

如果参数ptr是NULL指针,则函数什么事都不做

malloc和free都声明在stdlib.h头文件中

int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	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;
	}
	for (i = 0; i < 10; i++)
	{
	printf("%d", *(p + i));
	}
    
    //释放
    free(p);
    p = NULL;

	return 0;
}

不用free函数释放空间会出现内存泄漏,free回收完系统空间时,p还是指向那块吧被释放的空间,为了避免出现野指针的问题,一定要将它置为空指针

2.3 calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0

与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)calloc(10,sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	/*int i = 0;
	for (i=0;i<10;i++)
	{
		*(p + i) = i;
	}*/
	int i = 0;
	for (i = 0; i < 10; i++)
	{
	printf("%d ", *(p + i));
	}
	printf("\n");
	free(p);
	p = NULL;

	return 0;
}

?所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务

2.4 realloc

realloc函数的出现让动态内存管理更加灵活

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下:

void* realloc (void* ptr, size_t size);

ptr是要调整的内存地址

size调整之后新大小

返回值为调整之后的内存起始位置

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc在调整内存空间的是存在两种情况:

情况1:

当是情况1?的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址,旧的地址和数据都会被自动释放

情况2:

当是情况2?的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	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+1;
	}
	//扩容
	//追加40个字节
	int *ptr = (int* )realloc(p, 80);

	if (ptr != NULL)
	{
		p = ptr;
	}
	//使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
	free(p);
    p = NULL;

	return 0;
}

注意:开辟多了会出现内存碎片,导致内存利用率下降,程序的效率也会下降?

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	*p = 20;
	free(p);
	p = NULL;
	return 0;
}

要判断是否为空指针,如果是空指针就是开辟内存失败,出现对空指针的解引用

3.2 对动态开辟空间的越界访问

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
	}
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		p[i] = i;
	}
	
	free(p);
	p = NULL;
	return 0;
}

?当i是10的时候越界访问

3.3 对非动态开辟内存使用free释放

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

free函数只能对在堆上开辟的动态内存进行释放

3.4 使用free释放一块动态开辟内存的一部分

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));
	}
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

?程序运行起来,p已经不指向最开始的地址,因此最后释放,也不会将动态开辟的内存全部释放

3.5 对同一块动态内存多次释放

int main()
{

	int* p = (int*)malloc(40);

	free(p);
	//.....
	free(p);

	return 0;
}

重复释放并且没有将p置为空指针,会报错

3.6 动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(40);
	//....
	int a = 0;
	scanf("%d", &a);
	//...
	if (a == 10)
		return;

	free(p);
	p == NULL;
}

int main()
{

	test();

	return 0;
}

当满足a==10,free是没有机会被执行的,并且函数结束就找不到该空间的地址了,也不会释放,内存出现泄漏

注意:忘记释放不再使用的动态开辟的空间会造成内存泄漏

动态开辟的空间一定要释放,并且正确释放

4. 几个经典的笔试题

运行Test 函数会有什么样的结果

笔试题1?

void GetMemory(char* p)
{ 
    p= (char*)malloc(100);
}
void Test(void)
{
 char* str = NULL;
 GetMemory(str); 
 strcpy(str, "hello world");
 printf(str);
}

解析:

笔试题2

char* GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void? Test(void)

{
    char*str = NULL;
    str = GetMemory();
    printf(str);

}

?解析:

笔试题3

void GetMemory(char** p, int num)
{
    *p= (char*)malloc(num);
}
void Test(void)
{ 
    char* str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

?分析:

笔试题4

void Test(void)
{ 
    char* str = (char*) malloc(100);
    strcpy(str, "hello");
    free(str);

if(str != NULL)
 { 
    strcpy(str,"world");
    printf(str);
 }
}

分析:

5. C/C++程序的内存开辟

6. 动态通讯录

动态通讯录:默认存放三个人的信息,不够则扩容,每次增加两个人的空间,在静态通讯录的基础上修改即可

参看静态通讯录

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX  10
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_Sz 3
#define INC_SZ 2
//类型的声明
// 
//只是一个人的信息
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[100];//存放人的信息
//	int count;//记录当前通讯录有多少人的信息
//}Contact;
// 
// 
//动态版本
typedef struct Contact
{
	PeoInfo* data;//存放人的信息
	int count;//记录当前通讯录有多少人的信息
	int capacity;//记录当前通讯录容量

}Contact;

//初始化通讯录函数
int InitContact(Contact *pc);

//增加联系人到通讯录
void addContact(Contact* pc);

//打印通讯录信息
void showContact(const Contact* pc);

//删除指定联系人
void delContact(Contact* pc);

//查找指定联系人
void SearchContact(Contact* pc);

//修改指定联系人
 void modifyContact(Contact* pc);

 //按照名字排序通讯录内容
 void sortContact(Contact* pc);
 //销毁通讯录
 void DestroyContact(Contact* pc);

//动态版本
int InitContact(Contact* pc)
{
	pc->count = 0;
	pc->data = (PeoInfo*)calloc(DEFAULT_Sz,sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		printf("InitContact:%s\n", strerror(errno));
		return 1;
	}
	pc->capacity = DEFAULT_Sz;
	return 0;
}

?实现增容功能:

//增容函数
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));
		}
		else
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("增容成功\n");
		}
	}
}


//动态版本
void addContact(Contact* pc)
{
	assert(pc);
	//增容
	CheckCapacity(pc);
	
	//添加信息
	printf("\n请输入名字:>");
	//每次放进去的信息都是放进data 下标为count的数组
	scanf("%s", pc->data[pc->count].name);

	printf("\n请输入年龄:>");
	//因为name是存放在数组中,数组名本身就是地址,不需要再取地址
	//这里的年龄是int 型变量,需要取地址
	scanf("%d", &(pc->data[pc->count].age));

	printf("\n请输入性别:>");
	scanf("%s", pc->data[pc->count].sex);

	printf("\n请输入电话:>");
	scanf("%s", pc->data[pc->count].tele);

	printf("\n请输入地址:>");
	scanf("%s", pc->data[pc->count].addr);

	pc->count++;
	printf("\n增加成功\n");

}

?free增容所开辟的空间:


//销毁通讯录
void DestroyContact(Contact* pc)
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
}

?“ 本期的分享就到这里了,?记得给博主一个三连哈,你的支持是我创作的最大动力!”

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-21 21:19:47  更:2022-07-21 21:19:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/11 0:41:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码