问题聚焦:
泛型编程是独立于变量类型的方式编写代码;
模板是泛型编程的基础。
本篇主要介绍模板的基础知识,包括:模板的定义和模板的实例化。
1 模版定义
必要性:
Demo
int compare(const string &v1, const string &v2)
{
if ( v1 < v2 ) return -1;
if ( v2 < v1 ) return 1;
return 0;
}
int compare(const double &v1, const double &v2)
{
if ( v1 < v2 ) return -1;
if ( v2 < v1 ) return 1;
return 0;
}
很明显地看到,上面的两个函数几乎相同,事实上我也是复制第一个函数,然后修改参数类型作为第二个函数的。
这种写法的弊端显而易见。因此引入了模版函数。
定义模版函数
Demo
template <typename T>
int compare(const T &v1, const T &v2)
{
if ( v1 < v2 ) return -1;
if ( v2 < v1 ) return 1;
return 0;
}
定义: template + 模版形参列表
模板形参列表是用尖括号括住的一个或多个模板形参(可以表示类型的类型形参,也可以表示常量表达式的非类型形参)的列表,形参之间用逗号隔开。
类型形参跟在关键字class, typename之后定义
class和typename没有区别。
使用:
Demo
int main()
{
// 模板形参为int
cout << compare(1, 0) << endl;
// 模板形参为string
string s1 = "hi", s2 = "world";
cout << compare(s1, s2) << endl;
return 0;
}
注意:inline模板函数的inline关键字要放在摸板型列表之后,返回类型之前,不能放在关键字template之前。
定义类摸板
Demo
template <class Type>
class Queue {
public:
Queue();
Type &front();
const Type &front () const;
void push (const Type &);
void pop();
bool empty() const;
};
使用:
Queue<int> qi; //用int代替Type
Queue< vector<double> > qc; //用 double型的vector代替Type
Queue<string> qs; //用string代替Type
模版形参
模版形参选择的名字没有本质含义,如T可以替换为M或任意名字。
唯一含义:类型形参(表示未知类型)还是非类型形参(未知值)
作用域:
从声明为模版形参到模板声明或定义的末尾处使用。
屏蔽全局名字
Demo
typedef double T;
......
template <class T>
T calc (const T &a, const T &b)
{
T tmp = a;
....
return tmp;
}
T定义为double的全局类型别名将被名为T的类型形参所屏蔽。
限制:模版形参的名字只能在同一模板形参表中使用一次,且不能在当前模板内部重用,但是可以在不同的模板中重用。
声明:同一模板的声明和定义中,模板形参的名字不必相同。
typename和class的区别
相同含义,可以互换使用
非类型模板形参
模板形参不必都是类型。
在调用函数时,非类型形参将用值代替,值的类型在模版形参表中指定。
模板非类型形参是模板定义内部的
常量值。
Demo
template <typename T, size_t N>
void array_init(T array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i)
{
parm[i] = 0;
}
}
// 调用,当调用array_init时,编译器从数组实参计算非类型形参的值
int x[42];
double y[10];
array_init(x); // 等效 <int, 42>
array_init(y); // 等效 <double, 10>
编写泛型程序:减少对类型的要求
由于在函数模板内部完成的操作限制了可用于实例化该函数的类型,所以,编写模板代码时, 对实参类型的要求尽可能少是有益的。
两个原则:
- 模板的形参是const引用:允许使用不允许复制的类型
- 函数体中的测试只用<比较:减少对类型的要求(可能会有些类型,支持'<'但不支持'>')。
2 实例化
这里的实例化,需要和类对象的实例化区别开来。
模板的实例化:编译器用模板产生指定的类或函数的特定类型版本,产生模板的特定类型实例的过程称为实例化。
类的实例化:类模板形参是必需的。
Demo
//用string类型的对象创建Queue类
Queue<string> qs; // stirng代替Type的每次出现
函数模板实例化:编译器通常会为我们推断模板实参
Demo
int main()
{
compare(1, 0);
comapre(3.14, 2.7);
}
模板实参推断
从函数调用时的实参确定模板实参的类型和值的过程叫做模板实参推断。
1 多个类型形参的实参必须完全匹配
2 类型形参的实参的受限转换
只有两种情况会发生实参以匹配已有的实例化:
const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无需产生新的实例化。
数组或函数到指针的转换:对数组或函数类型的实参用常规的指针转换。
template <typename T> // 值传递
Tfobj(T, T);
template <typenam T>
T fref(const T&, const T&); // 引用传递
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok,const属性被忽略
fref(s1, s2); // ok , s1转换为const引用
int a[10], b[42];
fobj(a, b); // ok,数组转换为指针
fref(a, b); // error,形参为引用,数组不能转换为指针
3 应用于非模板实参的常规转换
类型转换的限制仅适用于类型为模板形参的那些实参。
4 获取模板实例化后的函数指针
Demo
template <typename T> int compare(const T&, const T&);
// pf1指向该模版函数的int型实例的地址
int (*pf1) (const int&, const in&) = compare;
指针pf1引用的是将T绑定到int的实例化。
函数模板的显式实参
无法推断模板实参的类型。这时需要显式指定模版形参所引用搞的类型或值。
1 指定显式模板实参
Demo
template <class T class U> ??? sum(T, U);
// 无法确定合适的返回类型
sum(2, 4L); // 适合该调用的为: U sum(T,U);
sum(3L, 4)); // 适合该调用的为:T sum(T, U);
// 解决方案:在调用时,进行强制类型转换
int i; short s;
sum( static_cast<int>(s), i);
2 在返回类型中使用类型形参
返回类型与调用实参的类型不同,无法进行推断
Demo
template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);
// 调用,需要显式指定返回值的类型
long val3 = sum<long>(i, lng);
// 还需要注意模版形参的顺序
template <typename T1, typename T2, typnename T3>
T3 sum(T1, T2)
// 在这种情况下的调用需要从左到右依次显式指定
long val2 = sum<int, long, long>(i, lng)
3 显式实参与函数模板的指针
当利用模板实例化重载函数的时候,需要显示指明调用的是哪一个实例
Demo
template <typename T>
int compare(const T&, const T&);
// 实例化
void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
// 显示指明哪一个func
func(compare<int>)(1, 2);
小结:
1 模板函数的声明和模板类的声明
2 模板形参和调用实参
3 类型推断,以及无法推断时的处理方法
4 实例化
参考资料:
《C++ Primer 4th》