解析模板(上)--模板函数

来源:互联网 发布:巨浪营销软件 编辑:程序博客网 时间:2024/04/19 12:00

模板引出:

在学习关于类的特性中,有一个重要的特性叫做多态,同样关于模板的学习也可以简单的把它理解成一种多态,只不过模板中所涉及的多态是关于类型的替换,正如在生活中使用模具一样,在模具中加入不同的原料就会得到不同材料的产品,而在C++中的模板里面来说,这种原料就是不同的参数类型。

对于下面一个求两个数之间的较大值的算法,对于不同的数据类型,我们需要为每种类的变量编写相应类型的求两个数之间的较大值的函数,如下:

int Max(int a, int b){return (a>b)?a:b;}char Max(char a, char b){return (a>b)?a:b;}double Max(double a, double b){return (a>b)?a:b;}
对于上面的程序代码来说,我们所写的函数中除了类型不一样之外别的部分都是一样的,这样的代码在写起来会使代码变得十分的冗余,但是在C++引入模板时,我们却可以轻而易举的解决这些类似的--只有参数类型和返回值类型不同的代码带来的代码冗余度过大问题。

模板:

模板分类


模板主要涉及到函数模板和模板类,模板是泛型编程的基础。

对于上面的求两个数之间的较大值,针对于不同的类型我们现在给出用模板来解决的方法,首先给出模板函数的一般定义格式:

template <typename T1, typename T2, ......>返回值类型 函数名(参数列表)

{

函数体

}

说明:

(1)template是定义模板的关键字,在定义模板时不能缺省;

(2)typename也可以用class来代替,为了与类的关键字区别,在以后的使用过程中我们应该尽量使用typename来定义类型名;

(3)typename是不能省略的,模板形参的名字在同一模板形参列表中只能使用一次<>中的内容叫做模板形参,模板形参不能为空一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名

(4)模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。

下面我们就来对引出模板的Max进行函数模板化,并进行函数模板的实例化

template<typename T>T Max(T a, T b){return (a>b)?a:b;}int main(){cout<<Max(1,2)<<endl;cout<<Max(2.1, 3.14)<<endl;cout<<Max('q', 'a')<<endl;}
上述代码中我们给出了,Max函数的模板函数,又对其实例化出了三个具体的模板函数,这点我们可以通过调试在反汇编中进行观察,具体的实例化函数


注意:模板被编译了两次:

(1)实例化之前,检查模板本身是否含有语法错误;

(2)实例化之后,检查模板代码,查看具体的调用是否有效。

除了通过查看反汇编来获得实例化后变量的类型外还可以通过使用typeid函数来获取一个变量的具体类型


实参推演&类型转化

实参推演--从函数实参确定模板形参类型和值的过程称为模板实参推断。多个类型形参       的实参必须完全匹配

类型转化--在函数的实参传递给形参时,会进行隐式的类型转化

在上面模板类的基础上,我们在次实例化出一个新的对象如下:

template<typename T>T Max(T a, T b){return (a>b)?a:b;}int main(){cout<<Max(1,'2')<<endl;return 0;}
对上面的代码进行编译,编译器给出如下的错误提示


类似于这种错误就是编译器在尽行实参推演时,不知道将T编译成什么样的参数,同时也说明在模板进行实例化的同时不会对参数T,进行隐式的类型转化,即不会将T转化为int型,也不会转化为char型进行计算,对于这种错误,我们给出以下几种解决方式:

(1)在实例化时,进行显示的形参说明

int main(){cout<<Max<int>(1,'2')<<endl;cout<<Max<char>(1,'2')<<endl;return 0;}
(2)给出新的函数模板

template<typename T1, typename T2>T1 Max(T1 a, T2 b){return (a>b)?a:b;}int main(){cout<<Max(1,'2')<<endl;return 0;}
(3)给出普通的函数,使其在传参时进行隐式的类型转化

