C++学习之模板

来源:互联网 发布:曼哈顿模考软件 编辑:程序博客网 时间:2024/05/22 03:12

之前写过有关C++模板的内容,但是现在回头再看看书本,发现之前写的东西太肤浅了。考虑的问题太不全面了。因此在这里重写下有关C++模板的知识总结。

为什么C++要引入模板这个概念呢?C++的发明者Bjarne Stroustrup曾这样说过:“这是为了支持类型安全、类容器的有效性和算法的通用性。”简单的来说,模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现真正的代码可重用性。C++中的模板可分为两类:函数模板和类模板。它们有共同的特点,也存在一定的用法差异。下面我们就分别介绍两种模板的使用方法和应注意的问题。

一、函数模板

有时我们为了实现不同类型参数的函数要分别写出各种类型的重载函数,这样做往往需要写几个甚至十几个只有略微差别的函数。在引入模板的概念之后,我们可以用一个函数轻松的解决这之类的问题了。首先举一个简单的加法运算的例子:

template <class T>

T add( T a, T b)

{

         return  a+b;

}

这个例子就是一个函数模板,我们可以分别用显式和隐式的方法调用该模板:

1.      显式的调用该模板: add<int>(5,6); add<double>(2.5, 3.4);

2.      隐式的调用模板:add(5,6); add(2.5,3.4);虽然这里没有显式的指定函数模板参数,但是它能自动地推导出函数模板参数为int。需要注意的是:模板的类型参数可以不止一个,比如template<class T1, class T2>是可以的。并且当模板的类型参数用于函数的返回类型时,隐式调用的自动推导就不会起作用了。比如:

template<class T1, class T2>

T2 add(T1 a, T1 b)

{

           returna+b;

}

此时调用必须是:add<int>(5,6);或者add<int, int>(5.6);因为函数返回类型的类型参数是不能自动推导的。

         下面就介绍函数模板的几个特殊用法:

1.      非类型模板参数

这个名字听起来有点怪怪的,我们来举个例子来看,就很明了了:

template<typename T, int VAL>

T addValue(T const &x)

{

           return x+VAL;

}

当我们执行语句,int i = addValue<int, 5>(1);结果为6。这样来看非类型模板参数的概念就有些明朗了,即它表示该参数名代表了一个潜在的值,而该值代表了模板定义中的一个常量。而且它是有限制的:它们可以是整形(包括枚举类型)或者指向外部链接对象的指针(指向外部链接对象的指针?怎么使用?求高人指点),但它们绝不可能是浮点数和类对象。

接着就介绍一种利用非类型模板参数求数组大小的方法,也是先举出例子再作分析:

template<class type, int size>int arrarysize(type (&arr)[size])//注意这里的格式,这是声明数组模板的方法。{    return size;    }int main(){    int i ;    int arr[] = {0,1,2,3,4,5,6,7,8};    i = arrarysize<int>(arr);//传入的只能是数组名,而不能是一个指针。    cout<<i<<endl;    return 0;}

上面的结果输出是:9。正确的输出了数组的大小,这样计算总有点怪怪的感觉对吧。其实这是通过自动推导模板参数计算出的size。在具体的怎么推导,我也不太明白,求高人指点。

2.      默认模板参数

函数模板使用默认模板参数和普通函数使用默认参数的作用一样,它们的使用方法也类似。比如:

template <class T>T square(T a = 1){           return a*a;}square<int>(); // 默认输出结果1square<double>(2.0)// 输出结果4.0

需要注意的是:和普通函数的默认参数一样,如果函数存在多个参数时,具有默认值的参数必须要放在最右边,因为实参和形参的结合是从左到右的顺序进行的。写到这里,让我想到了:具有默认参数的函数是不能和重载函数共同作用的。因为这样做了会引起二义性。

3.      函数模板和重载

当同一作用域中既存在函数模板和普通函数时,如果两者都精确匹配执行语句时,那么普通函数的调用优先级高于函数模板的优先级。并且函数模板不支持参数类型的自动转换。

二、类模板

类模板比函数模板复杂了很多。类包含了继承,嵌套等复杂的组合关系。首先类模板的声明是和函数模板一样的,同样是增加一行:template <class 类型参数名>。但需要注意的是:类模板的成员函数的定义和类模板的静态成员变量的定义的方法。成员函数的定义方法:

template <class typename>

函数类型  类模板名< typename>::类成员函数名(函数形参表)

{

         ……

}

静态成员变量的定义方法:

template<class typename>  变量类型类模板名< typename >::变量名 = 变量值;

