【C++初阶】内存管理 初识模板

目录

  • 一、C/C++内存分布
  • 二、C/C++动态内存管理方式
    • 2.1 new和delete的用法
    • 2.2 new与malloc、delete与free比较
    • 2.3 较复杂场景分析
  • 三、operator new与operator delete函数
  • 四、 new和delete的实现原理
  • 五、初识模板
    • 5.1 泛型编程
    • 5.2 函数模板
      • 5.2.1 概念
      • 5.2.2 写法
      • 5.2.3 不同类型时使用函数模板
      • 5.2.4 函数模板实例化
      • 5.2.5 函数模板匹配调用原则
    • 5.3 类模板

一、C/C++内存分布

C/C++的内存分布主要分为栈区、堆区、数据段和代码段,还有内存映射段。

栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
堆用于程序运行时动态内存分配,堆是可以上增长的。
数据段–存储全局数据和静态数据
代码段–可执行的代码/只读常量。

看以下代码:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}

有几个问题:

1.char2在哪里 2.*char2在哪里
3.pChar3在哪里 4.*pChar3在哪里
5.ptr1在哪里 6.*ptr1在哪里


7.sizeof(num1) =
8.sizeof(char2) = ? 9.strlen(char2) = ?
10.sizeof(pChar3) = ? 11.strlen(pChar3) = ?
12.sizeof(ptr1) = ?

1.2 char2是定义在Test函数里的数组名,是局部变量,在栈区;* char2对数组解引用得到数组的地址是字符串,但是是把字符串的内容拷贝到数组的地址去,所以 * char2 在栈区
3.4 pChar3是定义在Test函数里的指针,是局部变量,在栈区;* pChar3对指针进行解引用,得到字符串,字符串在代码段
5.6 ptr1是定义在Test函数里的整型指针,是局部变量,在栈区;* ptr1解引用得到的地址是malloc函数开辟的空间,在堆区
7. sizeof计算的是整个数组的大小,以字节为单位,数组里有10个元素,一个元素4个字节,所以答案是40
8. sizeof计算的是字符串的大小,注意包括后面的斜杠0,所以答案是5
9. strlen计算的是字符串的大小,不包括后面的斜杠0,所以答案是4
10. sizeof只关注类型,pChar3是指针,所以答案是4/8
11. strlen计算的是字符串的大小,不包括后面的斜杠0,所以答案是4
12. ptr1是整型指针,所以答案是4/8

一张图表示:
在这里插入图片描述

二、C/C++动态内存管理方式

C语言中动态开辟的方式有malloc、calloc和realloc,常用的是malloc函数,释放空间的方式是free。但是,这两种方式有一些局限性,所以C++有新的动态开辟管理方式,通过new和delete操作符进行动态内存管理。

2.1 new和delete的用法

先来作下对比:

int* a = (int*)malloc(sizeof(int));
///
int* a = new int;

new不需要我们自己写sizeof计算字节大小,是不是看起来更简洁了。

delete a;
///
free(a);

delete和free差不多,注意释放空间完要置空。

1️⃣new初始化元素
在后面加个括号,括号里就是要初始化的值

int* a = new int(4);//初始化为4

在这里插入图片描述

2️⃣new创建元素个数
在后面加个方括号,方括号里就是元素的个数

int* a = new int[5];

在这里插入图片描述

3️⃣new创建元素个数并初始化
在2️⃣的基础上后面加个花括号,花括号里面分别为初始化的值。如果初始化的数量小于元素个数,补0

int* a = new int[5]{ 1,2,3 };

在这里插入图片描述
4️⃣delete单个元素时

delete a;

5️⃣delete多个元素时

delete[] a;

注意:
申请单个元素空间与释放单个对象的new和delete要搭配使用;申请多个元素空间与释放多个对象的new[]和delete[]要搭配使用

2.2 new与malloc、delete与free比较

共同点:
都是从堆上申请空间,并且需要用户手动释放。

不同点:
1️⃣malloc只开辟空间大小,不能初始化;new就既可以开辟空间的大小,也可以对空间进行初始化。 前面代码就用演示。

2️⃣自定义类型初始化问题
当对象是自定义类型时,malloc不方便自定义类型初始化,因为malloc只会开辟空间,free只会释放空间。new会先开空间,再调用构造函数;delete会先调用析构函数,再释放空间。

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* aa = new A[3]{ 11,22,33 };
	delete aa;
	return 0;
}

在这里插入图片描述

同时new可以在开空间时完成初始化

在这里插入图片描述

补充:
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
malloc和free是函数,new和delete是操作符

2.3 较复杂场景分析

看以下代码:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* st = new Stack;
	delete st;
	return 0;
}

