目录
一. 泛型编程
二. 函数模板
2.1 什么是函数模板
2.2 函数模板的实例化
2.2.1 函数模板的隐式实例化
2.2.1 函数模板的显示实例化
2.3 函数模板实例化的原理
2.4 模板函数调用实例化原则
三. 类模板
3.1 什么是类模板
3.2 类模板的实例化
泛型编程,就是编写与类型无关的通用代码,使同一段代码可以适用于所有类型的数据。如:编写一个Swap函数,使其可以实现对所有内置类型数据的交换、编写一个链表类List,使其能够适用于存储所有数据类型的链表。
之前,如果我们想要编写支持所有数据类型的Swap函数,就需要编写多个重载函数,如:Swap(int& x, int& y)、Swap(double& x, double& y)。但是,函数重载会造成大量的代码冗余,而良好的代码对冗余是非常忌讳的。为了避免代码冗余,实现编写一份代码就适用于所有类型的数据,C++引入了模板的概念,而模板就是实现泛型编程的基础。
C++中的模板分为函数模板和类模板,由于C语言不支持模板,所有C语言也不支持泛型编程。
函数模板,本质上来说就是一个函数家族,在调用函数时,根据函数模板实例化出具体的函数,函数模板与参数类型无关。
定义函数模板的格式:
template
函数返回值 函数名(参数列表) { }。
typename是函数模板关键字,可以使用class来替代,但不能使用struct来替代。
演示代码2.1以Swap函数为例,编写了一个Swap函数的函数模板,这个Swap函数可以对任何内置类型数据进行交换操作。
演示代码2.1:Swap函数模板
template
void Swap(T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
根据实例化函数模板时是否指定参数类型,可分为隐式实例化和显示实例化。
隐式实例化,就是在调用函数时,不指定函数参数和返回值的类型,通过传入的数据,让编译器自己去推断类型。
演示代码2.2以Swap函数为例,分别向函数中传入了两个int型数据和两个double型数据进行交换,输出交换后的结果。根据输出结果可见,Swap函数模板成功对int型数据和double型数据进行了交换,证实了函数模板的通用性。
演示代码2.2:
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.11, d2 = 20.22;
Swap(a1, a2);
cout << "a1 = " << a1 << ", " << "a2 = " << a2 << endl;
Swap(d1, d2);
cout << "d1 = " << d1 << ", " << "d2 = " << d2 << endl;
return 0;
}
假设,我们传入一个int型参数和一个double型参数,试图实例化函数模板Swap:Swap(a1, d2)。当编译器看到a1时,会将T推断为int型数据,当编译器看到d2时,会将T推断为double型数据。但是,由于函数的模板参数列表中只有一个T,而一个T不能表示两种类型的数据,因此,这里会编译失败。
如果我们确实希望通过演示代码2.1中定义的Swap函数模板,实现int型数据和double型数据之间的数据交换,有以下两种方式可以实现:
函数模板的显示实例化,就是通过在函数调用时,在函数名后面添加<模板参数类型>,显示地指定模板参数类型,从而省去了编译器自动推断模板参数类型的过程。
演示代码2.3定义了加法Add的函数模板,通过显示指定模板参数的类型为int和double进行调用,分别完成了整形数据和浮点型数据的加法操作。
演示代码2.3:
template
T Add(T x, T y)
{
return x + y;
}
int main()
{
int a = 10;
double d = 20.2;
cout << Add(a, d) << endl;
cout << Add(a, d) << endl;
return 0;
}
函数模板其实并不是一个具体的函数,它相当于浇注制造中用到的模具,可用于生成具体的函数,但其本身不是函数。当要通过函数模板调用函数时,我们会传入模板参数,编译器则会根据模板参数类型,利用函数模板实例化出具体的函数。
如Swap函数,我们先后用它来交换char类型、int型和double型数据,编译器就会生成3个重载函数:void Swap(char& x, char& y)、void Swap(int& x, int& y)和void Swap(double& x, double& y),这三个函数存储在内存中不同的位置。
类模板与函数模板类似,通过模板参数列表,来使类中的成员函数参数和成员变量可以为任何想要的数据类型。
类模板的定义格式:
template
class 类名
{
成员函数列表 ......
成员变量列表 ......
}
演示代码3.1以栈类为例,定义了一个可以实例化为存储任何数据类型的栈。其中包括4个显示定义的成员函数:构造、析构、压栈和打印,包括三个成员变量:指向存储数据内存区域的指针_a、栈顶下标_top、栈容量_capacity。
该栈模板类引入T作为模板参数,其中压栈函数的参数和成员变量_a均涉及参数T,在实例化时,T可以被解析为多种类型(如int、double、char等)。
抛开类模板的实际功能来讲,T还可以实例化为自定义类型,如果T被实例化为自定义类型,那么这创建模板类对象时,会去调用T的构造函数。
演示代码3.1:
template
class Stack //栈类
{
public:
Stack(int capacity = 10);
~Stack();
void Push(T x);
void Print();
private:
T* _a;
int _top;
int _capacity;
};
//类模板成员函数定义和声明分离
//函数返回值 类名<模板>::函数名(参数列表)
template
Stack::Stack(int capacity)
: _capacity(capacity)
, _top(0)
{
_a = new T[capacity];
}
template
Stack::~Stack()
{
delete[] _a;
_top = _capacity = 0;
}
template
void Stack::Push(T x)
{
_a[_top++] = x;
}
template
void Stack::Print()
{
int i = 0;
for (i = 0; i < _top; ++i)
{
std::cout << _a[i] << " ";
}
std::cout << std::endl;
}
与函数模板既可以显示实例化也可以隐式实例化不同,类模板只能显示实例化。即:告知编译器模板参数的类型。
类模板实例化格式:类名<模板参数类型> 对象名
演示代码3.2展示了对于栈类模板的实例化,分别实例化处一个int栈对象st1和double栈对象st2,对两个栈进行压栈操作并打印栈中数据,证实了该栈类模板能够适用于各种数据类型的栈。
演示代码3.2:
int main()
{
//函数模板可以显示调用也可以隐式调用
//但类模板只能显示调用
Stack st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Print();
Stack st2;
st2.Push(1.1);
st2.Push(2.2);
st2.Push(3.3);
st2.Print();
return 0;
}