c++模板.docx

上传人:b****5 文档编号:4240034 上传时间:2022-11-28 格式:DOCX 页数:12 大小:140.95KB
下载 相关 举报
c++模板.docx_第1页
第1页 / 共12页
c++模板.docx_第2页
第2页 / 共12页
c++模板.docx_第3页
第3页 / 共12页
c++模板.docx_第4页
第4页 / 共12页
c++模板.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

c++模板.docx

《c++模板.docx》由会员分享,可在线阅读,更多相关《c++模板.docx(12页珍藏版)》请在冰豆网上搜索。

c++模板.docx

c++模板

一、什么是模板

    在C++编程中,当我们需要获取两个变量之间的较大值时,考虑到变量的类型可能会是int、double等,最常用的方法往往是通过重载函数,实现不同类型的函数版本:

[cpp]viewplaincopy

1.int Max(int lhs, int rhs)  

2.{   

3.    return (lhs > rhs) ?

 lhs :

 rhs;  

4.}  

5.  

6.double Max(double lhs, double rhs)  

7.{  

8.    return (lhs > rhs) ?

 lhs :

 rhs;   

9.}  

    此时,当我们接到新的需求,需要获取两个char类型的变量之间的较大值时,我们就会再添加一个char类型重载函数:

[cpp]viewplaincopy

1.char Max(char lhs, char rhs)  

2.{  

3.    return (lhs > rhs) ?

 lhs :

 rhs;  

4.}  

    每当新增一种的类型的需求,又需要实现多一个重载函数,无形中增加了开发人员的工作,当函数逻辑比较复杂,而且比较庞大时,也容易出现错误。

从上面的代码可以看出,三个版本的重载函数除了类型不一样之外,逻辑基本一致,完成的功能也是相同的,因此,如果能有一个通用的函数,抽取出这些相同的代码逻辑,将可以大大减少代码的重复,提高代码的重用性。

此时,使用C++的模板就可以很好的解决这些问题,通过模板,可以使得开发人员写出更通用、更灵活、类型无关的代码。

二、模板的分类和格式

    C++模板(template),主要分为函数模板和类模板。

2.1函数模板

2.1.1函数模板的格式和使用

    template

    返回类型函数名(参数列表)

    {

      //函数体 

    }

    模板定义以关键字template开始,后接以<>括号括住的模板形参表,其中class是关键字,在这里class可以使用typename代替,<>括号中的模板形参使用逗号进行分隔。

下面使用函数模板实现前面的求较大值函数:

[cpp]viewplaincopy

1.template  

2.T Max(const T &lhs, const T &rhs)  

3.{  

4.    return (lhs > rhs) ?

 lhs :

 rhs;  

5.}  

[cpp]viewplaincopy

1.//测试用例:

  

2.int a = 34, b = 257;  

3.double c = 1.4, d = 4.4;  

4.char e = 'b', f = 'k';  

5.  

6.cout<

257  

7.cout<

4.4  

8.cout<

k  

    当对上面定义的函数模板进行编译时,类型T就会被实际传入的类型所代替,例如Max(a,b)中a和b是int 型,这时函数模板Max中的形参T就会被int 代替,并实例化一个模板函数Max(constint&lhs,constint&rhs);同理,当实际传入的类型为double、char等其他类型时,就会实例化相应类型的模板函数,从而实现了类型无关的泛型编程。

2.1.2函数模板一般不对实参进行隐式类型转换

    好奇的你可能会问,前面几个调用都是用同一种类型,当调用Max(a,b),其中a为int类型,b为double类型,这样会得到正确的结果吗?

[cpp]viewplaincopy

1.#include   

2.using namespace std;  

3.  

4.template  

5.T Max(const T &lhs, const T &rhs)  

6.{  

7.    return (lhs > rhs) ?

 lhs :

 rhs;  

8.}  

9.  

10.int main(int argc, const char *argv[])  

11.{  

12.    int a = 10;  

13.    double b = 5.3;  

14.    cout<< Max(a, b)<

15.}  

    从编译器的出错信息可以看出,上面的例子在模板形参推导的过程中出现了错误。

这是由于编译器在编译时,会将函数模板的形参解析为首先遇到的类型,以上例子中,编译器首先遇到的是int类型,函数模板的形参就被解析为int类型,同时函数模板一般不对实参进行隐式类型转换(除了非const引用或指针到const引用或指针的转换、数组或函数到指针的转换),参数需要完全匹配,因此以上例子不能通过编译。

    解决方案有两种:

    第一:

强制类型转换

[cpp]viewplaincopy

1.int a = 10;  

2.double b = 5.3;  

3.cout<

10  

    第二:

在函数模板中增加一种形参类型

