C++内存管理和模板

news2024/12/24 2:41:38

目录

内存管理

new T[N]

 new和delete关键字的总结:

定位new表达式(placement-new):

作用:

使用格式:

 使用场景:

  实例:

调用析构函数的两个方法:

池化技术:

面试题:c语言的动态内存申请和c++的有什么不同

1:本身存在不同:

2:申请失败的结果不同:

3:是否初始化

4:是否需要计算空间的大小

5:返回值不同:

6:对于自定义类型的处理不同:

内存泄露:

 内存泄露是指针丢了还是内存丢了?

常见的内存泄露的情况:

模板:

泛型编程:

定义:

 写法结构:

实例:

函数模板的实例化:

显示实例化:

模板可以搞多个参数

通用和自定义函数同时出现优先自定义:

 实例化栈:

静态数组:


内存管理

new T[N]

 举一个例子:

#include<iostream>
using namespace std;
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 = new A;
	A*p2 = new A[10];
	return 0;
}

我们写一个简单的类A,类A中只有一个成员变量_a和一个构造函数以及一个析构函数,当调用构造函数和析构函数时会出现提示。

接下来,我们进行调试,转到反汇编:

 我们用new申请了一个对象的空间,只调用了两个函数:一个是operator new函数,这个函数内部和malloc函数非常相似,不同点在于operator函数申请空间失败的时候会抛异常。

第二个函数是构造函数。

总结:对于自定义类型,new申请空间首先会调用operator new函数,然后调用构造函数。

对于

 我们来查看对应的反汇编:

 这里同样也是调用了两个函数。

第一个函数operator new[],这个函数又相当于我们调用了10次operator new[]函数,完成申请十个对象。

第二个函数相当于迭代调用构造函数,

 这里的vector表示的是顺序表,这里有迭代的意思。

总结:operator new[]:相当于调用了多次operator new函数,并调用了多次构造函数。

 new和delete关键字的总结:

定位new表达式(placement-new):

作用:

 定位new表达式的作用是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

 使用场景:

  实例:

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 = new A;
	/*A*p2 = new A[10];*/
	A*p3 = (A*)malloc(sizeof(A));
	if (p3 == nullptr)
	{
		perror("malloc fail");
		exit(-1);
	}
	new(p3)A(1);
	return 0;
}

我们知道,类的构造函数一般不能主动调用,一般是对象在创建的时候自动进行调用,但是这里我们是通过malloc申请的空间,malloc申请空间是不会调用构造函数的。

p3指向我们申请的空间,假如我们想要对动态申请的对象调用构造函数,这里就用到了定位new表达式:

new(p3)A(1);

这串代码可以这样翻译:对动态申请的对象p3调用构造函数,初始化的值为1.

我们进行调试,查看是否调用完成构造函数:

 调用完成了构造函数。

调用析构函数的两个方法:

方法1:直接使用delete,例如:

delete p3;

我们进行编译,假如打印了~A()表示我们调用了析构函数。

方法1是可行的。 

方法2:通过p3来调用析构函数。

举一个例子:

new(p3)A(1);
	p3->~A();

我们可以这样写,进行编译:

 成功调用了析构函数。

池化技术:

 假如我们不断使用malloc和new在堆上申请空间(类比每一次都去水井取水),但是我们想要高效率的申请空间,我们可以准备一个内存池(为了取水方便,我们在家里使用一个蓄水池),当我们想使用堆上的空间,池中的内存就足够我们使用(当我们想要使用水,使用家里蓄水池的水即可),但是内存池的内存没有调用构造函数进行初始化(自家的蓄水池中的水不干净),所以我们就是用定位new的方法来初始化内存(用净水器的方法过滤家里蓄水池中的水)。

总结:定位new表达式的作用就是对已经申请过的没有调用构造函数初始化的动态对象进行初始化。

对于将来要学习的内存池,我们的定位new也能够对内存池哪些没有初始化的内存进行初始化。

面试题:c语言的动态内存申请和c++的有什么不同

1:本身存在不同:

c++的new和delete是操作符,而c语言的malloc和free是函数不同。

2:申请失败的结果不同:

c语言申请失败后返回空指针,c++申请失败后抛异常

3:是否初始化

