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类型的空间并初始化为10int* 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)); // Cint* 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 bytesvoid* 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_FINALLYreturn;
}

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; // intStack<double> st1; // doublereturn 0;
}


总结

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

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

ee192b61bd234c87be9d198fb540140e.png

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

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

相关文章

ERROR: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

今天本来想在A服务器上传文件给B服务器的结果发现明明给root用户设置了密码就是远程登陆不了&#xff0c;后来才发现在容器中很多服务都是没有的&#xff0c;所以刚安装后忘记了修改配置文件&#xff0c;导致远程登陆失败。 报错&#xff1a; 解决方法&#xff1a; 在/etc/ssh…

申请高德地图,报错INVALID_USER_SCODE处理

配置上key后 报错&#xff1a; 解决&#xff1a;将应用类型修改为出行&#xff0c;问题解决 扩展&#xff1a;应用申请 进入应用管理&#xff0c;创建新应用&#xff08;这里我选了导航&#xff0c;就报了上边的错误&#xff09; 新应用中添加 key&#xff0c;服务平台选择…

static和extern关键字详解

目录 创作不易&#xff0c;如对您有帮助&#xff0c;还望一键三连&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 回顾 1.作用域和声明周期 1.1作用域 1.2生命周期 2.static和extern 2.1extern 2.2static 2.2-1static修饰局部变量 2.2-2static修饰全局变量 创…

解决在服务器中减少删除大文件夹耗时太久的问题

在数据驱动的现代商业环境中&#xff0c;企业对服务器的高效运作有着极高的依赖性。然而&#xff0c;IT管理员们常常面临一个棘手的问题&#xff1a;删除服务器上的大型文件夹过程缓慢&#xff0c;这不仅降低了工作效率&#xff0c;还可能对用户体验造成负面影响。本文将介绍一…

2024 年 Rust 开发者路线图

Rust 近年来因其对性能、安全性和并发性的关注而广受欢迎。作为一名开发人员&#xff0c;掌握 Rust 可以为各种机会打开大门&#xff0c;包括 Web 开发。 在 github 上发现了这个优秀的路线图&#xff0c;由 Anshul Goyal 创建&#xff0c;它提供了一条全面的路径&#xff0c;概…

MIEC CS172(Prolog)

