C++初识内存管理和模版

目录

前言

1.C/C++内存分布

2. C++的内存管理方式

2.1 new/delete操作内置类型

2. new和delete操作自定义类型

3. operator new和operator delete函数

4. new和delete的实现原理

4.1 内置类型

4.2 自定义类型

5. malloc/free和new/delete的区别

6. 初识模版

6.1 泛型编程

6.2 函数模板概念和格式

6.3 函数模板原理

6.4 函数模板实例化

6.5 类模板定义格式与实例化

总结


前言

本文今天要浅浅的讲解C++内存管理和模板,关于C++是如何进行动态管理内存,C++中的模板的作用是什么,类型有哪些。虽然比较粗浅,但这是每个小伙伴学C++的必经之路,一起学起来吧!


1.C/C++内存分布

我们来看看下面的代码和相关问题:

int globalVar = 1;
static int staticGlobalVar = 1;

void Test()
{
	int size = sizeof(int);
	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(size*4);
	int* ptr2          = (int*)calloc(4, size);
	int* ptr3          = (int*)realloc(ptr2, size*4);

	free(ptr1);
	free(ptr3);
}

 1. 选择题:
  选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
  globalVar在哪里?____   staticGlobalVar在哪里?____
  staticVar在哪里?____   localVar在哪里?____
  num1 在哪里?____
 
  char2在哪里?____       *char2在哪里?___
  pChar3在哪里?____      *pChar3在哪里?____
  ptr1在哪里?____        *ptr1在哪里?____


2. 填空题:
  sizeof(num1) = ____; 

  sizeof(char2) = ____;      strlen(char2) = ____;
  sizeof(pChar3) = ____;     strlen(pChar3) = ____;
  sizeof(ptr1) = ____;


3. sizeof 和 strlen 区别?

答案:

1.  globalVar在C   staticGlobalVar在C
     staticVar在C      localVar在A
     num1 在A

     char2在A       *char2在A
     pChar3在A    *pChar3在D
     ptr1在A          *ptr1在B

2.  sizeof(num1) = 40;

    sizeof(char2) =  5     strlen(char2) =  4
    sizeof(pChar3) =  4     strlen(pChar3) =  4
    sizeof(ptr1) =  4/8

解析:

1.全局变量和静态变量都是存放在数据段(静态区)。

2.栈上存储的是函数内开辟的变量,在函数调用结束后,即时销毁。

  • num是一个数组,本质上是数组首元素的地址,也是存放在栈上。
  • char2本质是字符串首字符的地址,pChar也是类似的。不过当他们解引用的时候,char2是在栈上开辟的空间,pChar3是指向只读区域的代码段。
  • ptr1是指针变量,也是局部变量。存放的地址是指向堆,这是动态开辟的内存区域。
  • sizeof是计算该变量所占内存空间的大小,而char2这个字符串在字符结束后,会加上一个斜杠0,表示终止符。
  • strlen是一个计算字符个数的函数。

内存区域划分图:

【说明】

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

2. C++的内存管理方式

2.1 new/delete操作内置类型

void Test()
{
 // 动态申请一个int类型的空间
 int* ptr4 = new int;
 
 // 动态申请一个int类型的空间并初始化为10
 int* ptr5 = new int(10);
 
 // 动态申请10个int类型的空间
 int* ptr6 = new int[3];
 delete ptr4;
 delete ptr5;
 delete[] ptr6;
}
  •  new + 内置类型 +  (初始化内容)
  •  new + 内置类型 +   [元素个数]
  • delete + 申请空间的变量
  • delete[] + 申请多个元素空间的变量

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

2. new和delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
	// new/delete 和 malloc/free最大区别是 
	// new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;
	// 内置类型是几乎是一样的
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;
	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;

	return 0;
}

运行结果如下:

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会。

3. operator new和operator delete函数

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

下面是operator new和operator的底层代码:

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);
}

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;
}

4. new和delete的实现原理

4.1 内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL

4.2 自定义类型

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

5. malloc/free和new/delete的区别

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

不同的地方:

  1.  malloc和free势函数,new和delete是操作符。
  2. malloc申请的空间不会初始化,new可以初始化。
  3. malloc申请空间是,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
  4. malloc的返回值void*,在使用时必须强转类型,new不需要,因后面跟的是空间的类型。
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
  6. 申请自定义类型对象时,malloc/free只会对空间进行操作,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前调用析构函数完成空间中资源的清理。

6. 初识模版

6.1 泛型编程

Swap函数是经常使用的函数,内核逻辑就是开辟一个临时变量进行交换。当我们要交换的变量类型是int,double或者char时,需要写出三个Swap函数重载。如果还有其他类型变量需要交换,还要再写一个Swap函数的重载,而且函数内部实现是相同的。

重载函数有以下缺点:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
  2.  代码的可维护性比较低,一个出错可能所有的重载均出错

