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

news2025/1/12 16:10:37

 

目录

一、内存管理

1.new与delete基本用法

(1) 内置类型

(2) 自定义类型

2.new, delete与malloc, free对比

(1) 内置类型

(2) 自定义类型

(3)综合特点

 3.new与delete的底层实现

 4. 定位new表达式

二、模板

1.引入机制

2. 基本使用

(1) 函数模板

①概念:

②格式:

③原理

 ④模板实例化

1)隐式实例化

2)显式实例化

 ⑤模板参数的匹配原则

(2) 类模板

①格式:

②模板实例化

③类模板的声明和定义分离


一、内存管理

C语言中对内存管理主要借助的是malloc,calloc,realloc,free这几个库函数

而C++中进行内存管理借助的是new 与 delete这两个库函数

new是用来动态申请内存空间的,相当于malloc或者calloc

delete是用来手动释放动态申请的内存空间的,相当于free

1.new与delete基本用法

(1) 内置类型

① 申请与释放动态申请单个元素的空间

#include<iostream>
int main()
{
	//只是开空间
	int* p1 = new int; //申请一个int大小的空间,返回该空间的起始地址
	char* p2 = new char;  //申请一个char大小的空间,返回该空间的起始地址

	//开空间+初始化
	int* p3 = new int(1); //申请一个int大小的空间,并初始化这块空间为1
	char* p4 = new char('w'); //申请一个char大小的空间,并初始化这块空间为'w'

	//销毁动态申请空间
	delete p1;
	delete p2;
	delete p3;
	delete p4;
}

② 申请与释放连续的空间

#include<iostream>
int main()
{
	//只是开空间
	int* p1 = new int[5]; //申请5个int大小的空间,返回该空间的起始地址
	char* p2 = new char[5];  //申请5个char大小的空间,返回该空间的起始地址

	//开空间+初始化
	int* p3 = new int[10]{1, 2, 3, 4, 5}; //申请5个int大小的空间,并初始化为1, 2, 3, 4, 5
	char* p4 = new char[5]{'a','b','c','d','e'}; //申请5个char大小的空间,并初始化这块空间为'a','b','c','d','e'

	//销毁动态申请空间
	delete[] p1;
	delete[] p2;
	delete[] p3;
	delete[] p4;
}

ps: 初始化时, [ ]里面写的是个数, ()里面写的是初始化内容

(2) 自定义类型

① 申请与释放动态申请单个元素的空间

#include<iostream>
class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};
int main()
{
	//只是开空间
	A* p1 = new A; //创建1个A类型大小的空间

	//开空间+初始化
	A aa1; //A类型创建出了aa1对象
	A* p2 = new A(aa1); //用aa1对象初始化申请的一个A类型大小的空间
	A* p3 = new A(A()); //匿名对象初始化申请的一个A类型大小的空间(用缺省值)
	A* p3 = new A(A(1)); //匿名对象初始化申请的一个A类型大小的空间(传实参)
	A* p4 = new A(1); //1隐式类型转换成A类型的数据去初始化一个A类型大小空间

	//销毁动态内存空间
	delete p1;
	delete p2;
	delete p3;
	delete p4;
}

② 申请与释放连续的空间

#include<iostream>
class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};
int main()
{
	//只是开空间
	A* p1 = new A[2]; //创建2个A类型大小的空间

	//开空间+初始化
	A aa1; 
	A aa2;
	A* p2 = new A[2]{ aa1,aa2 }; //用aa1,aa2对象初始化申请的2个A类型大小的空间
	A* p3 = new A[2]{A(1),A(2)}; //两个匿名对象初始化申请的2个A类型大小的空间
	A* p4 = new A[2]{1, 2}; //1, 2隐式类型转换成两个A类型的数据去初始化2个A类型大小空间

	//销毁动态内存空间
	delete p1;
	delete p2;
	delete p3;
	delete p4;
}

2.new, delete与malloc, free对比

(1) 内置类型

#include<iostream>
int main()
{
	//开辟5个int大小的空间
	int* p1 = (int*)malloc(sizeof(int) * 5);
	int* p2 =  new int[5];

	free(p1);
	delete p2;
}

①new与malloc,delete与free 功能上没有实质差异

②new比malloc更简洁: 

1). malloc需要用计算单个数据类型大小, 写进表达式,new不需要

2). malloc需要对返回值做强制类型转换,new不需要

③new在开空间的时候可以手动初始化,malloc不可以,即使是calloc也只是自动初始化成0

