目录
一. 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++内存分布
栈又叫堆栈——非静态局部变量/函数参数/返回值等等,栈是向下增长的。
内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux 课程如果没学到这块,现在只需要了解一下)
堆用于程序运行时动态内存分配,堆是可以上增长的。
数据段——存储全局数据和静态数据。
代码段——可执行的代码/只读常量。
二. 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 的原理
调用 operator new 函数申请空间
在申请的空间上执行构造函数,完成对象的构造
delete 的原理
在空间上执行析构函数,完成对象中资源的清理工作
调用 operator delete 函数释放对象的空间
new T[N] 的原理
调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请
在申请的空间上执行 N 次构造函数
delete[] 的原理
在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
调用 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 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc 和 free 是函数,new 和 delete 是操作符
malloc 申请的空间不会初始化,new 可以初始化
malloc 申请空间时,需要手动计算空间大小并传递,new 只需在其后跟上空间的类型即可,如果是多个对象,[] 中指定对象个数即可
malloc 的返回值为 void*,在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型
malloc 申请空间失败时,返回的是 NULL,因此使用时必须判空,new 不需要,但是 new 需要捕获异常
申请自定义类型对象时,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 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
- 隐式实例化:让编译器根据实参推演模板参数的实际类型
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; }- 显式实例化:在函数名后的<>中指定模板参数的实际类型
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 模板参数的匹配规则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
模板函数不允许自动类型转换,普通函数允许自动类型转
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