前文回顾:
C++ 泛型编程(一) 基本概念
函数模版
-
模版定义
①.定义
模版定义以关键字 template 开始,后跟尖括号包围的模版参数列表,用关键字 typename 来定义模版参数类型。
template <typename T> int compare(const T &V1,const T &v2) { if( v1 < v2 ) return -1; if( v2 < v1 ) return 1; return 0; }
模版的声明必须包含模版参数,模版定义中模版参数名可以与声明不同。
template <typename T> int compare(const T &,const T &);
②.模版调用
模版调用有两种方式:自动调用和显式调用。自动调用是由编译器根据函数实参来推导模版实参;显式调用是我们明确的告诉编译器模版实参类型。
template <typename T> int compare(const T &v1,const T &v2) { if( v1 < v2 ) return -1; if( v2 < v1 ) return 1; return 0; } // cout << compare(1,2) << endl;//自动调用,编译器推断出 T 为 int cout << compare<int>(1,2) << endl;//显式调用,明确的告诉编译器 T 为 int
③.实例化
模版实现类型的抽象,是 C++ 重要的代码复用方式;编译器会根据推断出来的类型在编译阶段会为我们生成一份实例代码。
cout << compare(1,2) << endl; //以上调用编译器推断出 T 为 int,编译器会把 T 替换成 int,编写并编译以下代码 int compare(const int &V1,const int &v2) { if( v1 < v2 ) return -1; if( v2 < v1 ) return 1; return 0; }
-
模版参数
①.定义
类似函数参数名字,模版参数名字可以任意,通常定义为 T。参数名字不能重用,一个模版参数名在一个特定的模版参数列表中只能出现一次,并且模版内不能重用模版参数名。
typedef double A; template <typename A,typename B>// A 会覆盖上文的 A void f(A a,B b) { A tmp = a;//tmp 类型为 A ,非 double double B;//错误:模版内不可以重用模版参数名 }
②.模版类型参数
类型参数可以看做类型说明符,可以用来指定函数的返回类型、参数类型,以及函数体内变量声明和类型转换;类型参数前必须加关键字 template 或者 class。
template <typename T> // 模版类型参数 T T foo(T* P)//函数返回返回类型为 T ,函数参数类型是指向 T 类型的指针 { T temp = *p;// T 类型的变量 // return temp; }
③.非类型模版参数
除了定义模版类型参数,也可以在模版中定义非类型参数。一个非类型参数表示一个值而非类型,其值必须是常量表达式,可用由用户提供或者编译器推断出来。
常量表达式可以是整型,但不能定义成浮点型或者其他类型。
template<unsigned N,unsigned M> int compare(const char (&p1)[N],const char (&p1)[M]) { return strcmp(p1,p2); } compare("hi","mom");//可以在编译期推断出 N 和 M 的值 //以上调用会实例化出以下版本 int int compare(const char (&p1)[3],const char (&p1)[4])
非类型模版参数也可以是指针,指针的实参必须是全局的、编译期可获取的。
非类型模版参数也可用是函数指针。
④.模版默认实参
类似可以给函数实参提供默认值,也可以给模版实参提供默认值,并且函数模版的默认实参不用遵循从右向左的规则进行指定,即可以给任意位置的模版参数提供默认值;模版函数的默认实参不是模版参数推导的依据,函数模版参数总是由函数的实参推导而来的。
template<typename T1 = int ,typename T2> void defFunc(T1 a,T2 b);
-
模版编译
①.模版与头文件
只有当我们使用模版时,编译器才会实际生成代码。为了生成一个实例化版本,编译器需要掌握函数或者类模版成员函数的定义,因此模版的头文件通常既包含声明也包括定义。
②.模版编译
模版直到实例化时才会生成代码,模版的编译分为三个阶段:一是编译模版本身,编译器检查语法错误;二是编译器遇到模版时,编译器检查实参数目是否正确以及参数类型是否匹配;三是模版实例化时,编译器检查类型相关的错误。
③.验证编译器生成了真正的函数
可以通过两个不同类型的函数指针指向函数模版,然后打印指针值看地址是否一致。
-
外部模版
模版在使用的时候才会进行实例化,如果相同的多个源文件中使用了相同的模版、并提供了相同的模版参数,那么每个文件中都会有该模版的一个实例,引起代码重复。一般编译器会进行优化,只保留一份代码。
类似外部变量的声明,可以用 extern 关键字来声明一个外部的模版,承诺在其他位置会有一个显示的实例化定义。
#include "test.h"
template <typename T>
int compare(const T &v1,const T &v2)
{
if( v1 < v2 ) return -1;
if( v2 < v1 ) return 1;
return 0;
}
template int compare(const int &v1,const int &v2);//显示实例化定义
#include "test1.h"
extern template int compare(const int &v1,const int &v2);//实例化声明
compare(1,2);