malloc申请的对象不会被初始化,而new申请的对象可以被初始化。

4:是否需要计算空间的大小

malloc申请时需要我们自己计算所要申请空间的大小,而new申请时,我们只需要输入申请的对象和对象的个数即可。

5:返回值不同:

malloc申请对象的返回值是void*,而new申请对象不需要返回值,因为new后面直接跟的是类的类型。

6:对于自定义类型的处理不同:

对于自定义类型,new申请动态对象(delete释放空间)时,会调用对象的构造函数(对象的析构函数),而malloc则只会申请空间。

内存泄露:

 内存泄露是指针丢了还是内存丢了?

答:内存泄露的本质上是指针丢了,失去了指针,我们就找不到那部分没有释放的空间,就造成了内存泄露。

注意:只要程序正常结束了,内存就被回收了,就不存在内存泄露了。

常见的内存泄露的情况:

void MemoryLeaks()
{
	//1:内存申请忘记了释放
	int *p1 = (int*)malloc(sizeof(int));
	int*p2 = new int;

	//2:异常安全问题
	int *p3 = new int[10];
	Func();//这里Func()函数抛异常导致delete [] p3未执行,p3未被释放。
	delete[] p3;
}

模板:

先举例:

void Swap(int &left, int&right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	double c = 1.1, d = 2.2;
	Swap(c, d);
	return 0;
}

如图所示:我们只定义了一个整型的交换函数,对于c语言来说,假如我们想要调用浮点型类型的数据的交换,我们需要额外写一个不同命的函数,例如:

void Swapd(double &left, double&right)
{
	double tmp = left;
	left = right;
	right = tmp;
}
void Swapi(int &left, int&right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swapi(a, b);
	double c = 1.1, d = 2.2;
	Swapd(c, d);
	return 0;
}

对于c++来说,我们方便了一些,我们可以写函数重载:

void Swap(double &left, double&right)
{
	double tmp = left;
	left = right;
	right = tmp;
}
void Swap(int &left, int&right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	double c = 1.1, d = 2.2;
	Swap(c, d);
	return 0;
}

但是无论是c语言还是c++,我们要实现几种类型数据的交换,我们就需要写几个函数,无论是否重载。

现在我们可以使用模板来让编译器替我们定义函数。

泛型编程:

定义:

 写法结构:

 这几种写法都是正确的,相同的。

实例:

template<typename T>
void Swap(T &left, T&right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	double c = 1.1, d = 2.2;
	Swap(c, d);
	return 0;
}

我们进行调试:

 我们可以发现,我们分别完成了整型数据的交换和浮点型数据的交换。

问题1:我们这里写的Swap调用的是下面这个函数吗?

答:并不是,首先,我们的函数开辟的栈帧带线大小就不同,因为对于字符类型的交换函数,一个字符对象只占一个字节,对于整型类型的交换函数来说,一个整型对象占4个字节,所以整型的交换函数天然的就比字符类型的交换函数调用时所开辟的栈帧空间大。

函数调用时开辟的栈帧空间不同,所对应的函数就不同,所以我们肯定不会调用这个函数。

具体的过程是这样的:

上面是我们写的一个模具,假如我们在后面调用了int double类型的交换函数,那编译器就会自定义int double类型的函数来供我们使用。

我们来查看反汇编:

 并且我们调用的函数的地址也不相同,所以我们调用的一定不是同一个函数。

函数模板的实例化:

如何实例化对象?

答:根据我们的需求,假如我们调用了int类型的交换函数,我们就实例化int的交换函数。

我们进行证明:


template<typename T>
void Swap(T &left, T&right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	int c=2, d = 1;
	Swap(a, b);
	return 0;
}

 我们转到反汇编:

 我们调用的函数是相同的。

能不能实现不同类型的数据的交换:

答:我们进行尝试:

template<typename T>
void Swap(T &left, T&right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int x = 1, y = 2;
	double c = 1.1, d = 2.2;
	Swap(x, c);
}

如图所示:

我们的x和c是不同类型的。

我们进行编译:

 报错。

原因是什么?

答:因为假如我们传参传了一个整型和浮点型,我们的T类型具体对应的是哪个类型不清楚,所以就会报错。

这里可能发生类型转换吗?