(2) 自定义类型

①new在申请空间时会调用构造函数,malloc不会,因此new可以在申请空间同时完成初始化, malloc无法初始化

②delete在释放空间时会调用析构函数,free不会,因此delet可以在释放空间同时可以完成对对象中资源的清理, 而free无法完成

#include<iostream>
using namespace std;
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* p1 = (A*)malloc(sizeof(A));
	free(p1);
	cout << "-----------" << endl;

	A* p2 = new A;
	delete p2;
}

ps: 对于动态申请的连续空间,new和delete会多次调用构造函数和析构函数

(3)综合特点

①malloc和free是函数,new和delete是操作符

②malloc开辟空间失败,会返回空指针,因此我们在使用malloc时需要判断返回值是否为空,而new不需要做检查,new开辟失败的话会直接抛异常

 

 3.new与delete的底层实现

new = 开辟空间 + 调用构造函数

delete = 调用析构函数 + 释放空间

而库中开辟空间和释放空间的实现是借助两个全局函数operator new 与 operator delete 完成的

而operator new本质是对 malloc的封装,不过是增加了一些机制,使得开辟失败能报异常

而operator delete本质是对 free 的直接封装

因此,operator new 和operaotor delete 使用起来和 malloc 与 free 是完全一样的

int main()
{
	int* p1 = (int*)operator new(sizeof(int) * 10);
	operator delete(p1);
}

 4. 定位new表达式

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

格式: new (place_address) type(initializer-list), place_address必须是一个指针, initializer-list是参数列表,  如果构造函数需要传参,则必须传参

#include<iostream>
using namespace std;
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* p1 = (A*)operator new(sizeof(A) * 10);
	//显式调用构造函数
	new(p1)A(10);

	//显式调用析构函数
	p1->~A();
	operator delete(p1);
}

有些场景下会出现内存空间分配了,但是没有初始化的场景(内存池),这时就需要用定位new表达式对内存进行初始化

二、模板

1.引入机制

两数交换,是我们经常碰到的一个需求,因此我们经常会把两数交换逻辑封装成一个函数

void Swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

但是我要交换的数据不是整形呢?是其他类型呢?再写一份太麻烦了,那我们typedef一下~

typedef int DataType;
void Swap(DataType& left, DataType& right)
{
	DataType tmp = left;
	left = right;
	right = tmp;
}

这时我们只需要把int改成其他类型就行了,但是我如果想同时交换整形数据和其他类型数据呢?就算typedef也只能调用函数去交换固定类型的数据呀,于是还是得有多份逻辑相同的交换函数~

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

还是太麻烦了,因此C++引入了模板,模板如同现实中的模板, 比如说数学书把数学公式给你了,要根据数学公式去计算具体的题目,你只需要把公式中的符号替换成具体数字就行了~

C++中的模板就是给了编译器一个模子,让编译器根据不同的类型利用该模子生成代码

2. 基本使用

模板分为函数模板与类模板

(1) 函数模板

①概念:

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参化,根据实参类型产生特定的类型模板

②格式:

方式1) 关键字template <class T1,class T2,  class T3 ··· >

方式2) 关键字template <typename T1,  typename T2, typename T3···>

其中T1, T2, T3都是模板参数

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

③原理

模板本身并不是函数,只是一个模子,具体使用时编译器会根据实参类型将模板推演成函数,去之执行相关代码,因此之前需要我们做的事情交给编译器去完成了

 ④模板实例化

用不同类型参数使用模板称为模板的实例化(对比类的实例化---通过类创建出具体对象)

1)隐式实例化

编译器自动根据实参推演模板参数的类型

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	//以下代码均为隐式实例化
	Add(1, 2); 
	Add(1.1, 2.2);

	Add(1.1, 2);//(×) 传递参数不一致时,会报错,因为T也不知道该实例化成哪种类型

	//强制类型转化同一类型
	Add((int)1.1, 2);
	Add(1.1, (double)2);
}
2)显式实例化

在函数名后的<>中手动指定模板参数的实际类型

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	//显式实例化
	Add<int>(1, 2.2);
	Add<double>(1, 2.2);
}

 ⑤模板参数的匹配原则

1) 一个非模板函数可以和同名函数模板同时存在,且函数模板还可以被实例化成这个非模板函数

#include<iostream>
using namespace std;
//非模板函数
void Swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
//函数模板
template<class T>
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap<int>(a, b); //函数模板会实例化成上面的非模板函数
}