那我能不能只告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}
//需要重载三个Swap函数来实现交换
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	char ch1 = 'a', ch2 = 'b';

	Swap(a1, a2);
	Swap(d1,d2);
	Swap(ch1, ch2);
	return 0;
}

C++就存在这样的模具,通过给这个模具不同的参数类型,获得一份代码,这就是模板。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

6.2 函数模板概念和格式

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

格式:

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){ }

//只需要实现一个函数即可
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	char ch1 = 'a', ch2 = 'b';

	Swap(a1, a2);
	Swap(d1, d2);
	Swap(ch1, ch2);
	return 0;
}

6.3 函数模板原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于浮点数和字符类型也是如此。

6.4 函数模板实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

1.隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
    //隐式实例化
	Add(a1, a2);
	Add(d1, d2);

    //下面这个语句可以通过编译器吗?
    Add(a1, d1);
	/*
	该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错

	注意:在模板中,编译器一般不会进行类型转换操作,
    因为一旦转化出问题,编译器就需要背黑锅
	*/
	// 此时有两种处理方式:
    //1. 用户自己来强制转化 
    //2. 使用显式实例化
    //这就是强转
	Add(a1, (int)d1); 
	return 0;
}

2.显式实例化:在函数名后的<>中指定模板参数的实际类型

int main(void)
{
    int a = 10;
    double b = 20.0;

    // 显式实例化
    Add<int>(a, b);
    return 0;
}

6.5 类模板定义格式与实例化

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};

下面是栈使用类模板。

