【c++ Primier】第十六章 模板与泛型编程

  • 泛型编程鱼面香对象编程一样,都依赖于某种形式的多态性。面向对象编程所依赖的多态性成为运行时多态性,泛型编程所依赖的多态性成为编译时多态性或参数式多态性。
  • 模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。

        函数模板定义如下:

template <typename T>

int compare(const T &v1, const T &v2){

        if(v1 < v2) return -1;

        if(v2 < v1) return 1;

}

        类模板定义如下:

template<class Type > class Queue{

public:

        Queue(){}

        Type &front();

        const Type &front() const;

        void push(const Type &);

        void pop();

        bool empty();

        private:

        ......

};

  • 编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。推导出实际模板实参后,编译器使用实参代替相应的模板形参产生并编译该版本的函数。
  • 用作模板形参的名字不能再模板内部重用,例如:

template<class T> T calc(const T &a, const T &b)

{

        typedef double T; //error:redeclares template parameter T

        T tmp = a;

        return tmp;

}

  • 同一模板的声明和定义中,模板形参的名字不必相同。

template<class T> T calc(const T&, const T&);

template<class U> U calc(const U&, const U&);

template<class Type> Type calc(const Type &a, const Type &b){}

  • 每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明是错误的。

//error:must procede U by either typename or class

template<typename T, U> T calc(const T&, const U&);

  • 模板类型形参可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。
  • 在模板定义内部指定类型

示例1:

template <class Pram, class U>

Parm fcn(Parm *aray, Uvalue){

        Parm::size_type *p;

}

示例二:

template <class Pram, class U>

Parm fcn(Parm *aray, Uvalue){

        typename Parm::size_type *p;

}

        分析:size_type必定是绑定到Parm的那个类型的成员,但我们不知道size_type是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字制定数据成员,而不是类型。如果希望编译器将size_type当做类型,则必须显式告诉编译器,如示例二。

  • 非类型模板形参

template<class T, size_t N> void array_init(T (&parm)[N]){

        for(size_t i=0;i!=N;++i)

                parm[i] = 0;

}

        数组名作为参数传递时不转换为指针的例外情况有:

  • 数组名用作取地址操作符(&)的操作数
  • 数组名用作sizeof操作符的操作数
  • 用数组对数组的引用进行初始化时(示例中即为该种情况)

 

  • 实例化

1)类模板实例化

  • 类模板不定义类型,只有特定的实例才定义了类型。
  • 用模板类定义的类型总是包含模板实参。例如,Queue不是类型,而Queue<int>或Queue<string>才是类型。因此,模板类Queue的实例化示例如下:

Queue<int> qi;

Queue<string> qs;

Queue qs; //错误

2)函数模板实例化

  • 使用函数模板时,编译器通常会为我们推断模板实参。
  1. 模板实参推断
  • 多个类型形参的实参必须完全匹配。例如compare(int,int)或compare(shor,shor)

例如:

short si;

compare(si, 1024);//错误

如果compare的设计者想要允许实参的常规转换,则函数必须用两个类型形参来定义:

template<typename A, typename B>

int compare(const A &v1, const B &v2){

if(v1<v2) return -1;

if(v2< v1) return 1;

return 0;

}

  • 类型形参的实参的受限转换

const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型和实参都忽略const,即无论传递const或非const对象给接受非引用类型的函数,都是用相同的实例化。

数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当做指向函数类型的指针。

示例如下:

template<typename T> T fobj(T, T);

template<typename T> T fref(const T&, const T&); //引用实参

string s1("a value");

const string s2("another value");

fobj(s1, s2); //ignore const

fref(s1, s2); //non const object s1 converted to const reference

int a[10], b[42];

fobj(a, b);

fref(a, b); //error,当参数为引用时,数组不能转换为指针,a和b的类型不匹配,调用出错

  • 模板实参推断与函数指针

获取函数模板实例化地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。如果不能从函数指针类型确定模板实参,就会出错。例如:

void func(int(*) (const string &, const string &));

void func(int(*) (const int &, const int &));