Chapter 1 and 2 Fact Facts: Facts are statements that areassumed to be true. The dot ‘.’ character must come at the end of a fact. Example: We want to tell “John likes Mary” : English interpretation The standard form of fact in Prolog Likes (john, ma…

怎么用AI绘画进行人物修复?

用过AI绘画生成人物图片的朋友们是不是都碰到过这样的问题&#xff1a;诡异的造型、崩坏的五官、离谱的手指头、乱七八糟的背景...指望AI一次性生成百分百完美的图貌似有点难啊。 现在AI绘画有了【脸部修复】【手部修复】功能&#xff0c;就能够轻松解决这些的问题了&#xff0…

Facebook的时间机器:回溯社交媒体的历史

1. 社交媒体的起源与早期模式 社交媒体的历史可以追溯到互联网的早期发展阶段。在Web 1.0时代&#xff0c;互联网主要是一个信息发布平台&#xff0c;用户主要是被动地接收信息。但随着Web 2.0的兴起&#xff0c;互联网逐渐转变为一个互动和参与的平台&#xff0c;社交媒体应运…

2024.4.23 关于 LoadRunner 性能测试工具详解 —— VUG

目录 引言 LoadRunner 三大组件之间的关系 LoadRunner 脚本录制 启动并访问 WebTours 脚本录制 编译 运行&#xff08;回放&#xff09; LoadRunner 脚本加强 事务插入 插入集合点 插入检查点 参数化 ​编辑 打印日志 引言 问题&#xff1a; 此处为啥选择使用 Lo…

JdbcTemplate详解

1 概述 为了使JDBC更加易于使用&#xff0c;Spring在JDBC API上定义了一个抽象层&#xff0c;以此建立一个JDBC存取框架。 作为Spring JDBC框架的核心&#xff0c;JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法&#xff0c;通过这种方式&#xff0c;可以在尽可能保留…

Linux安装Docker的多版本PHP和多版本MySQL共存

1: 先安装docker 安装完后执行,权限设置 sudo usermod -aG docker $USER或者sudo usermod -aG docker kentrl#添加当前用户到Docker用户组中 sudo newgrp docker#更新用户组数据,必须执行否则无效 sudo systemctl restart docker 先看目录结构: 2:按照目录结构挂载磁盘,…

【Qt常用控件】—— QWidget 核心属性

目录 &#xff08;一&#xff09;控件概述 1.1 关于控件体系的发展 &#xff08;二&#xff09;QWidget 核心属性 2.1 核心属性概览 2.2 enabled 2.3 geometry 2.4 windowTitle 2.5 windowIcon 2.6 windowOpacity 2.7 cursor 2.8 font 2.9 toolTip 2.10 focus…

Esko Ukkonen: On-line Construction of Suffix Trees

Esko Ukkonen: On-line Construction of Suffix Trees 文章目录 Esko Ukkonen: On-line Construction of Suffix Trees一、后缀树的概念及应用【详见刘方州同学报告】1.1 字典树 Trie1.2 后缀树 Suffix Tree2 后缀树的应用 二、朴素后缀树构造方法及问题三、线性时间内后缀树在…

怎么办xgp会员一年多少钱xgp会员怎么开轻松教你xgp会员开通教程

怎么办&#xff1f;xgp会员一年多少钱&#xff1f;xgp会员怎么开&#xff1f;轻松教你xgp会员开通教程 XGP平台是由微软公司开发的xbox游戏平台的pc版本&#xff0c;为电脑玩家提供了一个游玩微软游戏的平台&#xff0c;XGP平台因其独特的会员服务而广受玩家们好评&#xff0…

《深度学习在医学图像分析中的应用(第二版)》

书籍&#xff1a;Deep Learning for Medical Image Analysis, 2nd Edition 作者&#xff1a;S. Kevin Zhou&#xff0c;Hayit Greenspan&#xff0c;Dinggang Shen 出版&#xff1a;Academic Press书籍下载-《深度学习在医学图像分析中的应用&#xff08;第二版&#xff09;》本…

采用php vue2 开发的一套医院安全(不良)事件管理系统源码(可自动生成鱼骨图)

采用php vue2 开发的一套医院安全&#xff08;不良&#xff09;事件管理系统源码&#xff08;可自动生成鱼骨图&#xff09; 医院安全&#xff08;不良&#xff09;事件管理系统采用无责的、自愿的填报不良事件方式&#xff0c;有效地减轻医护人员的思想压力&#xff0c;以事件…

Linux开发板配置静态IP

1、查看网口信息&#xff0c;易知eth0无IP地址 ifconfig2、首先分配一个IP地址 sudo ifconfig eth0 192.168.5.8 up3、此时配置的IP地址只是临时的&#xff0c;当你reboot重启板子上电后&#xff0c;ip地址会消失&#xff0c;因此需要为板子配置静态ip&#xff0c;避免每次上…

一次违法网站的渗透经历

0x01 前言 在一次攻防演练中&#xff0c;我发现了一个有趣的渗透路径。在信息收集阶段&#xff0c;我注意到目标网站和用户资产网站共享相同的IP网段。这意味着它们可能在同一台服务器上托管&#xff0c;或者至少由同一家互联网服务提供商管理。这种情况为我们的渗透测试提供了…

【window环境、Linux环境、QT三种方法实现TCP通信】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Windows环境下实现TCP通信1.服务器2.客户端3.运行 二、Linux环境下实现TCP通信1.服务端2.客户端 三、Qt实现TCP通信1.服务端1.客户端 总结 前言 大多数项目…

告别SQL注入攻击之扰!揭秘强大防护策略,筑牢网站安全防线,畅享无忧体验!

SQL注入攻击是一种极具破坏性的Web漏洞&#xff0c;它利用应用程序对用户输入的处理不当&#xff0c;让恶意用户能够执行非授权的SQL查询&#xff0c;进而对数据库造成巨大损害。这种攻击方式可能导致数据泄露、系统崩溃等严重后果&#xff0c;因此必须引起高度重视。 为了有效…