【C++】内存管理+模板

前言:

本章将详细讲解C++内存管理和模板的实现。

第一部分我们讲解C++内存管理,C语言中有malloc/calloc/realloc等开辟空间和free释放空间,那么C++将符合实现呢?

第二部分我们会一起来初步认识模板与泛型编程,并详细探讨函数模板和类模板,为后续的学习做准备。

目录

(一)C++内存管理

(1)引入

(2)C++内存管理方式

2.1、new和delete操作内置类型

2.2、new和delete操作自定义类型

(3)operator new与operator delete函数

(4)总结

(5)定位new(了解)

(二)模板初阶

(1)泛型编程

(2)函数模板

2.1、隐式实例化

2.2、显式实例化

3.2、模板函数匹配原则

(3)类模板


(一)C++内存管理

(1)引入

有了前面C语言的学习,我们对于栈,堆,静态区等存放的数据也会有初步了解。下面我们通过一个题目来回忆一下栈,堆,静态区等存放的是哪些数据:

问题:

 

分析:

下面是按顺序作答:

  • 1、globalvar是全局变量,所以在静态区;
  • 2、staticGobalVar是全局的静态变量,所以在静态区;
  • 3、staticVar是静态变量,所以在静态区;
  • 4、localVar是int类型的变量,所以在栈;
  • 5、num1是数组名,也就是数组首元素的地址,也就是int*类型的变量,所以在栈;
  • 6、char2是数组名,同上,是局部变量所以在栈;
  • 7、char2是一个数组,把后面常量串拷贝过来到数组中,数组在栈上,所以*char2在栈上;
  • 8、 pChar3局部变量在栈区   *pChar3得到的是字符串常量字符在代码段;
  • 9、ptr1局部变量在栈区     *ptr1得到的是动态申请空间的数据在堆区
  • 图解:

 

答案:

 

 

说明:

  • 1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
  • 创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
  • 3. 用于程序运行时动态内存分配,堆是可以上增长的。
  • 4. 数据段--存储全局数据和静态数据。
  • 5. 代码段--可执行的代码/只读常量。

(2)C++内存管理方式

C语言内存管理方式在有些地方并不适用于C++,所以C++又提出了自己的内存管理方式:

通过new和delete操作符进行动态内存管理。

2.1、new和delete操作内置类型

new和delete的用法类似于malloc和free,我们来一起探究如何使用吧:

 分析:

 这里要注意的是:

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。
——————————————————————————

2.2、new和delete操作自定义类型

上面是最基本的内置类型的用法,下面我们来探讨new和delete操作自定义类型时的用法:
我们有如下的类:
下面我们来分别运行下面的几段代码:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	//A* p2 = new A(1);
	free(p1);
	//delete p2;
    
}

这里我们发现并没有打印任何结果,下面我们把注释的代码解开,则会发现输出以下结果:

这就说明了new和delete分别调用了构造和析构函数。

我们再看下一段代码:


class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;

	return 0;
}

 这里也没有打印任何结果,但是上面不是才分析出new和delete会调用构造和析构函数吗?

这里是因为new int,其中int是内置类型,所以没有调用A的构造函数,这样一来,自然也不调用其析构函数。当是内置类型时,new和malloc,delete和free的作用几乎等同。

我们最后再看一段代码:


class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	
	A* p6 = new A[10];
	delete[] p6;
	return 0;
}

这里我们发现输出结果如下:

我们可以得出结论:

    new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
    还会调用构造函数和析构函数。

(3)operator newoperator delete函数

new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete
系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。
——————————————————————————————————
我们通过汇编代码可知(过程省略,汇编代码我们看不懂):
operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的
虽然内核也是通过malloc和free实现,但 最大的区别 就是申请失败是抛异常,而不是返回空指针。
(后续我们会讲解抛异常)
——————————————————————————————————
但是new和delete并不等同于operator new和operator delete。
实际上我们通过上面 自定义类型 已经可知:
其实
  • new=operator new(malloc)+调用构造函数;
  • delete=operator delete(free)+调用析构函数;
ps:对于内置类型来说没有什么区别。
样例:


class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
		_a = new int[4];
		top = 0;
		capacity = 4;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[]_a;
		top = 0;
		capacity = 0;
	}

private:
	int* _a;
	int top;
	int capacity;
};