答:不会,只有在传参和赋值的时候才会产生类型转换,我们这里只有类型推断,所以不能发生类型转换。

 我们再退出一个问题,以下代码可以调用吗?

void Swap(double &left, double&right)
{
	double tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1;
	double c = 2.0;
	Swap(a, c);
}

不可以调用,原因如下:

答:这里是传参没错,传参或赋值的时候会产生类型转换,会产生临时变量,这个临时变量具有常性,所以我们无法用引用接收常数,即使我们用const修饰参数,因为我们的函数是实现交换,所以两个参数值一定发生变化,用const修饰就不能改变了,产生矛盾。

第一种写法:

template<class T>
T Add(const T&left, const T&right)
{
	return left + right;
}
int main()
{
	int a = 1;
	double c = 2.0;
	cout << Add((double)a, c) << endl;
}

我们可以采用强制类型转换的方法:

我们进行调用:

 实现了相加。

这里去掉了const就会报错,原因如下

答:a强制类型转换出来的对象具有常性,引用是无法接收常数的。

 上面的这些都是自动推演实例化。

显示实例化:

例如:

template<class T>
T Add(const T&left,const  T&right)
{
	return left + right;
}
int main()
{
	int a = 1;
	double c = 2.0;
	cout << Add<double>(a,c) << endl;
}

我们在Add后面加上<类型>,这里就相当于隐式类型转化了,相当于把不是double类型的参数隐式雷西那个转换为double。

注意:显示实例化不会经过自动推演,因为我们已经规定了要调用的函数。

模板可以搞多个参数

例如:

template<class T1,class T2>
T1 Add(const T1& left, const T2& right)
{
	return left + right;
}
int main()
{
	int a = 10;
	double b = 20.1;
	cout << Add(a, b) << endl;
	return 0;
}

因为我们的模板有两个参数,所以我们可以接收两个不同类型的参数的函数的调用:

通用和自定义函数同时出现优先自定义:

例如:

int Add(int left, int right)
{
	return left + right;
}
template<class T>
T Add(T left, T right)
{
	return left + right;
}
int main()
{
	int a = 1, b = 2;
	cout << Add(a, b) << endl;
}

我们进行调试观察:

 我们发现当通用和自定义同时存在时,调用函数会使用自定义的。

假如我们不想调用自定义的,我们想调用实例化的:

我们可以这样写:

 我们这样写之后就会调用实例化了:

 实例化栈:

我们先写一个简写的栈:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (int*)malloc(sizeof(int)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
	// st2(st1)
	

	// st1 = st2;
	// st1 = st1;
	

	void Push(int x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

我们可以发现,我们输入到栈中的元素只会是整型,我们有没有办法输入其他数据类型?

答:我们进行尝试:

typedef int SLDateType;

我们先把int替换成为SLDataType,假如我们想要修改数据类型时,我们只需要修改这里的int即可。

假如我们想要输入浮点型数据:

 我们进行修改:

 我们进行调试:

 我们成功输入了浮点型数据。

但是typedef还有一些解决不了的问题:

例如:

假如我们创建两个栈对象,一个输入浮点型数据,一个输入整形数据,那我们就不能够完成需求。

所以,我们发现,我们这里使用typedef的作用是降低维护成本,而不是泛型。

想解决这个问题,我们有两种思路:

思路1:再创建一个不同名字的栈,对栈的内容进行修改,实现创建两个栈对象。

但是这种方法不够泛型,我们可以写一个更好的。

思路2:模具的方法:

template<typename T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = )" <<capacity<<endl;

		_a = (T*)malloc(sizeof(T)*capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}

		_top = 0;
		_capacity = capacity;
	}
	
	// st2(st1)
	

	// st1 = st2;
	// st1 = st1;
	

	void Push(T x)
	{
		// ....
		// 扩容
		_a[_top++] = x;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};

但是这里需要注意一个问题:

我们入栈的函数的参数最好用引用:因为对于内置类型,传值传参和传引用没有什么区别,但是对于自定义类型,传值传参就会调用拷贝构造,所以我们这里使用传引用传参。

又因为我们不会对x的值进行修改,所以我们可以在参数类型的上面加上const

