首页 > 系统相关 >C++之内存管理与模板初级

C++之内存管理与模板初级

时间:2024-10-25 20:47:19浏览次数:3  
标签:free 函数 int C++ 内存 new 模板 delete

内容介绍

Ⅰ.C++内存管理

1.C/C++内存分布

int n1 = 1;
static int n2 = 2;
void Test()
{
	static int n3 = 3;
	int n4 = 4;
	int n5[10] =    { 5,5,5,5,5, };
	char n6[] =      "666666";
	const char* n7 = "7777777";
	int* n8 =       (int*)malloc(sizeof(int) * 4);
	int* n9 =       (int*)calloc(4, sizeof(int));
	int* n10 =      (int*)realloc(n9, sizeof(int) * 4);
	free(n8);
	free(n10);
}

对上述代码在内存区域划分如下图:
在这里插入图片描述
说明:1.又叫堆栈–存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux操作系统的内容)
3. 用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段–也叫静态区–存储全局数据和静态数据。
5. 代码段–也叫常量区–可执行的代码/只读常量。

2.C与C++动态内存管理方式对比

2.1C中动态内存管理方式

int* p1 = (int*)malloc(sizeof(int) * 4);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, 8 * sizeof(int));
free(p1);
//free(p2);
free(p3);
  • C中实现动态扩容主要是用malloc,calloc与realloc来实现。同时释放空间是用free
  • malloc是直接找一块连续的内存空间进行开辟。
  • calloc是在malloc的基础上同时对开辟好的空间进行初始化为0。
  • realloc又叫增容,是在一块空间上进行增容操作,如果原有的空间后面不足以增容,则会另找一块足够空间进行开辟,同时将原来空间的数据拷贝进新空间,并释放旧空间,若开始的空间指向为空(NULL),则一开始调用malloc进行开辟。
  • 为了防止内存泄露,每次开辟的空间都要及时释放,free是对堆上开辟好的空间进行释放,注意free不能释放空的空间,且一块空间不能被连续释放多次,否则报错。
  • malloc,calloc,realloc和free都是函数。

注:上述代码是不需要对p2进行释放,是因为realloc的特性,释放(p2)反倒会报错。
在这里插入图片描述

2.2C++中内存管理方式

  • 首先C++向下兼容C,C的内存管理方式C++也可以使用,但我们发现C的方式使用起来多多少少不够方便,于是C++就推出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。 注:new和delete都是操作符。

new和delete的使用方法:

  • 1.对内置类型使用
void Test()
{
	//动态申请1个int类型空间
	int* p1 = new int;
	//动态申请1个int类型空间并初始化为2004,()中写初始化值
	int* p2 = new int(2004);
	//动态申请10个int类型空间,[]中写申请类型个数
	int* p3 = new int[10];
	//error: 无法使用带圆括号的初始值设定项初始化数组
	//int* p4 = new int[10](2024);
	delete p1;//申请是new,释放是delete
	delete p2;
	delete[] p3;//申请是用new[],释放也是用delete[]
}

对内置类型的动态申请和释放C和C++本质上没什么区别,都是开辟和释放空间。

  • 对自定义类型使用
class A
{
public:
	A(int a1 = 0,int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{	
		cout << _a1 << " " << _a2 << endl;
		cout << "A(int a1 = 0,int a2 = 0)" << endl; 
	}
	~A()
	{	cout << "~A()" << endl << endl; }
private:
	int _a1;
	int _a2;
};
int main()
{
	A* p1 = new A;
	delete p1;
	A* p2 = new A(2024, 4);
	delete p2;
	//error: 无法使用带圆括号的初始值设定项初始化数组
	//A* p3 = new A[3](2005, 8);
	A* p3 = new A[3];
	delete[] p3;//调用的是new[],释放也要用delete[]
	return 0;
}

在这里插入图片描述

上图可以看出,new和delete对于自定义类型来说,在开辟空间和释放空间过程这一时间段会调用自定义类型的构造函数和析构函数,具体是new 开辟好了空间后 会调用构造,delete 释放空间前 会调用析构。

  • 需注意new和deletenew[]和delete[] 需配套使用,否则会造成运行崩溃。 在这里插入图片描述
    总结:malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。 不同的地方是:
    1. malloc和free是函数,new和delete是操作符
    2. malloc申请的空间不会初始化,new可以初始化
    3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
    4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
    5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常(大项目一般都要捕获异常)
    6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放

3.new和delete的底层实现原理(了解)