int main()
{
		 //失败了抛异常
	int* p1 = (int*)operator new(sizeof(int*));

	// 失败返回nullptr
	int* p2 = (int*)malloc(sizeof(int*));
	if (p2 == nullptr)
	{
		perror("malloc fail");
	}

	 //申请空间 operator new -> 封装malloc
	 //调用构造函数
	A* p5 = new A;

	// 先调用析构函数
	// 再operator delete p5指向的空间
	// operator delete -> free
	delete p5;

	// 申请空间 operator new[] ->perator new-> 封装malloc
	// 调用10次构造函数
	A* p6 = new A[10];
	
	// 先调用10次析构函数
	// 再operator delete[] p6指向的空间
	delete[] p6;


	int* p7 = new int[10];
	free(p7);  // 正常释放

	A* p8 = new A;
	//free(p8); // 少调用的析构函数
	delete p8;
	//对于无空间开辟的少调用析构函数可以正常运行


	//Stack st;

	Stack* pts = new Stack;
	//free(pts);//这样就少调用了析构函数,会造成内存泄漏
	delete pts;


	return 0;
}

大家可以一一调试试验,验证上面的结论。

(4)总结

对于内置类型:

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申
请空间失败时会抛异常,malloc会返回NULL。
对于自定义类型:

new的原理

  • 1. 调用operator new函数申请空间
  • 2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

  • 1. 在空间上执行析构函数,完成对象中资源的清理工作
  • 2. 调用operator delete函数释放对象的空间

new T[N]的原理

  • 1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  • 2. 在申请的空间上执行N次构造函数

delete[]的原理

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

(5)定位new(了解)

定义:
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:
 A(int a = 0)
 : _a(a)
 {
 cout << "A():" << this << endl;
 }
 ~A()
 {
 cout << "~A():" << this << endl;
 }
private:
 int _a;
};
// 定位new/replacement new
int main()
{
 // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
有执行
 A* p1 = (A*)malloc(sizeof(A));
 new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
 p1->~A();
 free(p1);
 A* p2 = (A*)operator new(sizeof(A));
 new(p2)A(10);
 p2->~A();
 operator delete(p2);
  return 0;
}

后面我们在做内存池项目时候会使用,这里大家了解即可。

(二)模板初阶

(1)泛型编程

我们以前在一个项目里写交换函数可能会利用函数重载实现不同类型的函数,如下:

void Swap(int& left, int& right)
{
 int temp = left;
 left = right;
 right = temp;
}
void Swap(double& left, double& right)
{
 double temp = left;
 left = right;
 right = temp;
}
void Swap(char& left, char& right)
{
 char temp = left;
 left = right;
 right = temp;
}

但是我们发现,他们的基本思想和实现是差不多的,甚至除了变量的类型,其他都一样。那么我们可不可以使用一个“模具”来代替呢???

答案是可以的!我们会利用泛型编程来实现。

那么什么是泛型编程呢?

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
这里我们就引入了模板。
模板分为函数模板和类模板,下文我们一一探讨。

 

上面交换函数的模板实现:
// 泛型编程 -- 模板

template<typename T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

(2)函数模板

函数模板格式:

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表)
{}

样例参照交换函数的模板实现。

注意:typename是用来定义模板参数关键字也可以使用class

2.1、隐式实例化

template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}
int main()
{
 int a1 = 10, a2 = 20;
 double d1 = 10.0, d2 = 20.0;
 Add(a1, a2);
 Add(d1, d2);
 
 Add(a1,d2);
 
}

其中Add(a1,d2)无法通过编译。因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
 编译器无法确定此处到底该将T确定为int 或者 double类型而报错
 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

我们可以采用两种处理方式:

  •  1. 用户自己来强制转化 ,如Add(a, (int)d);
  • 2. 使用显式实例化

2.2、显式实例化

我们上面的两种处理方式中提到了显式实例化,那么如何实现呢?

在函数名后的<>中指定模板参数的实际类型
例:
int main(void)
{
 int a = 10;
 double b = 20.0;
 
 // 显式实例化
 Add<int>(a, b);
 return 0;
}

ps:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错

3.2、模板函数匹配原则

  • 1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板数;
  • 2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数;
  • 3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

(3)类模板

定义格式:

template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
}; 
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
class Vector
{ 
public :
 Vector(size_t capacity = 10)
 : _pData(new T[capacity])
 , _size(0)
 , _capacity(capacity)
 {}
 