这段代码main函数里有一个new和delete,构造函数里也有一个new,析构函数也有一个delete,那么它们之间的关系是怎样的
图示分析:
在这里插入图片描述
自定义类型先给对象开辟空间(对象个数问题后面分析),再调用构造函数,构造函数里给_a数组开辟空间,因为_a是内置类型,所以这里的处理方式与malloc相同。delete对象,先调用析构函数,清理_a,再释放空间,此时清理的是对象。

注意,有的人乱着用把delete st写成free st,free只会释放空间不会调用析构函数,它释放了对象的空间,但是对象的空间里的_a指向的空间没有被释放掉,是不是就变成野指针了,所以不可free和delete混着用。

三、operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

先来看一段汇编代码:
在这里插入图片描述

operator new 实际是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。 所以,operator new 和operator delete可以说是malloc和free的封装

四、 new和delete的实现原理

1️⃣对于内置类型,new和malloc,delete和free基本类似。有一点不同,只有单个对象时,匹配的两者是new/delete;多个对象时,匹配的两者是new[]和delete[],跟数组一样,也是连续的空间。要注意的是new在申请空间失败时会抛异常,malloc会返回NULL。

2️⃣对于自定义类型,只有单个对象时,就是前面一段代码的例子,这个就不讨论了。下面来看看多个对象的情况:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* st = new Stack[10];
	delete[] st;
	return 0;
}

在这里插入图片描述

new []的原理:
1.调用operator new[]函数,在operator new[]中实际调用operator new函数完成10个对象空间的申请
2.在申请的空间上执行10次构造函数
delete[]的原理:
1.在释放的对象空间上执行10次析构函数,完成10个对象中资源的清理
2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

在这里插入图片描述
一个栈对象有3个内置类型(int* int int),共12个字节,那么10个栈对象总共有120个字节,但是事实是这样的吗?
在这里插入图片描述
打开内存查看:
在这里插入图片描述
在st指向的地址处前面有4个字节存储的是对象的个数
在这里插入图片描述

如果把delete 后面的方括号去掉会怎样:

	Stack* st = new Stack[10];
	delete st;

在这里插入图片描述

程序运行崩溃了,那原因是什么呢?先来看以下代码:

以下是一个A类,私有成员变量只有一个整型,先把析构函数注释了,看会发生什么:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
	/*~A()
	{
		cout << "~A()" << endl;
	}*/
private:
	int _a;
};
int main()
{
	A* aa = new A[10];
	delete aa;/// 把方括号去掉
	return 0;
}

在这里插入图片描述
程序正常运行。如果不注释析构函数会怎样:
在这里插入图片描述
程序运行崩溃了。

为什么这个A类注释不注释析构函数有区别?

没有注释析构函数,会去调用这个析构函数,调用析构函数,aa指向下图的位置
在这里插入图片描述
前面红色的区域是存储对象个数的。我们知道delete[]对应匹配的是new[],而delete对应匹配的是new,因为delete后面没有方括号,所以对应使用的new开空间时就不需要前面的4个字节来存储对象的个数(1个对象没啥好记录个数的),因此aa指针指向如下图所示。
在这里插入图片描述

这样的话释放空间如下图:
在这里插入图片描述
释放的空间是蓝色区域,但是前面的红色的区域也必须要释放,不可以只释放部分,所以释放空间位置不对导致运行崩溃。

有注释析构函数,虽然编译器可以自动调用默认生成的析构函数,但是这取决于编译器。因为A类里面只有内置类型(一个整型),没有申请空间,所以可以不做处理。不做处理就不调用析构函数,没有析构函数也就没有前面的位置存储对象个数,释放的位置就不会只释放部分。

总结:new[]与delete[]和new与delete要匹配使用,不能混。

五、初识模板

5.1 泛型编程

概念:泛型编程是一种无具体类型的通用代码,可以实现不同类型时的复用。模板是泛型编程的基础,模板分为函数模板和类模板。

5.2 函数模板

5.2.1 概念

函数模板是一种通用的函数,通过实参的类型推出相应类型的函数,实现函数调用。

5.2.2 写法

关键字templete
格式:templete < typename T1,typename T2… >,T是类型
typename 也可以是class

一个交换函数的模板:

template <class T>
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}

实现两个数的交换:

int main()
{
	int a = 1;
	int b = 3;
	cout << a << endl << b << endl;
	Swap(a, b);//a和b传进去,编译器推出类型,T就变成了int类型
	cout << a << endl << b << endl;
	return 0;
}

运行结果:
在这里插入图片描述

5.2.3 不同类型时使用函数模板

1️⃣调用两次函数,函数实参的类型不同,一个是int,另一个是double,这两次调用都使用了函数模板,但是这两次调用的不是同一个函数。

通过汇编查看:
在这里插入图片描述
2️⃣多模板参数打印
想同时打印不同的类型可以使用多个模板参数