[cpp]viewplaincopy

1.template  

2.T1 Max(const T1 &lhs, const T2 &rhs)  

3.{  

4.    return (lhs > rhs) ?

 lhs :

 rhs;  

5.}  

6.  

7.int a = 10;  

8.double b = 10.3;  

9.cout<

10  

    上面的情况,返回的是T1类型,当我们需要返回T2类型的时候,就把返回类型改为T2就可以。

这样改来改去实在不方便,那能不能让调用者决定返回的类型呢?

答案是肯定的,只要再加一种形参类型就可以了。

[cpp]viewplaincopy

1.template  

2.T1 Max(const T2 &lhs, const T3 &rhs)  

3.{  

4.    return (lhs > rhs) ?

 lhs :

 rhs;  

5.}  

6.  

7.int a = 10;  

8.double b = 10.3;  

9.cout<

pre">      

    上面的函数模板可以让调用者决定返回的类型,但是却不可以通过编译,以下为对应的出错信息:

    出错的原因是,编译器可以根据传入的实参推导出T2和T3的类型,却没有其它信息可以让编译器推导出T1的类型,因此,在这种情况下,我们需要在调用的时候显式指定T1的类型:

[cpp]viewplaincopy

1.int a = 10;  

2.double b = 10.3;  

3.cout<(a, b)<

10.3  

2.1.3 模板函数vs重载函数

    在前面说到,通过模板函数可以避免了重载函数的一些问题,在这里大家会不会问一句,模板函数是否可以重载?

答案也是一如既往的肯定。

[cpp]viewplaincopy

1.template  

2.T Max(const T &lhs, const T &rhs)  

3.{   

4.    return (lhs > rhs) ?

 lhs :

 rhs;  

5.}  

6.  

7.template  

8.T Max(const T &arg1, const T &arg2, const T &arg3)  

9.{   

10.    T temp = (arg1 > arg2) ?

 arg1 :

 arg2;  

11.    return (temp > arg3) ?

 temp :

 arg3;  

12.}  

13.  

14.int a = 1, b = 2, c = 3;  

15.Max(a, b);//调用两个参数的模板函数  

16.Max(a, b, c);//调用三个参数的模板函数  

    模板函数不但可以重载,而且还可以与非模板函数进行重载,下面列出的是模板函数和非模板函数的重载:

[cpp]viewplaincopy

1.template  

2.T Max(const T &lhs, const T &rhs)  

3.{   

4.    return (lhs > rhs) ?

 lhs :

 rhs;  

5.}  

6.  

7.int Max(int lhs, int rhs)  

8.{  

9.    return (lhs > rhs) ?

 lhs :

 rhs;  

10.}  

11.  

12.int a = 1, b = 2;  

13.Max(a, b);//调用非模板函数  

    上面的例子中,实际调用的是非模板函数,而不是模板函数,这就涉及到模板函数和非模板函数的匹配顺序问题。

引用《C++Primer》的一段描述:

    C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:

    a.寻找一个参数完全匹配的函数,若找到就调用它。

    b.寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。

    c.若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。

    d.若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。

    简单来说,就是非模板函数匹配优先于模板函数,没有非模板函数时,选择最匹配和最特化的模板函数。

2.2类模板

2.2.1类模板的格式

    template

    class类名

    { 

      //... 

    };

    类模板的定义与函数模板类似,也是以关键字template开头,后接模板参数列表。

[cpp]viewplaincopy

1.template  

2.class Utility  

3.{  

4.pubic:

  

5.    Utility(T arg):

 m_value(arg){}  

6.    T Add(T arg) const;  

7.    T Add(T lhs, T rhs);  

8.  

9.private:

  

10.    T m_value;  

11.};  

12.  

13.template  

14.T Utility:

:

Add(const T &arg) const  

15.{  

16.    return arg + m_value;  

17.}  

18.  

19.template  

20.T Utility:

:

Add(T lhs, T rhs)  

21.{  

22.    return lhs + rhs;  

23.}  

24.  

25.Utility util

(1);cout<

(2)<

3  

26.cout<

5  

    类模板每次实例化的时候,都会产生一个独立的类类型,上例产生的就是一个int类型的Uitility类型。

在创建类对象时,一定要显式指定类模板的模板形参的类型,如上例的Utilityutil

(1),否则编译会出错,而且不允许出现Utility<10>util

(1),类模板形参不能进行实参类型推导,必须显式指定类型。

    上例中的util.Add(2,3.5)调用能通过编译,实参3.5会被隐式转换为int,并能得到正确结果,而在模板函数中却会出错,函数模板和类模板对于隐式类型转换的要求是不一致的。

三、模板特化

    模板特化分为全特化和偏特化两种。