  • new和delete底层是operator new 和operator delete两个函数,operator new 和operator delete是系统提供的全局函数,new和delete在底层会去调用这两个全局函数去完成资源的申请和释放。
  • operator new全局函数底层是通过调用malloc函数去申请空间,operator delete全局函数底层是通过调用free函数去释放空间,他们不会去调用构造和析构,调用构造和析构还是new和delete干的事情。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
 // try to allocate size bytes
 void *p;
 while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
      return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

Ⅱ.模板初阶

1.模板介绍

  • C++引入模板可谓是C++历史上跨时代的意义,模板是泛型编程的基础,当‌本贾尼博士(Bjarne Stroustrup)提出模板概念后,C++从此一发不可收拾,自此面向对象的编程语言也逐渐被广大程序员们所接受。
  • 模板正如它的名字一样,它是一个模具,当我们对模具浇筑不同原料时,会得到不同材料但形状大小一致的实质性物体。
  • 在程序中,不同的数据类型就是不同的原料,拿函数模板来说,当我们指明函数模板时,编译器会根据不同的数据类型去生成对应这个数据类型的函数,不同数据类型生成的函数框架都是相同的,只是在替换数据类型模板的地方会不同。
  • C++中模板分为函数模板和类模板,需要指出函数模板是模板,是函数的模板,模板函数是函数,是通过模板实例化出来的函数,类模板和模板类同理。
    在这里插入图片描述在这里插入图片描述

2.函数模板

  • 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
  • 使用格式为:
    template<typename T1, typename T2,…,typename Tn>
    返回值类型 函数名(参数列表){}
    其中 templatetypename关键字,前者直译为模板,后者直译为类型名字,这里typename也可以用class代替(不能用struct代替),根据个人习惯想用哪个用哪个。
  • 用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化显式实例化
    1.隐式实例化:让编译器通过传入实参的数据类型进行推导
template<class T>
T Add(const T& a, const T& b)//涉及类型转换时产生临时对象
{
	return a + b;
}
int main()
{
	int a1 = 10, a2 = 20;
	double b1 = 10.0, b2 = 20.0;
	cout << Add(a1, a2) << endl;//隐式实例化
	cout << Add(b1, b2) << endl;
	//error: “Add”: 未找到匹配的重载函数,要想解决:1.用户自己强转,2.使用显示实例化
	//cout << Add(a1, b1) << endl;
	cout << Add(a1, (int)b1) << endl;//强转
	return 0;
}

2.显示实例化:<>中传入用户指定的数据类型,当数据类型不匹配时,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

int main()
{
	int a1 = 10, a2 = 20;
	double b1 = 10.1, b2 = 20.2;
	cout << Add(a1, (int)b1) << endl;
	cout << Add<int>(a1, b1) << endl;//显示实例化
	cout << Add<double>(a2, b2) << endl;
	return 0;
}

3.函数模板参数匹配原则

  • 一个非模板函数和一个同名的函数模板可以同时出现,并且该函数模板可以被实例化成这个非模板函数,在实际编程环境中,编译器会去调用这两个中最匹配合适的。
  • 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
//1.普通加法函数
int Add(int& a, int& b)
{
	return a + b;
}
//2.通用的模板加法函数
template<class T1,class T2>
T1 Add(const T1& a, const T2& b)
{
	return a + b;
}
int main()
{
	int a1 = 10, a2 = 20;
	double b1 = 2004.4, b2 = 2005.8;
	cout << Add(a1, a2) << endl;//与1最匹配,会去调用1
	cout << Add(a1, b1) << endl;//与2最匹配,会去调用2
	cout << Add<double>(a2, b2) << endl;//显示调用模板函数,则会调用2
	return 0;
}

在这里插入图片描述

4.类模板

