目录
函数模板
函数模板的格式:
函数模板的实例化:
模板参数的匹配原则
类模板
类模板的格式:
类模板的实例化:
非类型模板参数
我们为什么要使用模板?
C++使用模板的目的是为了解决C语言中无法解决的一个关键问题。假如在C语言中我们在一个源文件里面写了一个栈,现在要求你使用同一个栈的类型去创建2个栈,一个栈中的数据存储int类型,另一个存储double类型。你会发现你没办法实现这个功能。然而在C++中,我们可以通过使用类模板,同时实例化出一个int类型的栈和一个double类型的栈。除此之外,使用模板是符合泛型编程的理念的。
模板是干什么的?
模板就是如字面意思一样,它只是一个模板,但是当我们使用函数模板/类模板去实例化对象的时候,编译器会根据你在实例化时给的类型去自动生成对应类型的函数/类对象让你去使用。举个例子,我们比如要实现1对整形数据的交换(swap),1对浮点类型的数据的交换和1对字符型数据的交换。此时我们需要写3个不同的swap函数去分别实现这三次调用。这样的代码看起来过于冗余了,并且这样的代码维护性也较差。此时我们就可以实现一个函数模板,当我们要进行整形数据的交换时,编译器会自动根据我们传的类型去实例化出一个整形的swap函数。
//不使用函数模板时,我们需要自己写3个不同的函数
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void Swap(double& x, double& y)
{
double tmp = x;
x = y;
y = tmp;
}
void Swap(char& x, char& y)
{
char tmp = x;
x = y;
y = tmp;
}
//使用函数模板
template
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
函数模板不是一个函数而是一个模板。函数模板与类型无关,也就是说我们在写函数模板时写与类型无关的代码。函数模板在使用时才会被实例化,并且它是根据具体的实参类型去实例化不同的函数模板的。
template
函数的实现
注意:class是用来定义模板参数的关键字,它也可以用typename取代。(但是不能用struct)
template //template
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
当你用不同类型的参数去调用该函数模板时,称为函数模板的实例化。下图就是3种不同类型的参数去调用同一个函数模板,从而实例化出了3个不同的函数。
模板参数实例化还可以分为:隐式实例化和显示实例化。
隐式实例化:就是编译器根据你所给的实参去推演模板参数的实际类型。
显式实例化:在函数名的后面<>中去指定模板参数的类型。
template
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 1;
int b = 2;
double c = 1.1;
double d = 1.2;
//隐式实例化
Swap(a, b);
Swap(c, d);
//显式实例化
Swap(a, b);
Swap(c, d);
return 0;
}
注意:
对于隐式实例化,只允许传同类型的变量 (当只有一个模板T时)。对于上面的例子,我们如果写 Swap(a, c); 的话,在编译期间,编译器通过a去推到T为int,通过c去推到T为double,这会导致编译器无法确定T的类型,因此会报错。 解决方法:可以写Swap(a, (int)c); 我们将c强转为int类型,这样就没问题了。或者我们去使用显式实例化,Swap
一个非模板函数可以和一个同名的函数模板同时存在,那么什么情况下会调用非模板函数?什么情况下会调用函数模板?
调用非模板函数:
当你调用该函数的类型与非模板函数的类型相同时,编译器会优先调用非模板函数。
调用函数模板:
如果没有你调用该函数类型的非模板函数时,编译器就会去调用函数模板从而实例化出你指定的类型的函数。如果非模板函数的类型是你想调用的类型,但是你又不想调用非模板函数,你可以使用<>来显式实例化函数模板,此时编译器就会调用函数模板而不是非模板函数了。
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
cout << "非模板函数" << endl;
}
template
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
cout << "函数模板" << endl;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b); //调用的是非模板函数
Swap(a, b);//调用的是模板函数
return 0;
}
类模板的的使用方法和函数模板大相径庭。它同样不是一个类,而是一个模板,只有当使用该类去进行实例化对象的时候,才会生成对应类型的类。
template //模板参数
class className //类名
{
//类中的成员变量和成员函数
}
//具体例子
template //template
class Stack
{
public:
Stack() {};
private:
T* _a;
size_t _capacity;
size_t _size;
};
类模板实例化需要在类模板的名字后面加上<>,<>中指定具体的类型。
int main()
{
//类名中的T替换成具体的类型
Stack st1;
Stack st2;
return 0;
}
注意:
1. 类模板名不是真正的类,实例化的结果才是真正的类。
2. 当类模板中的函数在类的外面进行定义时,需要在每个函数的上面一行都加上参数列表,如:template
template
void Stack::push_back(const T& x)
{}
小技巧:你就把类名当做 vector
模板参数是什么?
模板参数就是template
模板参数的分类有哪些?
模板参数可以分为:类型形参 和 非类型形参
类型形参:在参数列表中,跟在class或typename后面的参数类型的名称。(上面的T1,T2就是)
非类型形参:用一个常量去作为类/函数模板的一个参数。 (下面的例子中的N就是)
//非类型模板参数
template //这里的T就是类型形参,N是非类型形参且N是个常量
class Array //C++11中Array就使用了非类型形参
{
public:
Array() {}
private:
T _a[N]; //静态数组
};
int main()
{
Array a; //创建了一个大小为10的整形数组
Array b; //创建了一个大小为15的浮点型数组
return 0;
}
注意:
1. 自定义类型、浮点型、string等类型是无法作为非类型的模板参数的
(通常情况下非类型模板参数使用的都是 字符型 或 整形)
2. 非类型的模板参数必须在编译期间就确定