当前位置: 首页 > news >正文

C++中的内存管理和模板初识

一、内存管理

1.1内存区域的划分

1.1.1内存划分区域图示

1.1.1补:堆和栈都可以进行动态分配和静态分配吗?

不是的,堆无法进行静态分配,只能动态分配;栈可以利用_alloca动态分配,但是分配的空间不能用free或者malloc来释放。

1.1.2几个常见代码在内存区中对应的位置

char char2[] = "abcd" ;

此时char2在 栈区

*char2在 栈区

原因:首先char2为一个数组名,他创建出来就在栈区;字符串虽然是常量字符,但是char2对应的是一个数组,所以其实这一常量字符串被复制到了栈中的数组中,因此*char2得到数组首元素地址,在栈区。

const char* pchar3 = "abcd";

此时pchar3在 栈区

*pchar3在 代码段(常量区)

const修饰的是指向内容,影响到的是*pchar3

原因:pChar3是一个在栈中创建的指针变量,而“abcd”是位于常量区的一个常量字符串,他们之间的关系是pChar3指向常量区中的一个地址,因此*pChar3在常量区

int* ptr1=(int*)malloc(sizeof(int)*4);

此时ptr1在 栈区

*ptr1在 堆区

原因:栈区创建的变量,在堆区申请的空间

const int a = 0;

a在 栈区,但是a具有只读属性。

1.1.2补:

32位系统下,最大的内存访问空间大小为4G,但是不可能把所有内存都给堆用,因此32位系统下堆最大申请4G内存空间的说法是错误的。

1.2C++中的内存申请

1.2.1C中原来的内存申请函数是否可以接着用?

是可以的,包括malloc,realloc,calloc都可以正常使用

1.2.2C++中新增的内存申请方式

使用new和delete进行内存申请/释放

①对于内置类型

使用举例:

//申请
int* p1=new int;
int* p2=new int[10];//释放
delete p1;
delete[] p2;

默认不对内置类型进行初始化,但可以选择显式初始化

int* p1 = new int(10);
int* p2 = new int[10]{1,2,3,4};

②对于自定义类型

其实c++设计new,更多就是为了自定义类型

假如我们定义了一个class A:

A* a1 = (A*)malloc(sizeof(A));//错误,malloc只会进行内存的申请,不会调用构造,无法初始化A* a2 = new A;//调用默认构造A* a3 = new A(2);//调用传入参数2的构造函数
1.2.2补:自定义类型数组快速申请

new很强大,如果我们想快速创建自定义类型的数组,只需要

A* p4 = new A[10];

然后用

delete[] A;

来释放

1.2.3支持隐式类型转换快速参与new的构造

A* p4 = new A[10]{1,2,3,4}//包含隐式类型转换->整形转自定义类型//当然,构造函数多参数也是支持的
A* p4 = new A[10]{{1,2},{3,4},5,6};
1.2.3补:

若是在创建时创建的是数组,但是释放使用的是delete而不是delete[]的时候

内置类型与未显示析构的自定义类型:可以正常释放,不会出现内存泄漏

经过了显示析构的自定义类型:会导致运行时错误,因为delete只会被调用一次,所以会出现内存泄漏

1.2.4C中有realloc进行扩容,那么C++该如何扩容呢?

一则可以直接使用realloc函数,二则可以选择手动扩容,即自己申请一个空间,再把原空间的内容拷贝过去

重⭐1.2.5malloc/free和new/delete的区别

1.2.5.1用法上:

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

②malloc申请不会调用构造函数,但是new会,借此完成初始化;同理free也不会调用析构函数,但是delete会调用

③malloc申请空间需要传空间的大小,但是new不需要,new只要后面跟类型就可以,如果需要创建多个对象只需要在后面加上[]来指定个数即可

④malloc的返回值是void*类型,使用的时候需要强制类型转换,但是new不需要,因为new后面跟的就是类型

⑤malloc使用的时候需要进行判空,但是new不用,只是需要进行异常的捕获

1.2.5.2原理上:

申请自定义类型的时候,malloc/free不会自动调用构造函数和析构函数,只会开辟空间;但是new/delete会在空间开启完成以后调用这两个函数完成初始化/销毁

1.3operator new和operator delete函数

