【C++初阶】内存管理 && 初识模板

这篇具有很好参考价值的文章主要介绍了【C++初阶】内存管理 && 初识模板。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、C/C++内存分布

C/C++的内存分布主要分为栈区、堆区、数据段和代码段,还有内存映射段。

栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
堆用于程序运行时动态内存分配,堆是可以上增长的。
数据段–存储全局数据和静态数据
代码段–可执行的代码/只读常量。

看以下代码:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}

有几个问题:

1.char2在哪里 2.*char2在哪里
3.pChar3在哪里 4.*pChar3在哪里
5.ptr1在哪里 6.*ptr1在哪里


7.sizeof(num1) =
8.sizeof(char2) = ? 9.strlen(char2) = ?
10.sizeof(pChar3) = ? 11.strlen(pChar3) = ?
12.sizeof(ptr1) = ?

1.2 char2是定义在Test函数里的数组名,是局部变量,在栈区;* char2对数组解引用得到数组的地址是字符串,但是是把字符串的内容拷贝到数组的地址去,所以 * char2 在栈区
3.4 pChar3是定义在Test函数里的指针,是局部变量,在栈区;* pChar3对指针进行解引用,得到字符串,字符串在代码段
5.6 ptr1是定义在Test函数里的整型指针,是局部变量,在栈区;* ptr1解引用得到的地址是malloc函数开辟的空间,在堆区
7. sizeof计算的是整个数组的大小,以字节为单位,数组里有10个元素,一个元素4个字节,所以答案是40
8. sizeof计算的是字符串的大小,注意包括后面的斜杠0,所以答案是5
9. strlen计算的是字符串的大小,不包括后面的斜杠0,所以答案是4
10. sizeof只关注类型,pChar3是指针,所以答案是4/8
11. strlen计算的是字符串的大小,不包括后面的斜杠0,所以答案是4
12. ptr1是整型指针,所以答案是4/8

一张图表示:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

二、C/C++动态内存管理方式

C语言中动态开辟的方式有malloc、calloc和realloc,常用的是malloc函数,释放空间的方式是free。但是,这两种方式有一些局限性,所以C++有新的动态开辟管理方式,通过new和delete操作符进行动态内存管理。

2.1 new和delete的用法

先来作下对比:

int* a = (int*)malloc(sizeof(int));
///
int* a = new int;

new不需要我们自己写sizeof计算字节大小,是不是看起来更简洁了。

delete a;
///
free(a);

delete和free差不多,注意释放空间完要置空。

1️⃣new初始化元素
在后面加个括号,括号里就是要初始化的值

int* a = new int(4);//初始化为4

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

2️⃣new创建元素个数
在后面加个方括号,方括号里就是元素的个数

int* a = new int[5];

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

3️⃣new创建元素个数并初始化
在2️⃣的基础上后面加个花括号,花括号里面分别为初始化的值。如果初始化的数量小于元素个数,补0

int* a = new int[5]{ 1,2,3 };

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
4️⃣delete单个元素时

delete a;

5️⃣delete多个元素时

delete[] a;

注意:
申请单个元素空间与释放单个对象的new和delete要搭配使用;申请多个元素空间与释放多个对象的new[]和delete[]要搭配使用

2.2 new与malloc、delete与free比较

共同点:
都是从堆上申请空间,并且需要用户手动释放。

不同点:
1️⃣malloc只开辟空间大小,不能初始化;new就既可以开辟空间的大小,也可以对空间进行初始化。 前面代码就用演示。

2️⃣自定义类型初始化问题
当对象是自定义类型时,malloc不方便自定义类型初始化,因为malloc只会开辟空间,free只会释放空间。new会先开空间,再调用构造函数;delete会先调用析构函数,再释放空间。

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* aa = new A[3]{ 11,22,33 };
	delete aa;
	return 0;
}

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

同时new可以在开空间时完成初始化

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

补充:
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
malloc和free是函数,new和delete是操作符

2.3 较复杂场景分析

看以下代码:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* st = new Stack;
	delete st;
	return 0;
}

这段代码main函数里有一个new和delete,构造函数里也有一个new,析构函数也有一个delete,那么它们之间的关系是怎样的
图示分析:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
自定义类型先给对象开辟空间(对象个数问题后面分析),再调用构造函数,构造函数里给_a数组开辟空间,因为_a是内置类型,所以这里的处理方式与malloc相同。delete对象,先调用析构函数,清理_a,再释放空间,此时清理的是对象。

注意,有的人乱着用把delete st写成free st,free只会释放空间不会调用析构函数,它释放了对象的空间,但是对象的空间里的_a指向的空间没有被释放掉,是不是就变成野指针了,所以不可free和delete混着用。

三、operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

先来看一段汇编代码:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

operator new 实际是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。 所以,operator new 和operator delete可以说是malloc和free的封装

四、 new和delete的实现原理

