所谓泛型编程就是以独立于任何特定类型的方式编写代码,模板是泛型编程的基础。
1.函数模板
我们经常会遇到需要编写函数比较两个对象大小的情况,比如需要比较两个int值得大小、两个char值的大小等等,在C++以前,我们需要为每一种对象编写对应的函数,甚至必须取不同的函数名,有了C++以后,还没接触到泛型编程前我们可能会利用函数重载来避免要取多个不同名字的麻烦,但还是需要编写多个函数:
int compare(const int &a, const int &b)
{
if(a < b) return -1;
if(b < a) return 1;
return 0;
}
int compare(const string &a, const string &b)
{
if(a < b) return -1;
if(b < a) return 1;
return 0;
}
这些函数执行的操作都是一模一样的,重复编写相同的内容相当低效,有了泛型编程的思想后,我们可以定义一个函数模板,编译器会自己根据给定的模板实参生成不同的函数(实例):
template <typename T> int compare(const T &a, const T &b)
{
if(a < b) return -1;
if(b < a) return 1;
return 0;
}
函数模板定义从template关键字开始,后面括号里是模板形参表(template parameter list),模板形参T可以是表示类型的类型形参(type parameter),也可以是表示常量表达式的非类型形参(nontype parameter),此处的typename也可由class代替,两者没有区别。
使用函数模板时,编译器会推断传入函数compare的形参的类型(模板实参),并生成对应于此类型的函数(实例):
int main()
{
std::string a("hello"), b("world");
//生成int compare(const std::string &, const std::string &);
int ret1 = compare(a, b);
int m = 10, n = 5;
//生成int compare(const int &, const int &);
int ret2 = compare(m, n);
return 0;
}
函数模板也可以声明为inline,inline说明符必须放在模板形参列表之后:
template <typename T> inline compare(const T &, const T &);
我们的类中可能会定义一些自己的类型,比如标准库的很多容器类里都定义了size_type类型,当我们在函数模板中使用这样的类型时需要注意指明这是一个类型而不是一个对象,为此,我们必须在类型名前加上typename关键字,若不指定,编译器将默认这是一个对象:
class Test{
typedef std::vector<std::string>::size_type line_num;
...
};
template <typename T> func(const T &a)
{
//通过加上typename指明line_num是Test类中的类型而不是一个对象
//指定后此句就是声明了一个指针,否则就是在计算对象line_num和p的积
typename Test::line_num *p;
...
}
2.类模板
C++提供了很多类模板,比如我们经常用到的vector、list、queue等,我们可以使用类模板来定义自己的类。
类模板的定义与函数模板类似,都以template关键字接模板形参表开头:
template <typename T> class MyClass{
...
};
我们可以像使用vector、list那样来使用我们自己的类模板:
MyClass<int> a;
MyClass< vector<std::string> > b;
MyClass<string> c;
可以看出,与函数模板不同的是,在使用类模板时必须显式指定模板实参,这是因为编译器可以通过传入函数的参数类型推断函数模板的模板实参,而类模板的模板实参无从推断,故必须显式指定。
编写泛型程序的原则:对实参类型的要求尽可能少
上面的compare函数模板也体现了这个原则:
- 函数形参是const引用,这样就允许不能进行复制操作的类型调用该函数
- 函数体的测试中只用到了<,这样就允许只支持<而不支持>的类型调用该函数
有很多C++程序员在循环条件判断等处习惯使用!=而不是<或>也体现了这样的原则,因为有很多类型只支持判断相等或不等,而不支持大于小于操作。