2)对于非模板函数和同名函数模板,如果其他条件相同,在调用函数时会优先调用非模板函数;如果模板可以产生一个更好匹配的函数,那么选择模板

#include<iostream>
using namespace std;
//函数模板
template <class T1, class T2>
T1 Add(T1 left, T2 right)
{
	cout << "函数模板" << endl;
	return left + right;
}
//非模板函数
int Add(int left, int right)
{
	cout << "非模板函数" << endl;
	return left + right;
}
int main()
{
	Add(1, 2); //调用非模板函数
	cout << "------------" << endl;
	Add(1.1, 2); //函数模板实例化
}

3)模板函数不允许自动类型转化,但普通函数可以进行自动类型转换

void func(int a) 
{
	cout << a;
}
int main()
{
	func(1.1);//自动进行类型转换
}

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	Add(1.1, 2);//(×) 1.1不会自动转换成整形, 2也不会自动转换成浮点型
}

(2) 类模板

①格式:

与类函数是一样的,template <class T1, class T2, class T3···>

②模板实例化

类模板实例化需要在类模板名后面加上<>,将实例化类型写在<>即可

类模板名字不是真正的类,而实例化的结果才是真正的类

template<class T>
class Vector
{
public:
    Vector(size_t capacity = 10)
        : _pData(new T[capacity])
        , _size(0)
        , _capacity(capacity)
    {}

    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;
};
int main()
{
    //Vector是类名, Vector<int>才是类型
    Vector<int> v1;
    Vector<double> v2;
}

③类模板的声明和定义分离

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()
{
    delete[] _pData;
    _pData = nullptr;
}
int main()
{
    Vector<int> v;
    v.PushBack(1);
    v.PushBack(1);
    v.PushBack(1);
    v.PushBack(1);
    return 0;
}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

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

相关文章

【C语言】每日一题---1

大家好&#xff0c;我是苏貝&#xff0c;本篇博客是系列博客每日一题的第一篇&#xff0c;本系列的题都不会太难&#xff0c;如果大家对这种系列的博客感兴趣的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 下面代码的结果是&#xff1a; #include <…

203、仿真-基于51单片机6自由度机械手金属液体控制报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

2023国赛数学建模A题B题C题D题E题思路分析 2023全国大学生数学建模思路

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 全国大学生数学建模…

前端开发:数组对象判断重复的方法详解

前言 在前端开发过程中,关于数据处理是非常常用的操作,尤其是通过算法处理从后端获取的数据甚为重要。而且在前端开发中,两大类型的数据处理是必备的:数组和对象。与其说是数据处理,不如说是数组和对象的处理。实际开发中,关于数组数据的处理所占比例更高,尤其是涉及到表…

uniapp项目如何运行在微信小程序模拟器上

在HbuilderX中的小程序写完后自己一定要保存&#xff0c;否则会出不来效果 那么怎么让uniapp项目运行在微信小程序开发工具中呢 1 在hbuilderx中点击运行到小程序模拟器 2 然后在项目目录中会生成一个文件夹 在微信小程序开发软件中的工具>安全设置>打开端口 或者在微…

使用sqlplus连接oracle,提示ORA-01034和ORA-27101

具体内容如下 PL/SQL Developer 处 登录时 终端处 登录时 ERROR: ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist Process ID: 0 Session ID: 0 Serial number: 0 解决方法是执行以下命令 sqlplus /nolog conn / as sysdba startup …

ECS服务器安装docker

​ 为了安装并配置 Docker &#xff0c;你的系统必须满足下列最低要求&#xff1a; 64 位 Linux 或 Windows 系统 如果使用 Linux &#xff0c;内核版本必须不低于 3.10 能够使用 sudo 权限的用户 在你系统 BIOS 上启用了 VT&#xff08;虚拟化技术&#xff09;支持 on your s…

软件测试基础篇——Linux

1、Linux系统的特征 开源免费&#xff1a; 开源&#xff1a;开放源代码&#xff0c;指的是底层的源代码是可以开放出来&#xff0c;给相关的开发者&#xff0c;根据实际的需求做出修改的。 免费&#xff1a;不花钱&#xff0c;自由传播。 ​ Linux是一种免费使用和自由传播的…

【Tomcat】(Tomcat 下载Tomcat 启动Tomcat 简单部署 基于Tomcat进行网站后端开发)

