1.概述
问题:怎么理解C++里边的多态?
静态的多态(编译时期的多态):函数重载,模板 。
动态的多态(运行时期的多态):继承中的虚函数。
(1)模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
(2)模板是一种对类型进行参数化的工具;模板通常有两种形式:函数模板和类模板;函数模板针对仅参数类型不同的函数;类模板针对仅数据成员和成员函数类型不同的类。
(3)使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。也就是说不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
2.什么是模板??
(1)模板是C++的一种特性,允许函数或类(对象)通过泛型(generic types)的形式表现或运行。
(2)模板可以使得函数或类在对应不同的型别(types)的时候正常工作,而无需为每一个型别都写一份代码。
(3)模板是一种泛型编程的机制,也是一种复用的手段。
(4)模板是通用语言的特性,模板又叫参数化类型(parametrized types)。
3.什么是函数模板??
函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),**等发生函数调用时再根据传入的实参来逆推出真正的类型。**这个通用函数就称为函数模板(Function Template)。
4.什么是类模板??
C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
5.模板的实例化
编译器调用模板函数时,编译器会根据实参的类型,推演出模板的类型,并再生产相应的代码,称为模板的实参推演。
B<int> a; //类模板只能显式实例化,没法推演
fun<int>(2,6); //函数模板的显式实例化,不需要推演
fun(2,6) //函数模板的隐式实例化,需要进行推演
6.模板的参数列表
模板的参数列表有两种:模板类型参数、模板非类型参数
(1)模板类型参数
类型参数由关键字class和typename后接说明符构成。
例如:
template<class T>
void fun(T a){};
其中T就是一个类型形参,类型形参的名字由用户自已确定。模板形参表示的是一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,也就是说可以用于指定返回类型,变量声明等。
不能为同一个模板类型形参指定两种不同的类型,比如
template<class T>
void fun(T a, T b){},
// fun(25,12.2) == > 错的!!!
对函数模板来讲:
语句调用fun(25, 12.2)将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参25把模板形参T指定为int,而第二个实参12.2把模板形参指定为double,两种类型的形参不一致,会出错。
对类模板来讲:
template<class T>
T fun(T a, T b){}
A<int> a // 实例化模板类对象为a
fun(25,12.2)
语句调用fun(25,12.2)在编译时不会出错,但会有警告,因为在声明类对象的时候已经将T转换为int类型,而第二个实参12.2把模板形参指定为double,在运行时,会对12.2进行强制类型转换为12。 当我们声明类的对象为:A a,此时就不会有上述的警告,因为从int到double是自动类型转换。
(2)模板的非类型参数
模板的非类型参数也就是内置类型参数,如
template<class T, int a>
其中int a就是非类型的模板参数。
非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
非类型模板的形参只能是整型,指针和引用,像double,String, String这样的类型是不允许的。但是double &,double ,对象的引用或指针是正确的。
调用非类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果。
调用非类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果。
注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
当模板的形参是整型时,调用该模板时的实参必须是整型的,且在编译期间是常量,比如
template <class T, int a>
class A{};
int b;
A<int, b> m; // error,b不是常量
// 改为下面形式
const int b;
A<int , b>m; // 正确,因为这时b是常量。
非类型形参一般不应用于函数模板中,比如有函数模板
template<class T, int a>
void fun(T b){};
fun(2); // error,无法进行实参推演
对这种模板函数可以用显式模板实参来解决,如用下述方式就可以把非类型形参a设置为整数3
fun<int, 3>(2)
非类型模板形参和实参间所允许的转换
① 允许从数组到指针,从函数到指针的转换。
template <int * a>
class A{};
int b[1];
A<b> m; // 即数组到指针的转换
② const修饰符的转换.。
template<const int *a>
class A{};
int b;
A<&b> m; // 即从int* 到const int *的转换。
③ 提升转换。
template<int a>
class A{};
const short b = 2;
A<b> m; // 即类似从short到int的类型提升转换
④ 整值转换。
template<unsigned int a>
class A{};
A<3> m; //即从int 到unsigned int的转换
6.模板的特例化
为什么需要特例化,因为在对不同类型的参数进行一些相同的处理时,如增容,拷贝等,不同类型的处理方式可能有些不同,如string增容时和其他类型就不同,它需要考虑深拷贝,所以要另外处理,这时候就用特例化。
例如,我们有以下函数模板:
template<typename T>
bool compare(const T &a, const T &b)
{
return a > b;
}
int main()
{
int a = 10;
int b = 20;
compare<int>(a, b);
return 0;
}
但该模板不适于字符串的比较,因此我们继续编写一个针对char*类型数据的特例化版本。
template<>
bool compare<char *>( char* const &a, char* const &b)
{
return strcmp(a, b) > 0;
}