类模板也具有非类型的类模板参数,它的用法和函数模板的非类型的模板参数是一样的。只不过调用非类型模板形参的实参必须是一个常量表达式,或者是一个数,即它必须能在编译时计算出结果。类模板也具有缺省模板参数,就像为函数参数设置默认值一样。这些都是和函数模板很相似的,就不许要多说了。下面着重介绍类模板的类外几个重要的应用。

1.      模板的模板参数

又是一个怪怪的概念。先从定义上了解它:将一个模板作为另一个模板的参数,也即是说类型形参可以是类模板。模板是依靠类型参数来实现的,这个类型参数可以是基本数据类型(int 、long、 double),也可以是自定的数据类型或者stl库中的数据类型。如果是自定的数据类型,那么它也可以是一个模板类,也就是说模板要支持其类型参数是一个模板类的可能。也就是我们这里所说的模板的模板参数。

它的一个简单的定义是:

template<class T1, class T2, template<class T3>class A>

class B

{

           ……

}// 有时候T3可以省略不写

下面我们举一个非常有趣的例子,即:类模板递归求平方和

template <int N, template<int> class F>class Accumulate{    public:        enum {RET = Accumulate<N-1,F>::RET + F<N>::RET};    };template <template<int>class F>class Accumulate<0,F>{    public:        enum{RET = F<0>::RET};    };template<int n>class square{    public:        enum{RET = n*n};    };int main(){    cout<<"1*1 + 2*2 = "<<Accumulate<2,square>::RET<<endl;    cout<<"1*1 + 2*2 + 3*3 = "<<Accumulate<3,square>::RET<<endl;    return 0;}

它的输出结果是:



这种写法倒是第一次见到,不过它用到了非类型模板参数,和模板的模板参数。仔细分析下该程序,会有挺大的收获的。

2.      成员模板

任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板。成员模板包括:成员函数模板和成员类模板。两者的声明方式和普通的函数模板和类模板的声明方式是一样的。但这里值得细说的一部分是:C++中的适配器(Adapter)模式,即将一个类的接口转换成客户希望的另外一个接口。

比如:游戏中的一些玄幻门,从入口A中进入,但实际进入的场景可能是两种或两种以上,这里以两种为例(B和C)。B和C都有各自入口。那么我们的目的就是实现:从接口A转换为接口B或接口C。当然这可以利用虚函数来实现,但是如果类A和类B以及类C有本质的区别,继承不能很好的诠释它们怎么办呢?这里就利用一种成员模板的适配器模式。具体代码如下:

class A{public:virtual ~A() {};virtual void entry(){cout<<"Entry A door!"<<endl;}};class B{public:void entry(){cout<<"Entry B dorr!"<<endl;}};class C{public:void entry(){cout<<"Entry C door!"<<endl;}};class Adapter{public:template<class T>A * MakeAdapter(T * data){class LocalDoor:public A{public:LocalDoor(T* pobj):m_pobj(pobj){}~LocalDoor() {}virtual void entry(){cout<<"Entry A door!"<<endl;cout<<"adapter transfer …"<<endl;m_pobj->entry();                    }                private:                T * m_pobj;            };return new LocalDoor(data);        }};int main(){    Adapter a;    B b;    C c;    A *pa;    pa = a.MakeAdapter(&b);    pa->entry();    delete pa;    pa = a.MakeAdapter(&c);    pa->entry();    delete pa;    pa = NULL;    return 0;}

上面的输出结果是:


3.      类模板的派生

类模板的派生和一般类的派生有很大的相似的地方。只不过类模板的派生可包括:从类模板本身继承;也可以从类模板特定实例化继承。派生类可以是类模板也可以不是类模板。一个类模板可以派生成一个新类模板,派生类模板可以添加新的成员变量和成员函数,以实现类模板的扩充和修改。

3.      模板的特化

模板的特化是一个重要的概念。它的用途比较广泛。所谓特化就是将泛型搞得具体化一些,也即是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰或完全被指定了。比如上面提到的add函数的模板,它就不适合求复数的相加,此时我们可以为复数类型特化出来一个add函数。具体的使用方法是:

template <>

模板名<特化定义的模板参数>(函数形参表)

{

           函数体;

}

比如对complex类型特化一个add函数

template<>complex add<complex>(complex cm_a,  complex cm_b){                    complex temp;                    temp.real= cm_a.real + cm_b.real;                   temp.imag = cm_a.imag +cm_b.imag;                    returntemp;}

上面就实现了一个特化。而且需要注意的是:匹配函数的优先级,非模板函数>特化的模板函数>模板函数。

类也有特化的类模板,其主要是用于不同的数据类型的实现方法不同,因此要对其做一些特殊处理。这里就不在举例说明了。

 

 

 

以上内容参考了:《C++面向对象程序设计》,李晋江,刘培强编著的书籍。很不错的一本书推荐下。




原创粉丝点击