news 2026/10/2 8:12:13

C/C++内存管理+模板初阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C/C++内存管理+模板初阶

目录

一. C/C++内存分布

二. C语言中动态内存管理方式

三. C++内存管理方式

3.1 new/delete 操作内置类型

3.2 new/delete 操作自定义类型

四. operator new与operator delete函数

五. new和delete的实现原理

5.1 内置类型

5.2 自定义类型

六. 定位new表达式(placement-new)

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

八. 泛型编程

九. 函数模板

9.1 函数模板的概念

9.2 函数模板格式

9.3 函数模板的实例化

9.4 模板参数的匹配规则

十. 类模板

10.1 类模板的定义格式

10.2 类模板的实例化


一. C/C++内存分布

  1. 栈又叫堆栈——非静态局部变量/函数参数/返回值等等,栈是向下增长的。

  2. 内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 课程如果没学到这块,现在只需要了解一下)

  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。

  4. 数据段——存储全局数据和静态数据。

  5. 代码段——可执行的代码/只读常量。

二. C语言中动态内存管理方式

三. C++内存管理方式

解答 C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

3.1 new/delete 操作内置类型

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; int main() { //动态申请一个int类型的空间 int* ptr4 = new int; //动态申请一个int类型的空间并初始化为10 int* ptr5 = new int(10); //动态申请10个int类型的空间 int* ptr6 = new int[10]; delete ptr4; delete ptr5; delete[] ptr6; return 0; }

3.2 new/delete 操作自定义类型

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class A { public: A(int a1 = 0,int a2 = 0) :_a1(a1) ,_a2(a2) { cout << "A(int a1 = 0,int a2 = 0)" << endl; } A(const A& aa) :_a1(aa._a1) ,_a2(aa._a2) { cout << "A(const A& aa)" << endl; } ~A() { this->_a1 = 0; cout << "~A()" << endl; } A& operator=(const A& aa) { this->_a1 = aa._a1; this->_a2 = aa._a2; cout << "A& operator=(const A& aa)" << endl; } private: int _a1; int _a2; }; int main() { A* p1 = new A; A* p2 = new A(1); A* p3 = new A(2, 3); A aa1(1, 2); A aa2(2, 3); A aa3(3, 4); //1.隐式类型转换 A* p4 = new A[3]{ {1,2},{2,3},{4,5} }; //2.匿名对象 A* p4 = new A[3]{ A(1,2),A(2,3),A(4,5) }; //3.先构造对象再作参数 A* p4 = new A[3]{ aa1,aa2,aa3 }; delete p1; delete p2; delete p3; delete[] p4; return 0; }

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

四. operator new与operator delete函数

五. new和delete的实现原理

5.1 内置类型

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

5.2 自定义类型

  • new 的原理

    1. 调用 operator new 函数申请空间

    2. 在申请的空间上执行构造函数,完成对象的构造

  • delete 的原理

    1. 在空间上执行析构函数,完成对象中资源的清理工作

    2. 调用 operator delete 函数释放对象的空间

  • new T[N] 的原理

    1. 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请

    2. 在申请的空间上执行 N 次构造函数

  • delete[] 的原理

    1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理

    2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间

class B { public: B(int a1=10) :_a1(a1) { cout << "B(int a1=10)" << endl; } ~B() { cout << "~B()" << endl; } private: int _a1; }; int main() { int* AA = new int[10]; delete AA; B* _AA = new B[10]; delete _AA; return 0; }

对于内置类型数组,比如 int* p = new int[10]; 如果误用 delete p,在 VS 下可能不报错,也可能看不出内存泄漏,因为 int 不涉及析构。但从 C++ 标准上说,new[] 必须配 delete[],否则是未定义行为。

对于类类型数组,如果类型需要非平凡析构,编译器可能会在数组前面额外存储元素个数。delete[] 会利用这些信息调用多次析构,并释放正确的整块空间。如果误用 delete,可能只析构一个对象,并且把错误的位置交给 operator delete,导致报错或堆损坏。

六. 定位new表达式(placement-new)

定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type 或者 new (place_address) type(initializer-list)

place_address 必须是一个指针,initializer-list 是类型的初始化列表

使用场景:

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

#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class B { public: B(int a1=10) :_a1(a1) { cout << "B(int a1=10)" << endl; } ~B() { cout << "~B()" << endl; } private: int _a1; }; int main() { //1 B* p1 = new B(1); delete p1; //2 B* p2 = (B*)operator new(sizeof(B)); new(p2)B(1); p2->~B(); operator delete(p2); return 0; }

七. malloc/free和new/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 在释放空间前会调用析构函数完成空间中资源的清理释放

八. 泛型编程

九. 函数模板

9.1 函数模板的概念

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

9.2 函数模板格式

template<typename T1,typename T2,......,typename Tn>

返回类型 函数名(参数列表){}

template<typename T1,typename T2> void func(const T1& x, const T2& y) { T1 tmp = x; x = y; y = tmp; }

注意:typename是用来定义模板参数关键字,也可以使用 class(切记:不能使用 struct 代替 class)

9.3 函数模板的实例化

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

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template<typename T1> T1 func(const T1& x, const T1& y) { return x + y; } int main() { int a1 = 10, a2 = 20; double b1 = 10.0, b2 = 20.0; func(a1, a2); func(b1, b2); //类型不统一,办法1->强制类型转换 cout << func(a1,(int) b1) << endl; return 0; }
  1. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template<typename T1> T1 func(const T1& x, const T1& y) { return x + y; } int main() { int a1 = 10, a2 = 20; double b1 = 10.0, b2 = 20.0; func(a1, a2); func(b1, b2); //类型不统一,办法1->强制类型转换 cout << func(a1,(int) b1) << endl; //办法二->显示实例化 cout << func<int>(a1, b1) << endl; return 0; }

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

9.4 模板参数的匹配规则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

  3. 模板函数不允许自动类型转换,普通函数允许自动类型转

template<typename T1> T1 func(const T1& x, const T1& y) { return x + y; } int func(const int& x, const int& y) { return (x + y) * 10; } int main() { int a1 = 10, a2 = 20; cout << func(a1, a2) << endl; return 0; }

十. 类模板

10.1 类模板的定义格式

template<class T1,class T2,.......class Tn> class 类模板名 { //类内成员定义 };
template<class T> class stack { public: stack(int n=4) :_array(new T[n]) ,_size(0) ,_capacity(n) {} ~stack() { delete[] this->_array; this->_array = nullptr; this->_size = this->_capacity = 0; } void Push(const T& x); private: T* _array; int _capacity; int _size; }; template<class T> void stack<T>::Push(const T& x) { if (_size == _capacity) { int _newcapcity = 2 * _capacity; T* tmp = new T[_newcapcity]; memcpy(tmp, _array, sizeof(T) * _size); delete[] _array; _array = tmp; tmp = nullptr; _capacity = _newcapcity; } _array[_size] = x; _size++; } int main() { //类模板都是显式实例化 stack<int> st1; st1.Push(1); st1.Push(2); st1.Push(3); stack<double> st1; st1.Push(1.1); st1.Push(2.2); st1.Push(3.3); return 0; }

10.2 类模板的实例化

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

//stack是类名,stack<int>才是类型 stack<int> st1; // int stack<double> st2; // double
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 8:09:49

Agent循环的隐形代价:Strands Harness SDK如何把生产级封装成一行代码

做 AI 应用这些年&#xff0c;我越来越确认一个反直觉的结论&#xff1a;Agent 项目里最贵的东西不是模型 token&#xff0c;而是那套没多少人爱写、又不得不写的 Agent 循环。过去大半年&#xff0c;我前后手写过五个循环&#xff0c;每一个都从“无非是个 while 加几次模型调…

作者头像 李华
网站建设 2026/10/2 8:09:05

GNU Radio定时环路实战:精准锁定P201Pro采样时刻

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 8:08:05

从零构建AI工程:超越模型训练的系统化实践

1. 为什么“从零构建AI工程”不是写个模型就完事了“AI Engineering from Scratch”这个标题&#xff0c;乍看像极了某本技术书的副标题&#xff0c;或者某个开源项目的README第一行。但如果你真照着字面意思去干——下载PyTorch、抄一段ResNet代码、跑通MNIST&#xff0c;然后…

作者头像 李华
网站建设 2026/10/2 8:06:17

ArkUI动画实战:属性动画与转场动画从入门到进阶

这是我自己在整理鸿蒙中级课程笔记时一直想写的一篇——ArkUI 进阶的第一课&#xff0c;属性动画和转场动画。学鸿蒙开发有一段时间的朋友大概都有这种感觉&#xff1a;基础组件、布局写顺手之后&#xff0c;界面总差点意思&#xff0c;点击按钮没有反馈&#xff0c;页面切换硬…

作者头像 李华