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

C++内存管理,模板初阶(泛型编程)

文章目录

  • 变量在内存中的位置
  • C++内存管理方式
    • new和delete
      • 实现一个链表
    • operator new和operator delete
    • new和delete的实现原理
      • 内置类型
      • 自定义类型
    • 定位new表达式
  • malloc/free和new/delete的区别
  • 泛型编程
    • 隐式/显示实例化
    • 类模板

变量在内存中的位置

  先来一段代码,思考一下各个变量存在栈,堆,数据段(静态区),代码段(常量区)中哪一个区域。

#include<iostream>
using namespace std;
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(ptr2);
}

答:globalVar 存储在数据段(静态区)中
staticGlobalVar 存储在数据段(静态区)中
staticvar 存储在数据段(静态区)中
localvar存储在栈中
num1 存储在栈中
char2 存储在栈中
*char2 存储在栈中
pchar3 存储在栈中
*pchar3代码段(常量区)
ptr1 存储在栈中
*ptr1 存储在堆中

#include<iostream>
using namespace std;
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";//char2 栈     //*char2 栈  const char* pchar3 = "abcd";//pchar3 栈        //*pchar3代码段(常量区)int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1 栈    //*ptr1 堆int* ptr2 = (int*)calloc(4, sizeof(int));//realloc分为异地扩容和同地扩容//异地扩容,会提前释放掉ptr2,所以我们不用管ptr2//同地扩容,ptr3指向的就是ptr2,只需要手动释放ptr3即可int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}

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

C++内存管理方式

new和delete

class A {
public:A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}~A() {}
private:int _a1;int _a2;
};
void Test2() {A* p1 = new A;A* p2 = new A(1,1);A* p3 = new A[3]; //会调用3次构造函数A aa1(1, 1);A aa2(2, 2);A aa3(3, 3);A* p4 = new A[3]{ aa1,aa2,aa3 };//匿名对象A* p5 = new A[3]{ A(1,1),A(2,2),A(3,3) };//隐式类型转换A* p6 = new A[3]{ {1,1},{2,2},{3,3} };delete p1;delete p2;}

  1.通过new和delete操作符进行动态内存管理。
  2.使用new的时候可以进行初始化。
  3.申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。
  4.new/delete对于自定以类型除了开空间还会调用构造函数和析构函数。malloc和free不会调用构造函数和析构函数。

void Test1() {//动态申请一个int类型的空间int* p1 = new int;//动态申请一个int类型的空间并初始化为20int* p11 = new int(20);//动态申请10个int类型的空间int* p2 = new int[10];delete p1;delete p11;//相匹配,申请多少释放多少吧delete[] p2;//初始化int* p3 = new int(0);int* p4 = new int[10] {0};delete p3;delete[] p4;
}

实现一个链表

struct ListNode {int val;ListNode* next;ListNode(int x):val(x), next(nullptr){}
};void Test3() {ListNode* p1 = new ListNode(1);ListNode* p2 = new ListNode(1);ListNode* p3 = new ListNode(1);ListNode* p4 = new ListNode(1);p1->next = p2;p2->next = p3;p3->next = p4;
}

operator new和operator delete

  1.new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
  2.operator new实际也是通过malloc来省钱空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常,operator delete最终通过free来释放空间。

int main() {try {char* p1 = new char[1024 * 1024 * 1024];char* p2 = new char[1024 * 1024 * 1024];char* p3 = new char[1024 * 1024 * 1024];}catch (const exception& e) {cout << e.what() << endl;}return 0;
}

new和delete的实现原理

内置类型

  1.如果申请的内置类型的空间,new和malloc,delete和free基本类似。不同点:new/delete申请和释放的单个元素的空间,new[]/delete[]申请和释放的多个元素的空间,new在申请空间失败的时候会抛异常,malloc会返回NULL.

自定义类型

1.new
  调用operator new 函数申请空间。
  在申请的空间上执行构造函数,完成对象的构造。
2.delete
  在空间上执行析构函数,完成对象中资源的清理工作。
  调用operator delete函数释放对象的空间。
3.new T[N]
  调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。
  在申请的空间上执行N次构造函数。
4.delete[]
  在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
  调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。
一定要匹配使用

class B {
private:int _b1;
};
class C {
public:~C() {cout << "~C()" << endl;}
private:int _c1;
};
void Test5() {//此时在B类中,没有析构函数,编译器检测后,不需要生成析构函数,直接delete即可。B* p1 = new B[10];delete p1;//此时C类中有析构函数,会在生成一块空间,进行存储创建了几个p2,在delete的时候进行相应的析构C* p2 = new C[10];delete[] p2;
}

定位new表达式

定位new表达式在实际中一般配合内存池使用,因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

class A {
public:A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}~A() {}
private:int _a1;int _a2;
};
void Test6() {A* p1 = new A(1);//同mallocA* p2 = (A*)operator new(sizeof(A)); //没有初始化//显示调用构造函数初始化new(p2)A(1);delete p1;p2->~A();operator delete(p2);
}