template <class T>
class Stack
{
public:
	Stack (size_t capacity = 4)
	{
		_array = (T*)malloc(sizeof(T) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(const T& data);

private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

//模版不建议声明和定义分离到.h和.cpp会出现链接错误
//模板在类外进行定义需要加参数列表
template<class T>
void Stack<T>::Push(const T& data)
{
	// 扩容
	_array[_size] = data;
	++_size;
}

int main()
{
    //类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可
    //类模板名字不是真正的类,而实例化的结果才是真正的类。
	Stack<int> st1; // int
    Stack<double> st1; // double

	return 0;
}


总结

看到这里的小伙伴肯定堆内存管理和模板有了一定的了解,也熟悉了语法的使用。本文有众多代码示例,可以尝试敲敲,运行查看结果,加深理解。

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

ee192b61bd234c87be9d198fb540140e.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【python笔记】datafram的时间动态可视化 pyecharts地图

import pandas as pd# 假设DataFrame是这样的&#xff1a; df pd.DataFrame({ year: [2014, 2015, 2016, 2014, 2015, 2016, 2014, 2015, 2016], province: [广东省, 广东省, 河南省, 湖南省, 北京市, 北京市, 上海市, 新疆维吾尔自治区, 上海市], values: [100, 150, 75…

井字棋源码(网络线程池版)

源码链接&#xff1a;game 效果可能没有那么好&#xff0c;大家可以给点建议。 效果展示 game.h #include <stdio.h> #include <stdlib.h> #include <time.h>#define ROW 3 #define COL 3void InitBoard(char board[ROW][COL], int row, int col) {int i…

如何在linux服务器上用Nginx部署Vue项目,以及如何部署springboot后端项目

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、打包Vue项目二、安装Nginx1.更新系统的软件包信息&#xff1a;2.安装Nginx&#xff1a;3.启动 Nginx 服务&#xff1a;安装完成后&#xff0c;Nginx 服务会…

C语言进阶:指针的进阶(上)

首先 在学习新知识之前 我们先来回顾下之前的学习的内容 1 指针是个变量 用来存放地址 地址唯一标识的一块内存空间 2 指针的大小是固定的4/8字节&#xff08;32位平台/64位平台&#xff09; 3 指针有类型的 指针的类型决定了两点 一个是指针操作的权限以及整数的步长 4 指针的…

「deepin生态共建小组」正式启动招募!三大生态共建项目,速来 !

基于社区开源精神&#xff0c;为提高大家对deepin生态建设的参与感&#xff0c;应用商店将正式开放众多软件给广大开源爱好者进行维护。参与小组工作可获得多项专属小组福利&#xff0c;工作项目分为玲珑格式迁移、wine应用打包、deb原生应用维护。 招募条件 1&#xff09;不限…

vivado Versal 串行 I/O 硬件调试流程、使用 Vivado Serial I/O Analyzer 来调试设计

Versal 串行 I/O 硬件调试流程 Versal ™ ACAP 无需再生成 IBERT IP &#xff0c; 因为使用系统内串行 I/O 调试所需的必要逻辑现已集成到 GTY 收发器架构内。使 用 GTY 收发器的任何设计均可用于串行 I/O 硬件调试。 Versal 串行 I/O 硬件调试流程具有 2 个不同阶…

蓝桥杯python考级整理

4_1:算术运算符 4_2:基本语法 4_3:基本语法 4_4:列表 4_5:函数 4_6:字符串 4_7:列表 4_8:逻辑运算符 4_9:字典 4_10:函数

CSS中的 5 类常见伪元素详解!

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

InternLM2-lesson5

目录 大模型部署挑战常用大模型部署方式模型剪枝(Pruning)知识蒸馏量化 LMDeploy核心功能性能表现支持部署的模型 作业配置 LMDeploy 运行环境以命令行方式与 InternLM2-Chat-1.8B 模型对话 大模型部署 大模型部署就是将大模型在特定的环境种运行&#xff01;可以部署到服务器…

day13 ts后端持久层框架(java转ts全栈/3R教室)

简介&#xff1a;如果说TS全栈后端开发最重要的两个框架&#xff0c;除了nestjs就是持久层框架了&#xff0c;这里主要看下Typeorm&#xff08;java中常用的就是mybatis&#xff0c;springdatajpa&#xff0c;hebernite了&#xff09; 先回顾下ORM的概念&#xff1a;ORM就是建…

好用的在线客服系统PHP源码(开源代码+终身使用+安装教程) 制作第一步

创建一个在线客服系统是一个涉及多个步骤的过程&#xff0c;包括前端界面设计、后端逻辑处理、数据库设计、用户认证、实时通信等多个方面。以下是使用PHP制作在线客服系统的第一步&#xff1a;需求分析和系统设计。演示&#xff1a;ym.fzapp.top 第一步&#xff1a;需求分析 确…

Linux:进程创建 进程终止

Linux&#xff1a;进程创建 & 进程终止 进程创建fork写时拷贝 进程终止退出码strerrorerrno 异常信号exit 进程创建 fork fork函数可以用于在程序内部创建子进程&#xff0c;其包含在头文件<unistd.h>中&#xff0c;直接调用fork()就可以创建子进程了。 示例代码&…

暴雨亮相CCBN2024 助力广电行业数智化转型

4月23日&#xff0c;第三十届中国国际广播电视信息网络展览会&#xff08;简称CCBN2024&#xff09;在北京开展&#xff0c;本次展览会由国家广播电视总局指导、广播电视科学研究院主办&#xff0c;作为国内广电视听领域首个综合性、专业化、引领性、国际化科技产业盛会&#x…

【树莓派】如何用电脑连接树莓派的远程桌面,灰屏解决

要使用VNC桌面连接到树莓派&#xff0c;你需要确保已经安装并启动了VNC服务器。以下是连接到树莓派的步骤&#xff1a; 在树莓派上启动VNC服务器&#xff1a; 打开终端或SSH连接到你的树莓派。输入以下命令以安装RealVNC的VNC服务器&#xff1a;sudo apt update sudo apt insta…

第十讲:C语言指针(4)

目录 1、回调函数是什么&#xff1f; 2、qsort使⽤举例 2.1、使⽤qsort函数排序整型数据 2.2、使⽤qsort排序结构数据 3、qsort函数的模拟实现 4、sizeof和strlen的对⽐ 4.1、sizeof 4.2、strlen 4.3、sizeof 和 strlen的对⽐ 5、数组和指针笔试题解析 5.1、⼀维数组…

java-反射

简介 获取class对象的API // 1. 通过类名.class Class<Student> clazz Student.class; System.out.println(clazz.getName());// 2. 通过Class.forName()方法 Class<?> clazz2 null; try {clazz2 Class.forName("com.reflect.Student");System.out.p…

B2B企业如何做好谷歌Google广告推广营销布局?

当今全球化的商业环境中&#xff0c;B2B企业要想在激烈的市场竞争中脱颖而出&#xff0c;拓展海外市场成为了必经之路。而谷歌Google广告&#xff0c;作为全球最大的在线广告平台&#xff0c;无疑是企业触达全球潜在客户的黄金钥匙。云衔科技通过专业服务助力企业轻松开户与高效…

【ai相关】人工智能的概念

一、人工智能的定义 人工智能&#xff0c;简称AI&#xff0c;是指由机器或计算机系统所展现出的类似于人类智能的行为和能力。其核心在于使机器能够像人类一样进行思考和行动&#xff0c;而这些思考和行动都是基于理性的决策和判断。 什么是机器学习&#xff1f; 机器学习的核…

【蓝桥杯省赛真题40】python摘苹果 中小学青少年组蓝桥杯比赛 算法思维python编程省赛真题解析

目录 python摘苹果 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python摘苹果 第十三届蓝桥杯青少年组python编程省赛真题 一、题目要求 &…

二维码如何分享照片?3步在线生成相册二维码

拍摄的照片怎样快速分享给其他人呢&#xff1f;传统的图片传输方式多通过微信、QQ、空间、微博等方式来实现分享&#xff0c;当需要分享给指定人员时或者需要分享的图片数量较多时&#xff0c;这些方式传递起来并不是特别的方便。想要实现大量图片的分享&#xff0c;选择生成相…