文章目录 Tomcat下载Tomcat启动Tomcat简单部署 基于Tomcat进行网站后端开发 Tomcat Tomcat 是一个 HTTP 服务器.HTTP 协议就是 HTTP 客户端和 HTTP 服务器之间的交互数据的格式. HTTP 服务器我们可以通过 Java Socket 来实现. 而 Tomcat 就是基于 Java 实现的一个开源免费,也是…

【云原生】微内核的分布式操作系统 Kubernetes

微内核的分布式操作系统 Kubernetes 如今&#xff0c;Kubernetes 已经成为分布式集群管理系统和公有云 / 私有云的事实标准。实际上&#xff0c;Kubernetes 是一个分布式操作系统&#xff0c;它是 Google 在分布式操作系统领域十余年工程经验和智慧的结晶&#xff0c;而 Google…

实时语义分割网络 BiSeNet 训练自定义数据集

语义分割是一种将标签分配给每个像素的技术&#xff0c;广泛应用于场景理解、自动驾驶、人机交互、视频监控等领域。随着卷积神经网络的不断发展&#xff0c;研究人员提出了基于全卷积网络的语义分割算法&#xff0c;这些算法在语义分割任务中表现出良好的性能。 论文地址&…

IDEA设置Maven自动编译model

IDEA设置Maven自动编译model 项目工程结构IDEA maven设置 项目工程结构 假设我们的项目结构是下图这样&#xff0c;也就是一个父工程下包含多个子模块&#xff0c;其中dubbo-01-api是公共模块&#xff0c;其它两个模块要想使用必须在pom文件中引入。 本地开发要想不会报错&am…

T113-S3-调试debug串口修改

目录 前言 一、原理图示意 二、设备树文件配置 三、系统配置文件修改 四、调试问题 总结 前言 在嵌入式系统开发过程中&#xff0c;Debug串口是一个不可或缺的工具&#xff0c;用于输出调试信息、观察系统运行状态以及进行错误排查。T113-S3开发板作为一款功能强大的嵌入式…

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验五 继电器

目录 前言 一、原理图及知识点介绍 1.1、继电器原理图&#xff1a; 二、代码分析 前言 第一个实验&#xff1a; 51单片机&#xff08;普中HC6800-EM3 V3.0&#xff09;实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验&#xff1a;51单片机&am…

软件测试基础篇——Shell

1、 shell概述 脚本&#xff1a;也是属于文本文件/文本文档&#xff0c;除了读和写之外&#xff0c;还可以直接被执行/运行&#xff0c;一句话总结&#xff1a;一个可以直接被执行(运行)的文件/文档&#xff0c;被称为“脚本” shell脚本&#xff1a;利用shell技术编写出来的一…

ThinkPHP8命名规范-ThinkPHP8知识详解

本文主要讲解thinkphp8的命名规范&#xff0c;主要包括&#xff1a;遵循PHP自身的PSR-2命名规范和PSR-4自动加载规范、目录和文件命名规范、函数和类、属性命名规范、常量和配置命名规范、数据表和字段命名规范、不能使用PHP保留字。 在使用thinkphp8开发项目之前&#xff0c;…

Python pycparser(c文件解析)模块使用教程

文章目录 安装 pycparser 模块模块开发者网址获取抽象语法树1. 需要导入的模块2. 获取 不关注预处理相关 c语言文件的抽象语法树ast3. 获取 预处理后的c语言文件的抽象语法树ast 语法树组成1. 数据类型定义 Typedef2. 类型声明 TypeDecl3. 标识符类型 IdentifierType4. 变量声明…

人工智能原理概述 - ChatGPT 背后的故事

大家好&#xff0c;我是比特桃。如果说 2023 年最火的事情是什么&#xff0c;毫无疑问就是由 ChatGPT 所引领的AI浪潮。今年无论是平日的各种媒体、工作中接触到的项目还是生活中大家讨论的热点&#xff0c;都离不开AI。其实对于互联网行业来说&#xff0c;自从深度学习出来后就…

(7)原神各属性角色的max与min

在对全部角色进行分析之后&#xff0c;还有必要对各属性角色的生命值/防御力/攻击力进行max与min显示&#xff1a; 话不多说&#xff0c;上货&#xff01; from pyecharts.charts import Radar from pyecharts import options as opts import pandas as pd from pyecharts.ch…

Openlayers实战:选择feature,列表滑动,定位到相应的列表位置

在Openlayers的实际项目中,点击某个图层的feature,在左侧的列表中显示出来,滚动条滑动,能显示在视觉区内,具体的方法请参考源代码。 效果图 数据 guangdong.json https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json 源代码 /* * @Author: 大剑师兰特(xia…