模板是c++泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板是创建泛型类或函数的蓝图或公式。比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。
就以容器为例,每个容器都有一个单一的定义,比如 vector
模板的使用方法:
template
//返回值 函数名(参数列表)
{
//函数体
}
我们以下面几个名词来说明函数模板的特性。
1.函数模板。
函数模板,就是指一个模板,这个模板用于生成一个函数。
2.模板函数。
模板函数,指一个函数,这个函数用模板来定义,支持多种类型。
3.模板的实例化。
模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。
4.模板的实参推演。
当函数模板被调用时对函数实参类型的检查决定了模板实参的类型和值这个过程被称为模板实参推演。注意事项:1.不能让编译器产生二义性。 2.必须对函数传有实参。
在对模板函数传递实参的过程中,可以指定实参类型比如sum
#include
template
T sum(T a, T b)
{
return a+b;
}
int main()
{
std::cout<(10,20)<
5.模板的特例化(专用化)
虽说模板可以满足泛型编程,但是在某些特殊的情况下,对于某些特定的类型,不能像大部分类型一样处理,就需要将模板特例化来处理这个问题。特例化也分为:1.完全特例化也称全特化,是指特例化具体到某一个类型。2.部分特例化也称偏特化,指特例化只具体到一部分类型。
当某些类型同时满足特例化版本与普通模板版本时,会优先调用特例化版本,这说明特例化版本的优先级比普通的模板版本的优先级高。
在编写特例化版本的模板函数时,应当注意特例化版本应该符合普通模板的实例化逻辑。
template
bool Compare(const T a, const T b)
{
std::cout << "bool Compare(T,T) :" << typeid(T).name() << std::endl;
return a > b;
}
template<>
bool Compare(char*const a, char*const b)
{
std::cout << "bool Compare(char*,char*) :" << typeid(a).name() << std::endl;
return strcmp(a, b) > 0;
}
int main()
{
Compare(10, 20);
Compare(10, 20);
Compare(10.1, 20.1);
Compare("hello", "world");
return 0;
}
6.模板的类型参数。
模板的类型参数用template
模板的类型参数,故名思意,就是使用模板时,将类型当作参数传递给模板。比如上面例子中的 T ,E都是模板的类型参数。
7.模板的非类型参数。
对于函数模板与类模板,模板参数并不局限于类型,模板除了定义类型参数,我们还可以在模板定义非类型参数。什么是非类型形参。顾名思义,就是表示一个固定类型的常量而不是一个类型。固定类型是有局限的,只有整形,指针和引用才能作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。
非类型形参的局限:
1).浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数。
2).类不可以作为非类型形参。
3).字符串不可以作为非类型形参
4).整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参)
5).指向对象或函数的指针与引用(左值引用)可以作为形参
非类型实参的局限:
1).实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址及动态对象
2).非Const的全局指针,全局对象,全局变量(下面可能有个特例)都不是常量表达式。
3).由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参
备注:常量表达式基本上是字面值以及const修饰的变量
template
void Sort1(T arr[])
{
//LEN = 10;
T tmp = T();
for (int i = 0; i < LEN - 1; i++)
{
for (int j = 0; j < LEN - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
template
void Show(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main()
{
int arr[] = { 21, 32, 43, 234, 23, 42, 4212, 3 };
int len = sizeof(arr) / sizeof(arr[0]);
Show(arr, len);
Sort(arr, len);
Show(arr, len);
return 0;
}
8.模板的默认值。
模板的类型参数列表不仅仅只有自动推演实参类型的功能,也可以对其进行赋默认值的操作。但是值得注意的是函数模板的默认值与类模板大的默认值有一些区别。
函数模板的默认值有以下几个特点:
1).优先使用参数推演,即如果函数有实参推演 ,编译器就不会使用模板默认值,反之则使用。
2).函数模板默认值的赋值并不遵循函数默认值赋值自右向左依次赋予的规则 。但是类模板的默认值遵循自右向左依次赋予的规则。
template
C Add(A a, B b)
{
//C c = C();
std::cout << "A : " << typeid(A).name() << std::endl;
std::cout << "B : " << typeid(B).name() << std::endl;
std::cout << "C : " << typeid(C).name() << std::endl;
return a + b;
}
int main()
{
auto rt = Add(10.1, 20);
std::cout << "rt : " << typeid(rt).name() << std::endl;
return 0;
}
9.模板接收不明确类型的返回值。
模板可以接收不明确类型的返回值,在模板函数的返回值类型不明确的时候,可以用auto关键字来代替。auto具有自适应类型的功能。
10.模板的重载。
模板是可以进行重载的,比如普通函数版本,模板版本,模板的特例化版本,这三个版本之间可以进行重载,在重载的过程中模板的匹配是精确匹配的。
11.模板的显示实例化。
在test.cpp文件中建立一个函数模板,在main.cpp文件中调用,发现无法调用,这时候就需要对模板进行显示实例化。
当显式实例化模板时,在使用模板之前,编译器根据显式实例化指定的类型生成模板实例。其格式为:template int Sum
//test.cpp
template
T Sum(T a, T b)
{
return a + b;
}
template int Sum(int, int);