int Max(int a, int b){return (a>b)?a:b;}int main(){cout<<Max(1, '2');return 0;}
两个例外
(1)const转换--接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用;
(2)数组或函数到指针的转换--如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。
数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。


模板参数:


类型模板形参----类型形参由关键字class或typename后接说明符构成,如template<class T> void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定。模板形参表示的是一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定返回类型,变量声明等,具体的实例如前面的Max函数模板的实例化过程。

说明:

(1)模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则;

(2)模板形参的名字在同一模板形参列表中只能使用一次

template<typename T, typename T>void FunTest(T a, T b){}

(3)所有模板形参前面必须加上class或者typename关键字修饰

template<typename T1, T2>void FunTest(T1 a, T2 b){}

(4)在函数模板的内部不能指定缺省的模板实参。

非模板类型参数--非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。

eg:数组长度


注意上面函数形参的写法为T (&arr) [N]

模板函数重载

      和我们前面提到过的运算符重载一样,关于类模板的函数同样也可以类比前面函数重载时的条件,只是重载模板函数时和前面有所区别,相对于前面的普通函数重载来说,主要的函数重载的不同在于模板函数的不确定类型和各个普通函数构成的具有固定类型的函数之间构成重载。

      继续沿用我们在上面举过的求几个数之间最大值的Max函数,先看下面几个简单的模板函数重载的例子

template<typename T>int Max(const T& left, const T& right){return left > right ? left : right;}template<typename T>T Max(const T& left, const T& right){retrun left > right ? left : right;}template<typename T>T Max(const T& a, const T& b, const T& c){return Max(a, Max(b, c));}

    对于上面给出的几个函数,我们在main函数进行测试时给出以下几个测试实例

int mian(){Max(10, 20, 30);//测试1Max<>(10, 20);//测试2Max(10, 20);//测试3Max(10, 20.12);//测试4Max<int>(10.0, 20.0);//测试5Max('a', 'b');//测试6system("pause");return 0;}
     上面的几种情况在vs2013环境下编译时,都会报出对于重载函数Max的调用不明确的错误,对于上面给出的几种测试情况我们有如下的说明:

1、一个非模板函数可以和一个同名的函数模板同时存在,并且该函数模板还可以被实例化为这个非模板函数。

2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,则编译器选择模板构造的函数。

3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。

4、模板函数不允许自动的类型转换,但是普通函数可以进行自动的类型转换。

模板函数的特化

并不是所有情景模板函数都能为我们合成我们所需要的特定功能的函数,恰恰相反有时候编译器为我们合成的模板函的功能和我们所要的功能大不相同。例如在比较两个字符串的大小时,如果通过比较谁大返回谁的原则,那么我们所得到的结果将由我们定义的顺序所决定。下面先来模拟一下这种情景。

template<typename T>int compare(T str1, T str2){if (str1 < str2){return -1;}else{return 1;}return 0;}
     在main函数中给出下面的测试场景

int main(){char *p1 = "abcd";char *p2 = "1234";cout << compare(p1, p2) << endl;system("pause");return 0;}
      经过编译运行后,返回的结果为-1,显然这并不正确的答案,因为从ASCII码表上面,我们可以查到a的值大于大于1的ascii码值,因此输出的结果应该为1,为什么编译器为我们合成的比较函数的模板会得到错误的结果,我们有必要对它进行一个深层次的分析,在函数调用的时,直接将两个指针变量的地址传递给模板函数,在比较时,直接比较的是两个地址的大小,而并不是真正的比较这两个空间中所存放的内容。


      对于这种情况,我们可以通过下面的方法来比较两个字符串的大小

template<>int compare<const char*>(const char* const str1, const char* const str2){return strcmp(str1, str2);}

模板特化格式:

1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体

template<>返回值 函数名<Type>(参数列表){//函数体;}

在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,
如果不匹配,编译器将为实参模板定义中实例化一个实例。














































0 0