1.3.1他们的本质是什么?

①本质上底层为malloc进行实现

new实际上就是 operator new+构造 来实现

②本质上底层为free进行实现

实际为 operator delete+析构 来实现

1.3.2平时C中我们在写malloc的时候常常会有检查的步骤来确认malloc是否成功,C++中呢?为什么是 operator new+构造 而不是 malloc+构造 呢?

这两个问题的答案是一致的,

因为在C++中,申请空间失败会“抛异常”(跳到catch中)

1.3.3对于A* p4=new A[4]这种多个对象空间申请如何处理?

底层调用operator new[]函数,而operator new[]本质上调用的还是operator new函数,也就是仍旧为malloc的空间

1.3补:二者的调用

operator new和operator delete函数可以显示调用,他们的返回值是void*,传入字节数(类似malloc)

1.4对C++中新申请方式的小结

malloc和free,new和delete,new[]和delete[]互相搭配使用,不要混用

原因举例:例如当我们使用new T[]进行空间申请的时候(T为代号,可以为内置类型或者自定义类型)

在函数调用层面:

①内置类型或不显示析构的自定义类型,在向operator new[]函数传参的时候传正常size的总字节数

②进行了显示析构的自定义类型会加上前面4个字节的大小,而这四个字节是为了确定要调用多少次析构,此时传size+4的字节数,汇编层面

在②这种情况下与free/delete混用会导致程序崩溃

1.5定位new表达式(placement——new)(这是一个规定)

1.5.1有何作用?

对已经申请好的空间,帮助显示调用构造函数

①使用举例1

例如想创建自定义类型A的指针并给空间(不用new)

A* p1 = (A*)operator(sizeof(A));p1->A();//这是错误的,不支持这样显式调用构造new(p1)A;//这是定位new表达式,作用就是调用构造new(p1)A(10);//定位new表达式,构造函数传入参数10

②使用举例2-》用以参考

A* p2 = (A*)operator new[](sizeof(A) * 10);
for(int i=0;i<10;i++)new(p2+i)A(i);
for(int i=0;i<10;i++)(p2+i)->~A();operator delete[](p2);

1.5.2何时需要用?

这涉及到内存池:

如果T为自定义类型,一则可以new T[n],默认直接找堆;如果要从内存池申请n个对象那要用到定位new表达式来构造。

二、模板初识

2.1泛型编程

试想,在C语言中我们要多次交换两数的值,而且这些数还有许多不同的类型,我们要怎么办?

是的,只能一个一个去写。

可是他们的逻辑十分相似,因此我们希望可以快捷一些:

写一个框架,针对广泛的类型,而这一思想就是泛型编程,这个框架就是函数模板。

2.2函数模板

2.2.1函数模板格式

template <typename T1,typename T2>
//或者template <class T1,class T2>

此处的T1和T2就是类型的泛型名字

使用举例:

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

之后,主函数中

double a2=1.1,b2=1.3;
int a1=1,b1=3;Swap(a2,b2);
Swap(a1,b1);

2.2.2同一模板下不同类型调用的是同一个函数吗?

我们若是逐步运行,会发现函数调用都会回到模板中,所以这说明调用的都是同一函数吗?

不,眼见在这里并不真实,他们类型的大小都相同,怎么会是同一个函数呢?

我们把目光放到汇编层次,可以发现找的是函数Swap<int>和Swap<double>这两个不同地址处的函数

总的来说,编译器会根据传参的类型推演出所需要的函数,自动实现他们,方便链接时配对。

2.2.2补:

举的例子里涉及到交换函数,平时我们很常用,所以在std库里其实已经有了一个对应的函数模板,可以直接

swap(a1,b1);
swap(a2,b2);

2.2.3模板的推演

假设我们有这样一个函数模板

template <class T>
T Add(T& a,T& b)
{return a+b;
}

此时我传参

int a1=10;
double b1=10.2;

会发现程序报错了。

为什么?我们该如何解决这一问题呢?

原因是根据函数模板推演的时候存在歧义。

有三种解决方案

①可以强转赋值

Add((double)a1,b1);
Add(a1,(int)b1);

②显式模板实例化,不进行推演

Add<int>(a1,b1);
Add<double>(a1,b1);

③可以设置两个模板参数,利用auto来自动获取返回值类型