我们如何进行初始化呢?

 我们知道,函数模板可以推演实例化,因为调用函数就会传递参数,编译器会根据参数的类型来推演对应的实例化,但是对于类模板,我们没有推演的时机。

所以我们可以用显示实例化的方法:

 那么,st1和st2对应的类是相同的类吗?

答:不是,这两个类不相同,原因是在这里:

 

 假如我们这里不是指针,而是T _a,实例化的不同就会导致T的不同,T的不同类的大小就不同,大小不同的类是不会相同的。

他们是一个模板实例化出来的

但是模板参数不同,他们就是不同类型。

那允许我们把st2赋给st1吗?

答:不允许,如图:

 总结:不仅有函数模板,同样也有类模板,不过类模板不能进行推演实例化,而只能显示实例化。

静态数组:

#define N 10
template<class T>
class Array
{
public:
	T& operator[](size_t i)
	{
		return _a[i];
	}
private:
	T _a[N];
};

在类Array中,有一个成员变量,该成员变量是一个数组,该成员变量的类型由T来控制,有一个成员函数,该成员函数是符号重载函数,表示把[]进行重载。

这里就表示显示实例化:我们的类型是int类型。

 

 表示对数组进行初始化,a1是Array类型的成员,所以a1[i]就会返回成员变量_a[i]的改变,从而完成初始化。

​​​​​​​总结:我们写类的目的是为了通过成员函数或者其他函数完成对类的成员变量进行操作,来获取我们想要的结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 分享 功能 用Unity Native Share Plugin 实现链接、图片、视频等文件的分享+ 安卓 Ios 都可以,代码图文详解

Unity 分享 功能 用Unity Native Share Plugin 实现链接、图片、视频等文件的分享 安卓 Ios 都可以&#xff0c;代码图文详解前言环境效果一、Unity Native Share Plugin导入1.下载2.导入二、案例1.分享文字1.脚本2.发包注意2.分享视频1.完善下刚才的脚本2.给复制按钮添加点击事…

【Linux】Linux环境搭建

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;Linux的介…

【QT 自研上位机 与 STM32F103下位机联调>>>通信测试-基础样例-联合文章】

【QT 自研上位机 与 STM32F103下位机联调>>>通信测试-基础样例-联合文章】1、概述2、实验环境3、联合文章&#xff08;1&#xff09;对于上位机&#xff0c;可以参照如下例子&#xff08;2&#xff09;对于下位机&#xff0c;可以参照如下例子4、QT上位机部分第一步&a…

python中os库的使用

目录介绍1 listdir(path: str)2 path.isdir(path: str)3 path.isfile(path: str)4 path.join(path: str, file: str)5 path.getsize(path: str)介绍 本博客记录python中os库的一些函数使用。 1 listdir(path: str) listdir()函数输入一个目录&#xff0c;返回该目录下的所有…

web前端 html+css+javascript游戏网页设计实例 (网页制作课作业)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

数字化浪潮下,低代码能否加速企业的数字化转型

随着加快建设数字中国的目标明确下来&#xff0c;市场上与数字化相关的企业都得到了极大鼓舞&#xff0c;这不仅意味着后续数字领域的加速发展&#xff0c;更是代表着数字化已经完全可以向各行各业拓展&#xff0c;大力推进数字化建设。数字中国也说明了数字化并不能只是限制在…

FastTunnel Win10内网穿透实现远程桌面

目录 一、需求 二、购买公网服务器 三、远程公网服务器 四、FastTunnel 的使用 1.下载 FastTunnel 2.启动服务器端 3.启动客户端 五、测试 六、安装服务 结束 一、需求 FastTunnel 简介 高性能跨平台内网穿透工具&#xff0c;使用它可以实现将内网服务暴露到公网供…

【数据结构与算法】时间复杂度和空间复杂度

✨ 个人主页&#xff1a;bit me ✨ 当前专栏&#xff1a;数据结构 &#x1f31f;每日一语&#xff1a;窗外有风景 笔下有前途 低头是题海 抬头是鹏程 时间复杂度和空间复杂度的认知&#x1f30e; 一. 如何衡量一个算法的好坏&#x1f319; 二. 算法效率&#x1fa90; 三. 时间…

Word处理控件Aspose.Words功能演示:在 Python 中将 TXT 文件转换为 PDF