template <class T1, class T2>
void Print(T1& a, T2& b)
{
	cout << a << endl << b << endl;
}
int main()
{
	int a = 33;
	double b = 1.22;
	Print(a, b);
	return 0;
}

a和b的类型由直接定,反正传过去什么,它就打印什么
在这里插入图片描述

3️⃣加法函数

代码:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 2;
	int b = 6;
	cout << Add(a, b) << endl;

	return 0;
}

运行结果:
在这里插入图片描述
如果把b的类型换成double:
在这里插入图片描述
编译器报错了,说明不可以两个类型不同,不然的话就不确定应该使用哪种类型了。

5.2.4 函数模板实例化

1️⃣隐式实例化
通过实参的类型编译器自动推演出相应的函数模板参数类型叫做隐式实例化。

加法函数:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	int b = 2;
	int c = Add(a, b);
	cout << c << endl;
	return 0;
}

在这里插入图片描述

T推出为int类型。

如果其中一个参数的类型不一样,编译器会报错,这里有两种办法解决。

强制类型转换:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	double b = 2.1;
	
	cout << Add(a, (int)b) << endl;
	return 0;
}

在这里插入图片描述
另一种是显示实例化

2️⃣显示实例化

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	double b = 2.1;
	
	cout << Add<int>(a, b) << endl;
	return 0;
}

在函数名后的<>中指定模板参数的实际类型
在这里插入图片描述
一般情况下使用函数模板隐式实例化就够了,但是有些情况必须使用显示实例化

template <class T>
T* func()
{
	return new T[3];
}
int main()
{
	func<int>();
	return 0;
}

没有传参数,T就不知道应该变成什么类型,所以要显示实例化来确定T的类型。

5.2.5 函数模板匹配调用原则

当代码中既有普通函数,又有函数模板,那么调用函数时会使用哪个呢?

//函数模板
template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
//普通函数
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 1;
	int b = 3;
	cout << Add(a, b) << endl;

	return 0;
}

打开调试:
在这里插入图片描述
变量a和b定义的时候都是int类型,普通函数的参数类型也是int,所以有现成的就用现成的。

现在把a和b的类型改变一下。

	double a = 1.1;
	double b = 3.3;

调试:
在这里插入图片描述
如果没有现成的普通函数,就使用合适的,函数模板参数类型为double

5.3 类模板

类模板与函数模板差不多,但是要注意的点:必须显示实例化

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack<int> st1;
	Stack<double> st2;
	return 0;
}

这样才能确定类中成员变量的类型。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/160150.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

excel怎么能锁住行 和/或 列的自增长,保证粘贴公式的时候不自增长或者只有部分自增长

例如在C4单元格中输入了公式&#xff1a; 现在如果把C4拷贝到C5&#xff0c;D3会自增长为D4&#xff1a; 现在如果想拷贝的时候不自增长&#xff0c;可以先把光标放到C4单元格&#xff0c;然后按F4键&#xff0c;行和列的前面加上了$符号&#xff0c;锁定了&#xff1a; …

MySQL优化(1):B+树与索引

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 对于60%的程序员而言&a…

【智能家居】4、智能家居框架设计和代码文件工程建立

目录 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 SourceInsight创建新工程步骤 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 创建一个名为si的文件夹用于保存SourceInsight生成的文件信息&#xff0c;然后在SourceInsig…

.Net6 部署到IIS示例

基于FastEndpoints.Net6 框架部署到IIS 环境下载与安装IIS启用与配置访问网站 环境下载与安装 首先下载环境安装程序&#xff0c;如下图所示,根据系统位数选择x86或者x64进行下载安装,网址&#xff1a;Download .NET 6.0。 IIS启用与配置 启用IIS服务 打开控制面板&#xff…

学习css过渡动画-transition

文章目录 前言transition属性语法宽度改变效果透明度改变效果位置改变效果如有启发&#xff0c;可点赞收藏哟~ 前言 通常&#xff0c;当一个元素的样式属性值发生变化时&#xff0c;会立即看到页面发生变化。 css属性transition能让页面元素不是立即的、而是慢慢的从一种状态变…

【Proteus仿真】【STM32单片机】公交车报站系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD12864显示模块、DS18B20温度传感器、DS1302时钟模块、按键、LED蜂鸣器、ULN2003、28BYJ48步进电机模块等。 主要功能&#xff1a; 系统运行…

2022年6月 电子学会青少年软件编程 中小学生Python编程 等级考试一级真题答案解析(选择题)

2022年6月Python编程等级考试一级真题解析 选择题(共25题,每题2分,共50分) 1、在Python编辑器中写好程序代码后,在Run菜单中,下列哪个命令可以用来执行程序 A、Check Module B、Run Module C、Python shell D、任意一个都可以 答案:B 考点分析:考查python编辑的…

