在上一讲,我向大家介绍了C++内存管理以及new/delete的相关知识,在开启这一讲之前,我想先补充一点上一讲未交代完的知识点,replacement-new相关。
由于,现阶段知识储备有限,我先简单通俗的说一说内存池的概念,不会介绍太深。当需要动态申请内存时,我们需要到堆区申请,一次两次还好,频率过高难免有些浪费时间,这时候就有内存池出现了,所谓内存池你可以理解为它本来就预留了一些向堆区申请的空间,然后你就不用再去找堆区要了,直接让内存池给你。
简单介绍过后,我来介绍我们今天要讲的定位new表达式也就是replacement-new,它用于在已分配的原始内存空间中调用构造函数初始化对象,完美契合我们内存池的前提,因为内存池就是已分配的原始内存空间。
我简单演示一下用法。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 10)
:_a(a)
{
cout << "A(int a = 10)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* pa = (A*)malloc(sizeof(A));
new(pa)A(20);
pa->Print();
pa->~A();
free(pa);
return 0;
}

这段代码完美地为我们演示了什么叫多此一举......确实啊,你调用new/delete自动调用构造析构还整这么麻烦干啥呢?还是那句话,内存池可以用,现在确实展示不出来,只是为了让大家了解一下用法,显式调用构造函数一般我们也确实用不到。
那么接下来,我们正式进入第四讲模板的学习。
模板之于C++有着举足轻重的作用,以往我们写个Swap函数写个Add函数,你由于不同类型的考量需要写一堆,C++即使有重载也十分麻烦,明明大家都是干着差不多的任务,却要写好多次,代码十分冗余,模板就是来解决这个问题的。
cpp
template<class T>
void Swap(T& t1, T& t2)
{
T tmp = t1;
t1 = t2;
t2 = tmp;
}
int main()
{
int a = 10;
int b = 2;
double c = 1.1;
double d = 2.9;
Swap(a, b);
Swap(c, d);
return 0;
}

这是基本的语法,template<class T>其中class你可以写typename,T这个名字你随便。
看看这段代码,思考一个问题,传double和int的这两个Swap是一个函数吗?我可以很明确的告诉你,如果你调试,进的都是这个函数,那能证明这俩是一个函数吗?

看到了吧,这其实是两个函数,也就是说,函数模板的原理是根据预定模板生成对应函数,本来这个工作是你来完成的,但是由于工作比较简单,就交给编译器了,本质不变,仍然是两个函数,只是省事且不冗余,代码观感更好。当你传入int编译器会推断出相关类型执行相应操作。由函数模板生成对应函数的过程我们称为函数模板的实例化。
实例化分为隐式实例化和显式实例化两种,上边我为大家介绍的就是隐式实例化。
什么时候隐式实例化就不够了呢?
cpp
template<class T>
T Add(const T& t1, const T& t2)
{
return t1 + t2;
}
int main()
{
int a = 10;
int b = 2;
double c = 1.11;
double d = 2.9;
cout << Add(a, b) << endl;
cout << Add(c, d) << endl;
cout << Add(a, (int)c) << endl;
cout << Add<int>(a, c) << endl;
cout << Add<double>(a, c) << endl;
return 0;
}
你想要不同类型的变量相加,这个时候既定方式不管用了,在函数模板中,一般不会对类型进行隐式类型转换,所以要么就你手动转换类型要么就显式实例化。

还有一种情况,你必须显式实例化,当你没有传泛型参数的时候,编译器怎么知道你要用什么类型,所以你必须指定。

上述讲的都是只有一个模板参数的情况,但是现实中有多个模板参数的情况还是很常见的,毕竟你不能保证在一个函数里一直只用一种变量类型吧。
最简单的情况,Add可以实现一个整型加一个浮点数吧,你来回显式实例化,也很麻烦。

最后介绍一下函数模板的一些规则。
一个非模板函数可以和一个同名的函数模板同时存在,而且模板函数还可以实例化为该非模板函数。
如果非模板函数和模板函数其他条件都相同优先调用非模板函数。
模板函数内部不允许自动类型转换。
介绍完函数模板之后,还有类模板,接下来我介绍一下类模板。
cpp
template<class T>
class Stack
{
public:
Stack(int capacity = 4)
:_parr(new T[capacity])
,_capacity(capacity)
,_size(0)
{
}
void Push(const T& t);
private:
T* _parr;
int _capacity;
int _size;
};
template<class T>
void Stack<T>::Push(const T& t)
{
if (_capacity == _size)
{
//手动扩容
T* tmp = new T[2 * _capacity];
memcpy(tmp, _parr, sizeof(T) * _size);
delete[] _parr;
_parr = tmp;
_capacity *= 2;
}
_parr[_size] = t;
_size++;
}
int main()
{
Stack<int> s1;
Stack<double> s2;
return 0;
}
和函数模板不同的是,类模板必须要显式实例化,和它类似的是,Stack<int>和Stack<double>不是同一种类型。
值得注意的是,C++需要手动扩容,但是现在没办法说为什么,请期待后续讲解。