  • 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
  • 同时无论是函数模板还是类模板,都不建议声明和定义分离至两个文件,如.h和.cpp,否则会出现链接错误,具体原因在模板进阶部分会提到。
template<class T>
class Stack
{
public:
	Stack(int size = 0, int capacity = 4, T* arr = nullptr)
		: _size(size)
		, _capacity(capacity)
		, _arr(arr)
	{	_arr = new T[_capacity]; }
	void Push(const T& data)
	{	_arr[_size++] = data; }
	void Print()
	{	cout << _arr[0] << " " << _arr[1] << " " << _arr[2] << " " << _arr[3] << endl; }
	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;
		_size = _capacity = 0;
	}
//private:
	T* _arr;
	int _size;
	int _capacity;
};
int main()
{
	Stack<int> s1;
	Stack<double> s2;
	//Stack只是模板,Stack<int>和Stack<double>才是类型
	s1.Push(1); s1.Print();
	s2.Push(1.1); s2.Print();
	return 0;
}

在这里插入图片描述

标签:free,函数,int,C++,内存,new,模板,delete
From: https://blog.csdn.net/2401_84420653/article/details/143225553

相关文章

  • 【c++篇】:解析c++类--优化编程的关键所在(三)
    感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢个人主页:余辉zmh–CSDN博客文章所属专栏:c++篇–CSDN博客文章目录一.构造函数的初始化列表1.1构造函数体的赋值1.2初始化列表1.3`explicit`关键字二.静态`static`成员2.1.静态成员变量......
  • C++11中lambda表达式与包装器
    目录1.lambda表达式1.1引入lambda表达式1.2lambda表达式用法1.3函数对象与lambda表达式2.包装器2.1function包装器2.2bind1.lambda表达式在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。//lambda表达式#include<iostream>#inc......
  • 如何在Qt框架中使用C++进行跨平台开发
    摘要:使用Qt框架结合C++实现跨平台开发的要点涉及1、选择合适的Qt版本及工具集、2、掌握QtCreator与Qt库的使用、3、深入理解信号与槽机制、4、遵循模块化和解耦的设计原则、5、依据不同平台进行特定优化。特别地,掌握QtCreator与Qt库的使用是基础,该集成开发环境(IDE)包含了代码编辑......
  • 打卡信奥刷题(114)用C++工具信奥P1145[普及组/提高] 约瑟夫
    约瑟夫题目描述nnn个人站成一圈,从某个人开始数数,每次数到mmm的......
  • Linux下使用valgrind分析C++程序的内存泄漏
    目录一.前言二.下载安装三.使用valgrind分析内存泄漏一.前言这篇文章介绍一下Linux系统中如何使用valgrind分析C++程序的内存泄漏。二.下载安装下载地址:官网。或者直接使用命令aptinstallvalgrind安装。三.使用valgrind分析内存泄漏先看代码//main.cpp#inc......
  • C++获取当前时间并格式化为字符串
    代码一#include<iostream>#include<chrono>intmain(){ autonowTime=std::chrono::system_clock::now(); autonowTimeT=std::chrono::system_clock::to_time_t(nowTime); autopNowTm=std::localtime(&nowTimeT); charbuf[128]; std::strfti......
  • 浅拷贝与深拷贝 以及嵌套和递归使用模板类
     1.浅拷贝和深拷贝——对象和类浅拷贝浅拷贝只复制对象的基本属性,而不复制引用类型的属性指向的对象,即只复制引用而不复制引用指向的对象在浅拷贝中,新对象与原始对象共享引用类型的属性指向的对象,即它们指向同一个对象。编译器提供的拷贝构造函数是浅拷贝,当一个对象修......
  • c++/python/java/go用途介绍
    C++用途:系统编程:操作系统、驱动程序等底层开发。游戏开发:游戏引擎和高性能游戏应用,因其高效的性能和内存管理。嵌入式系统:适合资源受限的设备。科学计算:高性能计算和数值分析。金融系统:高频交易和复杂金融模型。Python用途:数据科学与分析:广泛用于数据处理和可视化......
  • 虚拟内存对 OI 的影响
    假想你写了这么四段代码:#include<vector>#include<bits/stdc++.h>usingnamespacestd;std::vector<int>vec;//inta[100000010];intmain(){freopen("a.out","w",stdout),cout<<1<<endl;}//,vec.resize(1e8);}......
  • 数据结构图的最短路径-弗洛伊德算法(有向图+数据结构课本C++代码一比一转C语言+邻接矩
    弗洛伊德算法有向图代码如下:#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<stdlib.h>#include<limits.h>#defineMaxInt32767#defineMVNum100intPath[MVNum][MVNum];//存放前驱索引的intD[MVNum][MVNum];//存放当前已知的权值//图的邻接......