 // 使用析构函数演示:在类中声明,在类外定义。
 ~Vector();
 
 void PushBack(const T& data);
 void PopBack();
 // ...
 
 size_t Size() {return _size;}
 
 T& operator[](size_t pos)


 {
 assert(pos < _size);
 return _pData[pos];
 }
 
private:
 T* _pData;
 size_t _size;
 size_t _capacity;
};

// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
Vector<T>::~Vector()
{
 if(_pData)
 delete[] _pData;
 _size = _capacity = 0;
}

 注意:

1、上述Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具;

2、类模板中函数放在类外进行定义时,需要加模板参数列表;

——————————————————————————————————————

类模板的实例化:
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
例:
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

感谢阅读,祝您学业有成!!

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

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

相关文章

微服务高级篇【1】之微服务保护

文章目录前言一 初识Sentinel1.1 雪崩问题1.2 解决方法1.3 小结1.4 服务保护技术对比1.5 Sentinel介绍1.6 Sentinel安装1.7 微服务整合Sentinel二 测试工具&#xff1a;Jmeter2.1 Jmeter安装和配置2.2 Jmeter快速入门2.2.1 设置中文语言2.2.2 设置Jmeter桌面快捷图标2.3 Jmeter…

已经提了离职,还有一周就走,公司突然把我移出企业微信,没法考勤打卡, 还要继续上班吗?...

黎明前的黑暗最容易出事&#xff0c;离职前的几天也最容易出幺蛾子&#xff0c;比如下面这位网友的遭遇&#xff1a;已经提了离职&#xff0c;还有一周就正式离职了&#xff0c;公司突然把我移出企业微信&#xff0c;没法考勤打卡了&#xff0c; 还要继续上班吗&#xff1f;该怎…

BGP小型实验

实验分析 1.主要考察的是对BGP配置的熟练 2.实验需要在R1与R5分别发布一条路由可以在BGP 中使用network 网段 掩码命令 3.R1与R2,R4与R5是EBGP&#xff0c;而R2,R3,R4是IBGP 实验操作 1.配置接口ip,与环回路由 以R1为例 2.AS内部需要实现非直连的建立是需要保证IBGP内部是通的所…

蓝桥杯30天真题冲刺|题解报告|第三十天

大家好&#xff0c;我是snippet&#xff0c;今天是我们这次蓝桥省赛前一起刷题的最后一天了&#xff0c;今天打了一场力扣周赛&#xff0c;前面3个题都是有思路的&#xff0c;第三个题只过了一半的案例&#xff0c;后面看完大佬们的题解彻悟&#xff0c;下面是我今天的题解 目录…

蓝桥杯备考

数论&#xff1a;判断素数&#xff0c;鸽笼定理&#xff0c;抽屉理论 注意事项&#xff1a; long类型的数后面要加L long s 2658417853L; 保留几位小数&#xff1a; System.out.printf(“%.2f”, arg); 四舍五入问题&#xff1a;比如保留两位小数&#xff0c;就在数的后面再…

java基础知识汇总

目录 1、Java基础语法 1、类型转换问题 1. 运算符 1.1 算术运算符&#xff08;理解&#xff09; 1.2 赋值运算符&#xff08;应用&#xff09; 1.3 自增自减运算符&#xff08;理解&#xff09; 1.4 关系运算符&#xff08;应用&#xff09; 1.5 逻辑运算符&#xff08…

【CSS】清除浮动 ④ ( 清除浮动 - 使用双伪元素清除浮动 | 代码示例 )

