六、泛型编程
6.1 使用模板的优点和缺点?
优点:
- 在一些场景可以避免重复代码
- 有些问题难以使用OO技巧(如继承和多态)来实现,而使用模版会很方便
- template classes更加的类型安全,因其参数类型在编译时都是已知的。
缺点:
- 一些编译器对template支持不好。
- 编译器给出的有些出错信息比较晦涩。
- 为每种类型都生成额外的代码,可能导致生成的exe膨胀。
- 使用templates写的代码难以调试
- templates在头文件中,这样一旦有所变更需要重编译所有相关工程
6.2 模板函数和函数的对比?
- 模板函数由函数模板实例化而来,编译器推断模板实参,然后实例化出对应的函数定义。模板函数是函数模板的实例。
- 普通函数需要程序员手动重载才能实现对于不同类型参数的支持。
- 函数模板只能用于函数的参数个数相同而类型不同的情况,如果参数个数不同,则不能使用函数模板,只能使用重载。
- 函数模板必须要求所有实参的类型T都相同,无法进行隐式类型转换。
- 进行函数调用时,编译器优先选择匹配的非模板函数,如果找不到再试着进行函数模板的实例化,如果还不行,则这个调用违法。这样做可以减少函数模板实例化次数,提高效率。
6.3 模板的全特化和偏特化?
什么是特化?
所谓特化,就是将泛型的东东搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰(例如const或者摇身一变成为了指针之类的东东,甚至是经过别的模板类包装之后的模板类型)或完全被指定了下来。
模板有两种特化,全特化和偏特化(局部特化) 模板函数只能全特化,没有偏特化(以后可能有)。 模板类是可以全特化和偏特化的。 全特化,就是模板中模板参数全被指定为确定的类型。 全特化也就是定义了一个全新的类型,全特化的类中的函数可以与模板类不一样。 偏特化,就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。 在类型上加上const、&、*( cosnt int、int&、int*、等等)并没有产生新的类型。只是类型被修饰了。模板在编译时,可以得到这些修饰信息。
模板为什么要特化,因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的。
模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。
先看类模板:
template<typename T1, typename T2>
class Test
{
public:
Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}
private:
T1 a;
T2 b;
};
template<>
class Test<int , char>
{
public:
Test(int i, char j):a(i),b(j){cout<<"全特化"<<endl;}
private:
int a;
char b;
};
template <typename T2>
class Test<char, T2>
{
public:
Test(char i, T2 j):a(i),b(j){cout<<"偏特化"<<endl;}
private:
char a;
T2 b;
};
那么下面3句依次调用类模板、全特化与偏特化:
Test<double , double> t1(0.1,0.2);
Test<int , char> t2(1,'A');
Test<char, bool> t3('A',true);
而对于函数模板,却只有全特化,不能偏特化:
//模板函数
template<typename T1, typename T2>
void fun(T1 a , T2 b)
{
cout<<"模板函数"<<endl;
}
//全特化
template<>
void fun<int ,char >(int a, char b)
{
cout<<"全特化"<<endl;
}
//函数不存在偏特化:下面的代码是错误的
/*
template<typename T2>
void fun<char,T2>(char a, T2 b)
{
cout<<"偏特化"<<endl;
}
*/
注意:
- 至于为什么函数不能偏特化,似乎不是因为语言实现不了,而是因为偏特化的功能可以通过函数的重载完成。
- 函数模版的全特化不参与函数重载, 并且优先级低于函数基础模版参与匹配,也就是说,匹配的顺序是:
1. 非模板函数
2. 某个没有进行全特化的template function
3. 如果这个没有进行全特化的template function有全特化版本,并且类型也比较匹配,则选择这个全特化版本