1️⃣对于内置类型,new和malloc,delete和free基本类似。有一点不同,只有单个对象时,匹配的两者是new/delete;多个对象时,匹配的两者是new[]和delete[],跟数组一样,也是连续的空间。要注意的是new在申请空间失败时会抛异常,malloc会返回NULL。

2️⃣对于自定义类型,只有单个对象时,就是前面一段代码的例子,这个就不讨论了。下面来看看多个对象的情况:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* st = new Stack[10];
	delete[] st;
	return 0;
}

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

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

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
一个栈对象有3个内置类型(int* int int),共12个字节,那么10个栈对象总共有120个字节,但是事实是这样的吗?
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
打开内存查看:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
在st指向的地址处前面有4个字节存储的是对象的个数
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

如果把delete 后面的方括号去掉会怎样:

	Stack* st = new Stack[10];
	delete st;

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

程序运行崩溃了,那原因是什么呢?先来看以下代码:

以下是一个A类,私有成员变量只有一个整型,先把析构函数注释了,看会发生什么:

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* aa = new A[10];
	delete aa;/// 把方括号去掉
	return 0;
}

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
程序正常运行。如果不注释析构函数会怎样:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
程序运行崩溃了。

为什么这个A类注释不注释析构函数有区别?

没有注释析构函数,会去调用这个析构函数,调用析构函数,aa指向下图的位置
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
前面红色的区域是存储对象个数的。我们知道delete[]对应匹配的是new[],而delete对应匹配的是new,因为delete后面没有方括号,所以对应使用的new开空间时就不需要前面的4个字节来存储对象的个数(1个对象没啥好记录个数的),因此aa指针指向如下图所示。
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

这样的话释放空间如下图:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
释放的空间是蓝色区域,但是前面的红色的区域也必须要释放,不可以只释放部分,所以释放空间位置不对导致运行崩溃。

有注释析构函数,虽然编译器可以自动调用默认生成的析构函数,但是这取决于编译器。因为A类里面只有内置类型(一个整型),没有申请空间,所以可以不做处理。不做处理就不调用析构函数,没有析构函数也就没有前面的位置存储对象个数,释放的位置就不会只释放部分。

总结:new[]与delete[]和new与delete要匹配使用,不能混。

五、初识模板

5.1 泛型编程

概念:泛型编程是一种无具体类型的通用代码,可以实现不同类型时的复用。模板是泛型编程的基础,模板分为函数模板和类模板。

5.2 函数模板

5.2.1 概念

函数模板是一种通用的函数,通过实参的类型推出相应类型的函数,实现函数调用。

5.2.2 写法

关键字templete
格式:templete < typename T1,typename T2… >,T是类型
typename 也可以是class

一个交换函数的模板:

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

实现两个数的交换:

int main()
{
	int a = 1;
	int b = 3;
	cout << a << endl << b << endl;
	Swap(a, b);//a和b传进去,编译器推出类型,T就变成了int类型
	cout << a << endl << b << endl;
	return 0;
}

运行结果:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

5.2.3 不同类型时使用函数模板

1️⃣调用两次函数,函数实参的类型不同,一个是int,另一个是double,这两次调用都使用了函数模板,但是这两次调用的不是同一个函数。

通过汇编查看:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
2️⃣多模板参数打印
想同时打印不同的类型可以使用多个模板参数

template <class T1, class T2>
void Print(T1& a, T2& b)
{
	cout << a << endl << b << endl;
}
int main()
{
	int a = 33;
	double b = 1.22;
	Print(a, b);
	return 0;
}

a和b的类型由直接定,反正传过去什么,它就打印什么
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

3️⃣加法函数

代码:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 2;
	int b = 6;
	cout << Add(a, b) << endl;

	return 0;
}

运行结果:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
如果把b的类型换成double:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
编译器报错了,说明不可以两个类型不同,不然的话就不确定应该使用哪种类型了。

5.2.4 函数模板实例化

1️⃣隐式实例化
通过实参的类型编译器自动推演出相应的函数模板参数类型叫做隐式实例化。

加法函数:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	int b = 2;
	int c = Add(a, b);
	cout << c << endl;
	return 0;
}

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言

T推出为int类型。

如果其中一个参数的类型不一样,编译器会报错,这里有两种办法解决。

强制类型转换:

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	double b = 2.1;
	
	cout << Add(a, (int)b) << endl;
	return 0;
}

【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
另一种是显示实例化

2️⃣显示实例化

template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 9;
	double b = 2.1;
	
	cout << Add<int>(a, b) << endl;
	return 0;
}

在函数名后的<>中指定模板参数的实际类型
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
一般情况下使用函数模板隐式实例化就够了,但是有些情况必须使用显示实例化

template <class T>
T* func()
{
	return new T[3];
}
int main()
{
	func<int>();
	return 0;
}

没有传参数,T就不知道应该变成什么类型,所以要显示实例化来确定T的类型。

