一,动态内存管理
(1)C语言和c++区别
在C语言中,我们常用malloc和calloc申请空间,用free来释放空间,而现在我们更多的情况下用new来申请空间,用delete来释放空间。
申请一个空间:
int* p2 = (int*)malloc(sizeof(int));//空间值为随机值
int* p3 = new int;//空间值为随机值
申请多个空间:
int* p4 = (int*)malloc(sizeof(int) * 10);
int* p5 = new int[10];
释放一个空间:
free(p2);
delete p3;
释放多个空间:
free(p4);
delete[ ] p5;
(2)申请空间的同时进行初始化:
int* p6 = new int(10);申请4个字节,一个空间,用()进行初始化
int* p7 = new int[10] {1, 2};申请多个空间,[ ]里面写申请空间的个数,{ }中,写初始化的值
int* p8 = new int[10] {};//默认为0,当写成这个样子的时候,默认是初始化为零,如果没有写{}则初始化为随机值(不同的编译器也有可能不同)
为什么出++中改用new来申请空间,而不用malloc?
答:因为malloc没有办法很好的支持动态申请的自定义对象初始化。
例如,用new来开辟自定义类型的空间并初始化
class A {
private: int _a;
public:
A(int a = 0)
:_a(a)
{}
}
A* P2 = new A;
A* P3 = new A(3);
delete P2;
delete P3;
A* P4 = new A[10];
delete[ ] P4;
将A类实例化为aa1,aa2,并开辟一块空间,将这两个对象放到里面:
A aa1(1);
A aa2(4);
A* P5 = new A[10]{ aa1,aa2 };
delete[] P5;
以上式子可以简写为以下代码:
A* P6 = new A[10]{ A(1),A(5) };
delete[] P6;
继续将其简化(运用:隐式类型转换):
A* P7 = new A[10]{ 1,5 };
delete[] P7;
——————————————————————————————
(3)new和delete
用stack类,来深入探究一下new和delete的用法:
class Stack {
private:
int* _arr;
int _top;
int _capacity;
public:
Stack(int capacity=4) {
int* _arr = new int[capacity];
_top = 0;
_capacity = capacity;
}
~Stack() {
delete _arr;
}
void push(int val) {
_arr[_top] = val;
_top++;
}
};
实例化一个对象,并将对象初始化为n
int n; cin >> n;
Stack* pst = new Stack(n);
由此可知:这里在栈上开辟了对象的空间,并调用了构造函数(开辟了数组的空间,也就是在堆上开辟了空间)
delete pst;
由此可知:调用了析构函数,释放了堆上的空间(数组的空间),并释放了栈上对象的空间
(4)operator new和operator delete
operator new的用法:
operator new的用法与malloc的用法相似,这时这里开辟空间失败时,不会返回null,而是直接抛异常
例如:Stack* pst = (Stack*)operator new (sizeof(Stack));
在执行这条代码时:int* arr = new int[10];会调用operator new[ ],它又会调用operator new。
注意:当申请一块空间时,用的是new ,与之相对的是delete,但对于多块空间来说,用的是new[ ],而与之相对的是delete [ ],在使用时要匹配的使用,不同的编译器对这两种方式有不同的处理方式,如果随意匹对使用,会在意想不到的地方冒出bug。
总结:要匹配使用
(5)new和operator new ,delete和operator delete联系
new和operator new 区别:new里面包含了operator new和构造函数
delete和operator delete:delete里面包含了operator delete和析构函数
一般来说:我们通常使用new和delete,operator new和operator delete一般是由编译器来使用的(我们使用new时,编译器再调用operator new)但是如果我们就是想用operator new(也就是说:想将开辟空间和实例化分开来进行)也可以由以下代码实现:
开辟空间:Stack* pst1 = (Stack*)operator new (sizeof(Stack));
调用构造函数:new(pst1)Stack(4);(这里用到了:显示调用构造函数)
注意:不可以写成pst1->Stack(4);来调用构造函数。但可以pst1->~Stack()来调用析构,operator delete(pst1);
pst1->~Stack();
operator delete(pst1);//这两步相当于直接调用delete
(6)总结:malloc和new,free和delete区别
1,malloc和free是函数,new和delete是操作符
2,malloc申请的空间不会初始化,new可以初始化
3,malloc需要手动计算空间的大小,new不需要
4,malloc申请失败会返回null,new抛异常
5,malloc的返回值是void*,所以必须要强转
6,new会调用构造函数,delete会调用析构函数
二,内存区间
·1,代码区(常量区):存放程序代码;
·2,全局变量与静态变量区(数据段):存放全局变量和静态变量以及对象;
·3,栈区(局部变量区):存放局部变量;
·4,堆区(自由存储区):存放动态分配对象。
动态:分配内存(运行时) 存储(堆区) 释放内存(手动释放)
静态:分配内存(编译时) 存储(栈区) 释放内存(自动释放)
小试牛刀:
int gg = 1;
static int ss = 1;
void test() {
static int aa = 1;
int bb = 1;
int num[10] = { 1,2,3,4 };
char char2[] = "abcd";
const char* pchar3 = "abcd";
int* p1 = (int*)malloc(sizeof(int) * 4);
free(p1);
}
————————————————————————————
gg 为全局变量,储存在静态区
ss 静态变量,在静态区
void test() {
aa静态变量,在静态区
bb为局部变量,储存在栈
num为局部变量,储存在栈
char2为局部变量,储存在栈(将常量区的abcd\0,拷贝到char2数组中)
*char2;//栈(数组名代表首元素地址,解引用,指向首元素)
const char* pchar3 = "abcd";//栈(变量为指针,存放地址,这个地址指向常量区的字符串)这个地址存放在栈上
*pchar3;//常量区
p1 //栈(指针)
*p1;//堆(在堆上开辟空间)
}
如果堆上的空间不手动进行释放,会引起内存泄漏,内存泄漏在长期运行的程序上危害极大
三,模板:
分为函数模版和类模板,模版的参数是一个泛型,也就是不同类型的参数可以调用同一个模版来实现功能,极大的简化了代码,和人工成本。编译器通过参数的不同,用模板实例化生成对应的函数或者类。
写法:template<typename/class T>放在函数或者类的前面
模板的参数可以有多个,例如:template<typename T1, typename T2>
函数模板:
template<typename T>
void Swap(T& a,T& b) {
T tmp = a;
a = b;
b = tmp;
}
类模板:
template<class T>
class Stack2 {
private:
T* _arr;
int _top;
int _capacity;
public:
Stack2(int capacity = 4) {
_arr = new T[capacity];
_top = 0;
_capacity = capacity;
}
~Stack2();
void push(const T& val) {
_arr[_top] = val;
_top++;
}
};
实例化的时候写法为,类型+引用,这里的类型不是类名,是类名<T>:
Stack2<int > s1;
Stack2<double > s1;
当模板只有一个参数,但参数确实不同的类型时,可以进行一下方法进行处理:
template<typename T >
T Add(const T& a, const T& b) {
return a + b;
}
cout << Add((int)2.8, 3) << endl;
cout << Add(2.8, (double )3) << endl;
cout << Add<int >(2.8, 3) << endl;
cout << Add<double >(2.8, 3) << endl;
多参数的模板:
template<class T,int n>
class Num {
private:
T arr[n];
public:
Num(T s[]) {
for (size_t i = 0; i < n; i++)
{
arr[i] = s[i];
}
}
};
int arr[7] = { 1,2,3,4,5,6,7 };
Num<int,7> num1(arr);//这样进行初始化