一、C++内存管理
在C语言中我们曾学习过动态内存管理的相关知识,通过malloc、calloc、realloc和free等对堆上的空间进行申请和释放。在C++中我们同样会面临类似的需求,因此C++对动态开辟内存的方式进行了一些调整,我们可以使用new和delete操作符来对堆空间进行空间管理。
①首先要明确的是new和delete的使用方法。new操作符后跟随需要开辟空间的类型,然后会返回一个指针。delete后跟随动态开辟出的指针并对其指向的空间进行释放。
②new可以在其后使用方括号来一次性申请多个同类型的空间,此时就是使用了操作符new[]。在释放空间的时候也应该对应匹配地使用delete[]来正确合理释放空间。
③new操作符在开辟空间时可以进行初始化,对于内置类型初始化值在类型后使用圆括号表示。对于一次申请了多个同类型空间,初始化使用大括号。如果没有大括号则认为没有初始化,得到的空间均为随机值;如果初始化了但不完全,那么没有初始化的部分会默认初始化为0。
④当new的delete的类型为自定义对象时,则会调用其对应的构造函数和析构函数。
class A
{
public:
A(int a, int b)
:_a(a)
,_b(b)
{
cout << "A(int, int)" << endl;
}
~A()
{
cout << "~A(int, int)" << endl;
}
private:
int _a;
int _b;
};
int main()
{
//对于内置类型:
int* p1 = new int; //动态申请int类型空间
int* p2 = new int(2); //使用圆括号动态申请的空间进行初始化
int* p3 = new int[4]; //使用方括号标识申请的数量
int* p4 = new int[6] {1, 2, 3}; //使用大括号进行初始化,不完全初始化下(new时使用了大括号),未初始化的空间默认为0
delete p1; //释放空间使用delete
delete p2;
delete[] p3; //释放new[]的空间,使用delete[]
delete[] p4;
//对于自定义类型
A* pa1 = new A(1, 2); //new自定义类型时会自动调用构造函数进行初始化
//A* pa1 = new A; //error,因为new自定义类型时会调用构造函数,A没有默认构造函数,所以会报错
A* pa2 = new A[4]{ {1,2},{2,3},{3,4},{4,5} }; //new多个自定义类型变量并初始化
delete pa1; //delete自定义类型时会自动调用析构函数进行销毁
delete[] pa2; //delete[]释放new[]的空间
//new的使用不需要进行检查,当出错时malloc会持续进行开辟空指针,new则会抛异常
return 0;
}
⑤使用new申请空间时不需要进行检查(在C语言中我们会对malloc返回的指针进行非空检查),因为当开辟空间出现问题时,new会直接抛异常而不是返回空指针。
⑥对于new和delete,这二者是提供给我们所使用的动态内存申请和释放的操作符,而实际上还有操作系统提供的operator new和operator delete两个全局函数。new在使用时调用了operator new来帮助自己进行空间申请,同样的delete调用了operator delete来进行释放空间。而operator new和operator delete两个函数的开辟空间和释放空间实际上又是通过调用malloc和free来实现的。
这样一层层的调用看似复杂,实际上是这样一层层的包含才有了我们可以方便使用,功能完善的new。
同理,对于new[]和delete[]而言,调用了operator new[]和operator delete[]两个函数。然后由operator new[]和operator delete[]调用了operator new和operator delete。再然后调用了malloc和free来实现。
二、模板
在以往写代码的时候,常常会遇到针对不同类型的功能相同的函数,这些函数代码相同区别只在于类型不同,但却因此要写好几份来满足我们的需求。C++中引入模板的概念来很好的解决这个问题。
1. 函数模板
通过提供一个函数模板,在后续使用函数的时候,只需要标明所需要的类型即可由编译器根据我们所写的模板生成对应的函数。
在定义函数模板时,需要使用template关键字,然后使用尖括号和关键字typename或class来指定模板参数,该参数在使用中会由编译器自动推演为合适的类型。具体的使用,也就是函数模板实例化有两种方式,由编译器自动推演或者直接显式实例化。
当存在函数的同时又存在模板的情况下,若参数匹配则会优先选择函数,否则会使用模板。如果采取显式实例化的方式则会强制使用模板。
//函数模板
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
//模板和函数可以同时存在,调用时优先选择函数
//如果函数不够匹配,则使用模板
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
template<class T1,class T2> //模板参数可以多个,以逗号分隔,关键字也可以是class
void func()
{}
int main()
{
int a1 = 10, a2 = 20;
cout << a1 << ' ' << a2 << endl;
Swap(a1, a2); //函数模板实例化,int类型 //使用函数
cout << a1 << ' ' << a2 << endl;
double d1 = 1.2, d2 = 3.7;
cout << d1 << ' ' << d2 << endl;
Swap(d1, d2); //函数模板实例化,double类型 //使用模板
cout << d1 << ' ' << d2 << endl;
Swap<double>(d1, d2); //函数模板显式实例化 //显示实例化强制使用模板
cout << d1 << ' ' << d2 << endl;
return 0;
}
2. 类模板
类模板和函数模板非常相似,在之后我们会频繁接触到类模板。需要注意的是类模板中的成员函数声明与定义分离时需要在定义处附加模板参数列表,并且二者必须在同一文件中。
//类模板
template<class T>
class Stack
{
public:
void Push(T x)
{}
private:
T* _a;
int _size;
int _capacity;
};
int main()
{
Stack<int> st1; //类模板实例化
Stack<double> st2;
return 0;
}