template <class T1,class T2>
auto Add(const T1& a,const T2& b)
{return a+b;
}

使用时

Add(a1,b1);

即可

2.2.4必须使用显示模板实例化的情况

之前举的例子可以通过参数对模板T的类型进行推演,但如果无法推演呢?

例如

templat <class T>
T* func(int a)
{//...
}

此时则必须要有显示模板实例化

int* ret = func<int>(1);

2.2.5模板与普通函数可以同时存在

例如模板

template <class T1,class T2>
auto Add(const T1& a,const T2& b)
{return a+b;
}

与函数

int Add(int a,int b)
{return a+b;
}

在选择进行调用的时候遵循“有现成的吃现成的”,即

先调用普通函数在调用模板,若没有普通函数,掉对应模板,如果没有对应模板,进行隐式类型转换也会完成调用

2.3类模板

2.3.1作用

假设我在C语言中实现了一个栈(Stack),此时我希望创建两个栈类型的变量st1和st2,一个存int,一个存double,我该怎么办?

我需要写两个仅DataType类型不同的结构体,那可否简化?

可用类模板

templat <class T>
class Stack
{
public://...完成栈的定义
}

此时T可以表示类型

2.3.1补

类模板和函数模板都是不提高效率,只简化代码,变量st1与st2的类型并不相同

2.3.2类模板的实例化

函数模板尚有推演的可能,但是类模板一点也没有,所以必须进行实例化

Stack<int> st1;
Stack<double> st2;

2.3.3类模板中的声明与定义分离

类模板的声明和定义分离相较于普通类要修改格式

template <class T>//每一个声明定义分离的函数之前都要加上这一行
void Stack<T>::Push(const T& data)//域访问限定符之前要写完整的类类型
{//...;
}

且不可以分离文件进行定义(.cpp与.h也不可以),会报链接错误

2.4模板注意事项

①模板运行时不检查数据类型,也不保证类型安全,仅仅相当于类型的宏替换

②模板可以用来创建动态增长的数据结构(模板类型的大小可变)

③模板的实参:模板实例化的内容

④模板参数可以为类型,也可以为非类型,例如

template <class T,size_t N>

其中非类型其实相当于typedef

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • A Single Generic Prompt forSegmenting Camouflaged Objects
  • C#程序 Debugger,Release都没问题,但是,打包安装后:System.FormatException: 输入字符串的格式不正确
  • Linux线程概念
  • 多云架构下大模型训练的存储稳定性探索
  • vue3页面组件中怎么获取上一个页面的路由地址
  • 八叉树,分裂空间的魔法师【Unity】
  • 【开发环境搭建】Macbook M1搭建Java开发环境
  • 二维背包问题(C++)
  • C++入门9——list的使用
  • vue增加长时间无操作退出登录功能
  • C# 数组定义和常用方法
  • AI 浪潮中的一体化数据库|外滩大会之OceanBase实录
  • P1308 [NOIP2011 普及组] 统计单词数
  • aliyun图片存储OSS工具类
  • Cesium 问题:视角漫游时添加的无人机模型飞行时有抖动
  • 【comparator, comparable】小总结
  • Android Volley源码解析
  • eclipse的离线汉化
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Fundebug计费标准解释:事件数是如何定义的?
  • GraphQL学习过程应该是这样的
  • HTML5新特性总结
  • nodejs实现webservice问题总结
  • PAT A1017 优先队列
  • python学习笔记 - ThreadLocal
  • Rancher-k8s加速安装文档
  • Redis字符串类型内部编码剖析
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 微信开放平台全网发布【失败】的几点排查方法
  • 学习笔记TF060:图像语音结合,看图说话
  • 从如何停掉 Promise 链说起
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (C语言)共用体union的用法举例
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (七)理解angular中的module和injector,即依赖注入
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (算法)Game
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)使用VMware vSphere标准交换机设置网络连接
  • ./configure、make、make install 命令
  • .env.development、.env.production、.env.staging
  • .Mobi域名介绍
  • .NET MVC 验证码
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • .NET的数据绑定
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [ vulhub漏洞复现篇 ] Apache APISIX 默认密钥漏洞 CVE-2020-13945
  • []FET-430SIM508 研究日志 11.3.31