malloc/free和new/delete的区别

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

泛型编程

现在我想实现两个变量的交换,这时候要怎么实现函数呢?
关键字:class , typename

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

是按照上述的函数,例举每一个类型的函数吗?这太麻烦了,我们引出泛型编程。

//模板实例化
template<class T>
void Swap(T& t1, T& t2) {T tmp = t1;t1 = t2;t2 = tmp;
}

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的摸具。

隐式/显示实例化

T Add(const T& left, const T& right) {return left + right;
}
void Test() {int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.5;//类型需要相同Add(a1, a2);Add(d1, d2);//推导实例化cout << Add(a1, (int)d1) << endl;cout << Add(double(a1), d1) << endl;//显示实例化cout << Add<int>(a1, d1) << endl;
}

类模板

  类模板实例化与函数实例化不同,类模板实例化需要在类模板名字后跟 <>,然后实例化放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

template<class T>
class Stack {
public:Stack(int n = 4):_array(new T[n]),_size = 0, _capacity = 4{}void Push(const T& x) {if (_size == _capacity) {//扩容T* tmp = new T[_capacity * 2];//拷贝memcpy(tmp, _array, sizof(T) * _size);delete[] _array;_array = tmp;_capacity *= 2;}_array[_size++] = x;}void Pop() {--_size;}~Stack() {delete[] _array;_array = nullptr;_size = _capacity = 0;}private:T* _array;size_t _capacity;size_t _size;
};void Test3() {//需要显示实例化Stack<int> st1;st1.Push(1);
}

在这里插入图片描述
觉得我回答有用的话,记得点个关注哟!谢谢支持!

http://www.dtcms.com/a/362801.html

相关文章:

  • 项目组文档标准不一致的原因有哪些
  • 设计模式:命令模式(Command Pattern)
  • 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
  • java-设计模式-3-创建型模式-原型
  • GPT-5 技术应用指南:从底层能力到工业级落地
  • 零基础Linux操作基础小白快速掌握Shell脚本bash的配置文件
  • PHP操作LibreOffice将替换变量后的word文件转换为PDF文件
  • CICD的持续集成与持续交付和Zabbix
  • Rsync + Rsyncd 从入门到项目实战:自动化备份全攻略
  • 系统配置不是“乐高积木”:制造企业如何通过科学变更管理保障稳定运行
  • 关于ANDROUD APPIUM安装细则
  • 科研绘图(二):R 语言实现小鼠脑图谱 3D 渲染,附完整代码与数据获取指南
  • LoRaWAN®协议,如何为工业制造的数字化转型赋能?
  • 《CrystalDiskInfo》 [9.7.2] [单文件版] 下载
  • CHT共轭传热: 导热系数差异如何影响矩阵系数
  • 从0死磕全栈第2天:Vite + React 配置全解析,让你的开发效率飞起来
  • Element-Plus 入门指南
  • 跳出“中央集权”的泥潭:以Data Mesh重构AI时代的活性数据治理
  • MongoDb(②pymongo)
  • 豪华酒店品牌自营APP差异对比分析到产品重构
  • 腾讯混元世界模型Voyager开源:单图生成3D世界的“核弹级”突破,游戏、VR、自动驾驶迎来新变量
  • C++ 面试高频考点 力扣 852. 山脉数组的峰顶索引 二分查找 题解 每日一题
  • ansible循环
  • GitHub Classroom:编程教育的高效协作方案
  • 从零开始的云计算生活——第五十七天,蓄势待发,DevOps模块
  • 数据量太大处理不了?Hadoop+Spark轻松解决海洋气象大数据分析难题
  • HQX SELinux 权限问题分析与解决
  • 使用 Avidemux 去除视频的重复帧
  • 亚马逊美加站点物流新规解读:库存处理逻辑重构与卖家应对策略
  • 两台电脑通过网线直连共享数据,设置正确,却互相ping不通的解决方法