5.2.5 函数模板匹配调用原则

当代码中既有普通函数,又有函数模板,那么调用函数时会使用哪个呢?

//函数模板
template <class T>
T Add(const T& a, const T& b)
{
	return a + b;
}
//普通函数
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 1;
	int b = 3;
	cout << Add(a, b) << endl;

	return 0;
}

打开调试:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
变量a和b定义的时候都是int类型,普通函数的参数类型也是int,所以有现成的就用现成的。

现在把a和b的类型改变一下。

	double a = 1.1;
	double b = 3.3;

调试:
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言
如果没有现成的普通函数,就使用合适的,函数模板参数类型为double

5.3 类模板

类模板与函数模板差不多,但是要注意的点:必须显示实例化

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack<int> st1;
	Stack<double> st2;
	return 0;
}

这样才能确定类中成员变量的类型。
【C++初阶】内存管理 && 初识模板,C++初阶,c++,数据结构,开发语言文章来源地址https://www.toymoban.com/news/detail-757174.html

到了这里,关于【C++初阶】内存管理 && 初识模板的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c++ 内存管理一:初识内存分配工具

    前言 侯捷 c++内存管理学习总结笔记。 在C++中,有几种常用的内存分配工具可以帮助进行动态内存管理。 从c++应用程序自上而下,通常会有这样的几种分配内存的方式,当然最终都是直接或间接的调用系统的API。 1 new 和 delete new 和 delete :new操作符用于在堆上分配内存,de

    2024年02月11日
    浏览(43)
  • 【1++的C++初阶】之内存管理

    👍作者主页:进击的1++ 🤩 专栏链接:【1++的C++初阶】 如上图所示,C/C++程序在运行时占用的内存主要是这几部分 我们首先来说他们各自的作用与用法: malloc:malloc是C语言中用来动态申请内存的函数,它能够申请所输入参数大小的空间,单位为字节,并且malloc的类型为vo

    2024年02月05日
    浏览(32)
  • 数据结构与算法—二叉树数组表示(ArrayBinTree)、二叉树的链式表示(LinkedBinTree)的基于C++模板代码实现

    1、二叉树的顺序表示:ArrayBinTree.h 二叉树的顺序表示法操作方便,但缺点是容易造成存储空间浪费。 这是一个用数组实现的二叉树类模板。它可以创建一个空树,也可以在指定的位置插入结点并设置结点的值,可以删除子树,并支持逐层遍历。使用该类时,需要提供一个元

    2024年02月06日
    浏览(41)
  • 【C++】C++内存管理,模板

    C++中通过 new 和 delete 两个操作符实现动态内存管理 为自定义类型设计,可以在申请空间得同时初始化 使用 new 先申请空间,再调用构造 使用 delete 时先调用析构,再释放空间 operator new 和 operator delete 是系统提供的全局函数,不是运算符重载, 在使用 new 或 delete 时,底层实

    2024年02月01日
    浏览(38)
  • 【C++初阶】七、内存管理(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】六、类和对象(初始化列表、static成员、友元、内部类)-CSDN博客  ==================

    2024年02月05日
    浏览(42)
  • 【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)

    学习专栏 : 《零基础学C语言》 《数据结构世界》 俗话说的好,要想学好 数据结构 (数据结构世界,对数据结构感兴趣的小伙伴可以移步),就必须学好以下三方面知识: 指针 不允许你还不了解指针的那些事(一)(内存和地址+指针变量+指针运算+野指针+传址调用) 不

    2024年02月05日
    浏览(42)
  • C++动态内存管理+模板

    💓博主个人主页:不是笨小孩👀 ⏩专栏分类:数据结构与算法👀 C++👀 刷题专栏👀 C语言👀 🚚代码仓库:笨小孩的代码库👀 ⏩社区:不是笨小孩👀 🌹欢迎大家三连关注,一起学习,一起进步!!💓 C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使

    2024年02月09日
    浏览(41)
  • 【C++初阶】第五站:C/C++内存管理 (匹配使用,干货到位)

    前言: 本文知识点: 1. C/C++内存分布2. C语言中动态内存管理方式3. C++中动态内存管理4. operator new与operator delete函数         5. new和delete的实现原理 ( 干货在此 ) 6. 定位new表达式(placement-new)7. 常见面试题 目录 C/C++内存分布 1.内存划分题 2.sizeof 和 strlen 区别? 3.计算siz

    2024年03月11日
    浏览(31)
  • 【数据结构与算法】C++的STL模板(迭代器iterator、容器vector、队列queue、集合set、映射map)以及算法例题

    更多算法例题链接: 【数据结构与算法】递推法和递归法解题(递归递推算法典型例题) 什么是迭代器(iterator) 迭代器(iterator)的定义: 迭代器是一种检查容器内元素并遍历元素的数据类型。 迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。 容器

    2024年04月14日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包