func(compare); //error:which instantiation of compare?

  1. 函数模板的显式实参
  • 情景:希望定义名为sum、接受两个不同类型实参的函数模板,希望返回的类型足够大,可以包含按任意次序传递的任意两个类型的两个值的和。怎样才能做到?

在返回类型中使用形参,例如:

template<class T1, class T2, class T3>

T3 alternative_sum(T2, T1);

正确的调用方法为:long val2 = alernative_sum<long, int, long>(i,lng)(其中i为int,lng为long类型)

  1. 非类型模板实参必须是编译时常量表达式。
  2. 类模板中的友元声明
  • 普通友元

非模板类或非模板函数可以使类模板的友元:

template<class Type> class Bar{

friend class FooBar;

friend void fcn();

......

};

  • 一般模板友元关系

template<class Type> class Bar{

template<class T> friend class Foo1;

template<class T> friend void temp1_fcnl(const T&);

......

};

  • 特定的模板友元关系

类也可以只授予对特定实例的访问权:

template<class T> class Foo3;

template<class T> void temp1_fcn3(const T&);

template<class type> class Bar{

friend class foo3<Type>;

friend void temp1_fcn3<Type>(const type&);

......

};

Bar<int> bi; //Foo3<int> 和temp1_fcn3<int>是友元

Bar<string> bs; //Foo3<string> 和temp1_fcn3<string>是友元

  • 想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数。

template<class T> class A;

template<calss T> class B{

friend class A<T>;

friend class B;

template<class S> friend class D;

friend class E<T>; //error:E没有声明为模板类

friend class F<int>; //error:F没有声明为模板类

};

  1. 成员模板
  • 任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员成为成员模板,成员模板不能为虚。
  • 定义成员模板

template<class Type> class Queue{

public:

template<class It> Queue(It beg, It end):head(0), tail(0) {copy_elems(beg, end);}

template<class It> void assign(Iter, Iter);

private:

template<class Iter> void copy_elems(Iter, Iter);

};

  • 在类外部定义成员模板

当成员模板是类模板的成员时,它的定义必须包含模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign函数定义如下:

template<class T> template<class Iter>

void Queue<T>::assign(Iter beg, Iter end){

....

}

  • 成员模板遵循常规访问控制
  1. 类模板的static成员

Foo类的每个实例化有自己的static成员。每个实例化表示截然不同的类型,所以给定实例化的所有对象都共享一个static成员。

  • 实用类模板的static成员

当试图通过类使用static成员时,必须引用实际的实例化.与任意其他成员函数一样,static成员函数只有在程序中使用时才能进行实例化。

Foo<int> fi, fi2;

size_t ct = Foo<int>::count();

ct = fi.count();

ct = fi2.count();

ct = Foo::count(); //error:要指明模板实例化

  • 定义static成员

template<class T>

size_t Foo<T>::ctr = 0;

  1. 模板特化
  • 函数模板的特化

模板特化必须总是包含空模板形参说明符,即template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显示指定模板实参。如下:

template<>

int compare<const char*>(const char* const &v1, const char* const &v2){

return strcmp(v1,v2);

}

template<> int compare(cosnt char* const &v1, const char* const &v2);//函数声明

  • 类模板的特化

template<> class Queue<const char*1>{

......

};

在类特化外部定义成员时,成员之前不能加template<>标记。例如

void Queue<const char*>::push(const char* val){

reutrn real_queue.push(val);

}

  • 特化成员而不特化类、

template<>

void Queue<const char*>::push(const char* const &val){

........

}

  • 类模板的部分特化

类模板的部分特化本身也是模板。部分特化的定义看来像模板定义,这种定义以关键字template开头,接着是由尖括号括住的模板形参表。部分特化的模板形参表是对应的类模板定义形参表的子集。部分特化与对应类模板有相同名字,即这里的some_template。

template<class T1, class T2> class come_template{

......

};

template<class T1> class come_template<T1, int>{

......

}

  1. 重载与函数模板
  • 函数匹配与函数模板

函数模板可以重载:可以定义有相同名字但是形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。

当匹配同样好时,非模板版本优先。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值