文章目录一、清除浮动 - 使用双伪元素清除浮动二、代码示例一、清除浮动 - 使用双伪元素清除浮动 为 .clearfix:before 和 .clearfix:after 并集选择器 , 设置如下样式 : /* 清除浮动 - 使用双伪元素清除浮动 */.clearfix:before,.clearfix:after {content: "";displ…

ERTEC200P-2 PROFINET设备完全开发手册(1)

本教程为ERTEC200P-2的基础开发教程&#xff0c;可以掌握PN设备开发的基本流程。虽然没有涉及PN协议的详细解析&#xff0c;但是希望根据本文档多多练习&#xff0c;熟能生巧&#xff0c;逐渐能够掌握PN设备开发。 &#xff08;注意&#xff1a;本手册基于西门子DEVKIT V47协议…

oracle导入的表中文名称乱码无法删除导致删除用户也失败

由于一开始弄数据库的时候忘记设置编码格式&#xff0c; 导致导入dmp文件之后带中文的表名变成了乱码 然后plsql右键删除表显示表不存在 一开始的时候寻思备份下表结构跟表数据 直接删除用户完事了 删除用户报递归遍历错误 寻思重装这个数据库太过于耗时 不值当的 就是看那几…

【JWT鉴权】如何来写一个token令牌认证登录?

目录一. &#x1f981; 话题引入1.2 什么是JWT&#xff1f;二. &#x1f981; 技术体现2.1 引入依赖2.2 编写JWT工具类2.3 编写登录方法2.4 编写JWT拦截器验证令牌2.5 编写要配置拦截的接口三. &#x1f981; 话题终结一. &#x1f981; 话题引入 在做项目过程中&#xff0c;我…

【halcon】为啥匹配到ROI外面去了?

背景 匹配到ROI外面去了 中心恰好在roi有效区域内&#xff01;&#xff08;粉色是ROI区域&#xff09; 网上查到的资料&#xff01; PaintRegion改变外部环境 //HOperatorSet.ReduceDomain(image, ho_ProductRegionAll, out imgReduced); //替换为&#xff1a; HObject all…

Web前端 HTML、CSS

HTML与CSSHTML、CSS思维导图一、HTML1.1、HTML基础文本标签1.2、图片、音频、视频标签1.3、超链接、表格标签1.4、布局1.5、表单标签1.6、表单项标签综合使用1.7、HTML小结二、CSS&#xff08;简介&#xff09;2.1、引入方式2.2、选择器2.3、CSS属性Web前端开发总览 Html&…

Linux基础操作 常用命令 Centos

Linux 1.Linux的引言 Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。伴随着互联网的发展&#xff0c;Linux得到了来自全世界软件爱好者、组织、公司的支持。它除了在服务器操作系统方面保持…

面试题-学习网络协议必备:七层模型与协议之间的映射关系

一、概念 OSI七层模型是计算机网络中的一种标准化分类和描述方式&#xff0c;它将网络协议划分为不同的层次&#xff0c;每个层次负责不同的功能。这种模型被广泛应用于网络设计、开发和维护&#xff0c;以便于不同系统之间的互操作性和相互通信。 二、各层介绍 第一层&#x…

vue项目代理配置大全

1.vite &#xff08;vue3-admin-element-template-master&#xff09; server: {host: 0.0.0.0, //服务器ip地址 port: 5566, //本地端口fs: {strict: false, // 支持引用除入口目录的文件},open: true, // 是否自动在浏览器打开proxy: {/pcapi: {target: https:....../pcapi…

老板想要可视化大屏?零代码开发3D可视化大屏,只要10分钟

上周末和供应链管理的朋友一起喝茶&#xff0c;我吐槽着我做报表的繁琐&#xff0c; 他用很疑惑的眼神看着我说&#xff1a;这事不是在模板上改改数据就行了吗&#xff1f;我连忙逼他给我分享了这个香饽饽。 回到家&#xff0c;我直接开始研究起了可视化大屏&#xff0c;后悔自…

Android告别Shape.xml

天下苦shape.xml久已&#xff0c;特别是遇上不靠谱的UI&#xff0c;圆角背景色边框&#xff0c;三个属性就能给你折腾出来几百种组合&#xff0c;每个组合都要写对应的shape.xml&#xff0c;太折腾人了&#xff01; 展示 效果图 代码 /*** 设置shape*/ BindingAdapter(// 圆角…

44学习自动化运维工具 Chef 的基本用法,包括厨师编写、节点管理

Chef是一种自动化运维工具&#xff0c;它允许您定义基础设施的状态&#xff0c;并根据需要管理这些状态。在这里&#xff0c;我们将学习Chef的基本用法&#xff0c;包括如何编写和管理Cookbook和Node。 安装Chef 在使用Chef之前&#xff0c;您需要在管理节点和目标节点上安装…

图最短路径算法

图最短路径算法迪杰斯特拉算法弗洛伊德算法BFS迪杰斯特拉算法 求原点0到其他点的最短路径 #include<iostream> #include<vector> #include<string.h> #define N 10 #define INF 65535 using namespace std;int graph[N][N]; int dist[N]; int parent[N];/…