其中,类模板可以全特化和偏特化,函数模板只能全特化,函数的模板只能重载,没有偏特化。

3.1函数模板全特化

[cpp]viewplaincopy

1.//函数模板通用版本  

2.template  

3.int Compare(const T lhs, const T rhs)  

4.{  

5.    if (lhs < rhs) return -1;  

6.    if (rhs < lhs) return 1;  

7.    return 0;  

8.}  

    当参数类型为指针类型时,上面定义的模板可能不能正常工作,通常两个字符串比较都是按字典顺序进行比较,但是上面定义模板,当参数类型为指针类型时,就会变成比较两个地址的大小。

因此,当具体到字符串指针类型时,就需要下面的特例化模板定义:

[cpp]viewplaincopy

1.//函数模板全特化版本  

2.template<>  

3.int Compare(const char* lhs,const char* rhs)  

4.{  

5.    return strcmp(lhs, rhs);  

6.}  

3.2类模板全特化和偏特化

[cpp]viewplaincopy

1.//类模板通用版本  

2.template  

3.class Compare  

4.{  

5.public:

  

6.    bool IsEqual(T lhs, T rhs)  

7.    {  

8.        return (lhs == rhs);  

9.    }  

10.};  

11.  

12.//类模板全特化版本  

13.template<>  

14.class Compare  

15.{  

16.public:

  

17.    bool IsEqual(const char* lhs, const char* rhs)  

18.    {  

19.        return strcmp(lhs, rhs);  

20.    }  

21.};  

    以上例子展示了类模板的全特化,下面展示类模板的偏特化:

[cpp]viewplaincopy

1.//类模板通用版本  

2.template  

3.class Compare  

4.{  

5.    //...   

6.};  

7.  

8.//类模板偏特化版本  

9.template  

10.class Compare  

11.{  

12.    //...   

13.};    

四、使用模板的注意事项

4.1typename与class的区别

    在模板形参列表中,关键字typename和class没有区别,但在某些情况下,typename与class还是有区别的。

[cpp]viewplaincopy

1.template  

2.void Func()  

3.{   

4.    T:

:

A *var;//猜一下,是声明一个指针,还是进行一次乘法操作  

5.}  

    在以上情况下,编译器不确定类型T作用域下的A是一个类型,还是一个静态成员变量,因此需要在模板参数前面加上typename来表明这个A是一个类型(typename可以用来指示后面的名字是一个类型名)。

另外,使用typename含义更加清晰,而使用class容易与类定义的class混淆,所以还是推荐是在模板形参列表中使用typename。

4.2模板声明或定义的范围

    模板的声明或定义只能在全局,命名空间或类范围内进行,不能在局部范围,函数内进行,例如不能在main函数中声明或定义一个模板。

4.3template在声明的时候就需要定义

    当实现一个类的时候,我们常常会在一个.h文件中声明类结构,然后在一个.cpp文件中定义该类,下面为Utility.h文件:

[cpp]viewplaincopy

1.#ifndef UTILITY_H  

2.#define UTILITY_H  

3.  

4.template  

5.class Utility  

6.{  

7.public:

  

8.    T Max(const T &lhs, const T &rhs);  

9.};  

10.  

11.#endif  

    相应的Utility.cpp文件如下:

[cpp]viewplaincopy

1.#include "Utility.h"  

2.  

3.template  

4.T Utility:

:

Max(const T &lhs, const T &rhs)  

5.{  

6.    return (lhs > rhs) ?

 lhs :

 rhs;  

7.}  

    以下代码在main.cpp文件中,将会对前面定义的Utility类进行调用:

[cpp]viewplaincopy

1.#include   

2.#include "Utility.h"  

3.  

4.int main(int argc, const char *argv[])  

5.{  

6.    Utility util;  

7.    cout<

8.    return 0;  

9.}  

    编译结果如下图所示:

   《C++编程思想》第15章(第300页)说明了上例出错的原因:

     模板定义很特殊。

由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。

在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。

所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

    可行的解决方法主要有三种:

    第一种:

把模板的声明和定义放在一起

    第二种:

在Utility.h文件尾部加上#include"Utility.cpp",其实就是第一种方法,只是间接将两个文件拼在一起了。

    第三种:

C++理论上支持export关键字,以实现模板的分隔编译,本人机器的g++版本为4.7.3,但在使用export关键字时,仍会出现以下问题:

总结:

模板是C++泛型编程中不可或缺的一部分,在STL(StandardTemplateLibrary)中得到广泛的使用。

理解了模板,特别是模板的特化和偏特化,也就基本理解了STL中的核心-traits编程,为STL的学习打下了坚实的基础。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 数学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1