各种人使用记事本以TXT格式记下重点或快速创建笔记。此外&#xff0c;TXT 文件用于在各种应用程序中存储纯文本。但是&#xff0c;由于记事本不提供高级功能&#xff0c;因此 TXT 文件通常会转换为PDF。为了以编程方式自动将 TXT 转换为 PDF&#xff0c;本文介绍了如何在 Pytho…

WEB API 接口签名sign验证入门与实战

目录参考什么是加解密加密方式分类对称加密技术非对称加密技术&#xff08;RSA加密算法&#xff09;&#xff08;数字证书&#xff09;场景1&#xff1a;公钥加密&#xff0c;私钥解密场景2&#xff1a;秘钥加密&#xff1a;数字签名&#xff0c;公钥解密&#xff1a;验证签名M…

从位运算理解位图

位图是一种较难理解的数据结构&#xff0c;想了解位图&#xff0c;我需要先温习一下基础&#xff0c;复习下一些二进制的知识 位运算 1个字节8个二进制位 二进制每逢二进一&#xff0c;下面是二进制对应的十进制转换方式 二进制十进制0000 00012^010000 00102^120000 00112…

用户故事地图怎么用?实践才能出真知

在产品设计和交互过程中&#xff0c;用户体验是一个非常重要的部分。 随着产品的逐渐完善&#xff0c;主创团队也需要通过各个维度来了解用户需求&#xff0c;完善用户的整体体验。在这里&#xff0c;我们经常用到的一个实践是用户故事地图。 一、用户故事地图是什么&#xff…

【Linux】指令及权限管理的学习总结

文章目录1️⃣ Linux的文件系统结构简介2️⃣ Linux下的基本指令1. ls2. pwd3. cd4. touch5. mkdir6. rm7. rmdir8. cp9. mv10. cat11. more12. less13. head14. tail15. find16. grep17. zip18. unzip19. tar20. uname3️⃣ Linux下的权限权限管理1. 文件的访问者分类&#xf…

【C++】多态

1.多态 1.1多态的概念&#xff1a; 多态&#xff1a;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。李明要吃饭&#xff0c;那就要吃早饭&#xff0c;午饭&#xff0c;晚饭&#xff0c;而不是一天只吃午饭这种单一…

Java多线程(4):ThreadLocal

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 为了提高CPU的利用率&#xff0c;工程师们创造了多线程。但是线程们说&#xff1a;要有光&#xff01;&#xff08;为了减少线程创建&#xff08;T1启动&#xf…

Synchronized底层核心原理

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中Synchronized锁的底层核心原理知识记录&#xff0c;由于篇幅原因&#xff0c;下篇文章将介绍各种锁的优化原理。 本篇文章记录的基础知识&#x…

vue3 异步组件

前端开发经常遇到异步的问题&#xff0c;请求函数&#xff0c;链接库&#xff0c;等&#xff0c;都有可能需要通过promise或者async await 来进行异步的一个封装。 异步组件也由此诞生&#xff0c;我用settimeout来模拟一个vue3的异步组件 异步的子组件 <template><…

spring框架源码十三、spring ioc高级特性-后置处理器

spring ioc高级特性-后置处理器BeanPostProcessor实例MyBeanPostProcessorapplication-context.xmlTestServiceImpl测试BeanFactoryPostProcessorspring提供了两种后置处理bean的扩展接口&#xff0c; 分别为BeanPostProcessor和BeanFactoryPostProcessor&#xff0c; BeanPos…

攻防世界WEB练习 | easyphp

目录 题目场景 代码分析 找到flag 题目场景 代码分析 if(isset($a) && intval($a) > 6000000 && strlen($a) < 3) isset&#xff1a;检查变量是否设置 intval&#xff1a;检查变量是否为int型 strlen&#xff1a;检查变量的长度 要求a存在且大于6…

Matlab之多平台雷达检测融合仿真(附源码)

此示例演示如何融合来自多平台雷达网络的雷达检测。该网络包括两个机载和一个地面远程雷达平台。中央跟踪器以固定的更新间隔处理来自所有平台的检测。这能够根据目标类型、平台机动以及平台配置和位置评估网络的性能。 一、定义中央跟踪器 将trackerGNN用作中央跟踪器&#…