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);
}
觉得我回答有用的话,记得点个关注哟!谢谢支持!