表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信你对这篇博客也感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;表白墙/留言墙初级Spring Boot项目&#xff08;此篇博客的简略版&#xff0c;不带MyBatis数据库开发&#xff09; 目录 1、项目前端页面及项目…

ER 图是什么

文章目录 前言什么是 ER图ER 图实例简化的 ER 图总结 前言 产品经理在梳理产业业务逻辑的过程中&#xff0c;非常重要的一项工作就是梳理各个业务对象之间的关系。如果涉及对象很对的时候&#xff0c;没有工具支持的话很难处理清楚。今天我们就来介绍一个梳理业务对象关系的工…

前置语音群呼与语音机器人群呼哪个更好

最近通过观察自己接到的营销电话&#xff0c;通过语音机器人外呼的量应该有所下降。同时和客户交流获取到的信息&#xff0c;也是和这个情况类似&#xff0c;很多AI机器人群呼的量转向了OKCC前置语音群呼。询问原因&#xff0c;说是前置语音群呼转化更快&#xff0c;AI机器人群…

头歌 MySQL数据库 - 初识MySQL

本章内容是为了完成老师布置的作业&#xff0c;同时也是为了以后考试的时候方便复习。 数据库部分一条一条的写&#xff0c;可鼠标手动粘贴&#xff0c;除特定命令外未分大小写。 第1关&#xff1a;创建数据库 在操作数据库之前&#xff0c;需要连接它&#xff0c;输入命令&a…

《Deep learning for fine-grained image analysis: A survey》阅读笔记

论文标题 《Deep learning for fine-grained image analysis: A survey》 作者 魏秀参&#xff0c;旷世研究院 初读 摘要 细粒度图像分析&#xff08;FGIA&#xff09;的任务是分析从属类别的视觉对象。 细粒度性质引起的类间小变化和类内大变化使其成为一个具有挑战性的…

一起学docker系列之五docker的常用命令--操作容器的命令

目录 前言1 启动容器2 查看容器3 退出容器4 启动已经停止的容器5 重启容器6 停止容器7 删除已经停止的容器8 启动容器说明和举例9 查看容器日志10 查看容器内运行的进程11 查看容器内部细节12 进入正在运行的容器并进行交互13 导入和导出容器结语 前言 当涉及到容器化技术&…

对话芯动科技 | 助力云游戏 4K级服务器显卡的探索与创新

2021年芯动科技推出了基于IMG BXT GPU IP的风华1号显卡。单块风华1号显卡可在台式机和云游戏中实现4K级别的性能&#xff0c;渲染能力达到5 TFLOPS&#xff0c;如果在服务器中同时运行两块显卡&#xff0c;性能还可翻倍。该显卡是为不断扩大的安卓云游戏市场量身定制的&#xf…

代码随想录算法训练营第三十八天【动态规划part01】 | 动态规划理论基础、509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

动态规划理论基础 什么是动态规划 动态规划 (Dynamic Programming, DP)&#xff0c;是求解决策过程最优化的过程。 如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪…

Spring IOC - 推断构造方法

一、前言 上文解析了Bean生命周期的实例化阶段&#xff0c;其中bean真正开始实例化的核心代码位于方法AbstractAutowireCapableBeanFactory#createBeanInstance中&#xff0c;这里也是spring推断构造方法的核心所在。 二、整体介绍 首先看下方法的源码及注释如下&#xff0c;下…

“具有分布式能源资源的多个智能家庭的能源管理的联邦强化学习”文章学习四——基于联邦深度学习的多智能家居能源管理

一、用于家庭能源管理的FRL算法 在本节中&#xff0c;我们将阐述提出的FRL算法&#xff08;算法1&#xff09;&#xff0c;该算法以分布式方式调度多个智能家庭的能量消耗。在提出的FRL框架中&#xff0c;LHEMS和GS相互迭代并有效训练LHEMS的模型。我们考虑了由LHEMS控制的空调…

vivado产生报告阅读分析7-时序报告3

1、“ Timing Summary Report ”详情 “ Timing Summary Report ” &#xff08; 时序汇总报告 &#xff09; 包含下列部分 &#xff1a; • “ General Information ”部分 • “ Timer Settings ”部分 • “ Design Timing Summary ”部分 • “ Clock Summary ”部…

公网使用PLSQL远程连接Oracle数据库【内网穿透】

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

Linux常用命令——bzcat命令

在线Linux命令查询工具 bzcat 解压缩指定的.bz2文件 补充说明 bzcat命令解压缩指定的.bz2文件&#xff0c;并显示解压缩后的文件内容。保留原压缩文件&#xff0c;并且不生成解压缩后的文件。 语法 bzcat(参数)参数 .bz2压缩文件&#xff1a;指定要显示内容的.bz2压缩文…