C++ Primer 中文版 学习笔记(十五)

第16章  模板与泛型编程

1  

 

 

2   函数模板 

template<typename T>

int compare(const T &v1,const T &v2){ }   //模板形参不能为空

3   类模板

     template<class Type>

         class  Queue{

         public:

         Queue();

         private:

         //

}

4   非类型模板形参  例子  允许传递指向任意大小的数组的引用形参

         template<class T, size_t     N>

         void array_init(T (&parm)[N])

{

         for(size_ti = 0; i != N; ++i){

                   parm[i]= 0;

}

}

确定数组长度的函数模板

template<class T,size_t     N>

         std::size_t          size(T   (&parm)[N])

{

         return N

}

 

1        模板函数的显式实参

template<class   T1,class  T2,class   T3>

T1  sum(T2 , T3)

2        模板编译模型

a)        包含编译模型,编译器必须看到用到的所有模板的定义。#include “xx.cc”

b)        分别编译模型,编译器跟踪相关模板定义。export,指定定义可能会在其他文件中产生实例化

3        当与c风格字符串一起使用时,可能需要模板特化。

4          所谓泛型程序设计就是以独立于任何特定类型方式编写的代码.使用泛型程序时,我们需要提供具体程序实例所做的类型和值.并且依赖于某种形式的多态性.
  面向对象程序设计中的多态性在运行时应用于存在继承关系的类,我们能够编写使用这些类的代码,忽略基类和派生类之间类型上的差异
  另外面向对象程序设计所依赖的多态性称为运行时的多态性,泛型程序设计所依赖的多态性称为编译时多态性或者参数式多态性.

5        函数模版:独立于类型的函数,可作为一种方式,产生函数的特定类型版本.
   定义格式为:   
等价于
函数模版可以用于非模版函数一样的方式声明为inline.说明符放在模版形参表之后,返回类型之前,不能放在关键词template之前

6        模版形参的语法:

                i.              模版形参的名字可以在声明为模版形参之后直到模板声明或定义的末尾处使用.

              ii.              用作模板形参的名字不能在范本内部重用.这一限制意味着模版形参的名字只能在同一模板形参表中使用一次

             iii.              向其他任意函数或者类一样,对于模板可以只声明而不定义.声明必须指出函数或类是一个范本.

7        在模板定义内部指定类型:

通过在成员名前加关键词typename,告诉编译程序将成员当作类型.如果不加,则编译程序假定这样的名字指定数据成员而非类型.
  如果拿不准是否需要以typename指明一个名字是一个类型,那么指定它是一个好主意.

8              模板非类型形参是模板定义内部的常量值.在需要常量表达式的时候,可以使用非类型形参指定.
  在函数模板内部完成的操作限制了可用于实例化该函数的类型.程序员的责任是,保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用那些操作的环境中那些操作运行正常.
  我们在编写模板代码时,对实参类型的要求尽可能少是很有益的.

9            编写泛型代码的两个重要的原则:

a)         范本的形参是const引用

b)         函数体中的测试只有<比较

一般而言,编译范本时,编译程序可能会在三个阶段中标识错误:
◆ 第一阶段是编译模板定义本身.在这阶段中编译程序一般不能那个发现许多错误,可以检测到诸如漏掉分好或者变量名拼写错误一类的语法错误

◆  第二个错误检测事件是在编译程序见到模板使用时,在这个阶段,编译程序仍然没有很多检查可做.只检查实参的数目和类型是否恰当

◆  产生第三个时间是在实例化的时候,对程序是否有效所知不多.类似的甚至可能会在已经成功编译了使用模板的每个文件之后出现编译错误.只在实例化期间检测错误的情况很少,错误检测可能发生在连结时.

15    类模板的每次实例化都会产生一个独立的类类型.为int类型实例化的函数(或类)与其他的函数(或类)没有关系,对其他函数(或类)也没有特殊的访问权.

16   范本实参推断:
◆ 多个类型形参的实参必须完全匹配
 
◆ 类型形参的实参的受限转换:
  一般而言,不会转换实参以匹配已有的实例化,相反,会产生新的实例.除了产生新的实例化之外,编译器只会执行一下两种转换:
      ■ const转换:接受const引用或者const指针的函数可以分别用非const对象的引用或指针来调用,无需产生实例化.如果函数接受非引用类型,形参类型和实参都忽略const,即无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化
      ■数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换.数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针.
      ■应用于非模板实参的常规转换:类型转换的限制只适用于类相关为模板形参的那些实参.

     ■ 模板实参推断与函数指针:可以使用函数模板对函数指针进行初始化或者赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本.
      获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值.

17   函数模板的显示实参:
       ◆指定显式模板实参:
       ◆在返回类型中使用类型形参,指定返回类型的一种方式是引入第三个模板形参,他必须由调用者显示指定.

只是要注意一个问题:没有实参的类型可用于推断Type的类型,相反,调用者必须在每次调用fun时为该形参显式提供实参.
显式模板实参从左至右与对应模板形参相匹配.假如可以从函数形参推断,则只有结尾形参的显式模板实参可以省略.这类似于重载函数的调用规则.

这个例子中,初始化了Type的类型,但是由于Type2使用的类型是必须显式给出的,所以我们不能只是在调用时使用 而是只能全部形参都要显式指定实参.
      ◆显示实参与函数模板的指针:通过此来消除二义性.

18    当编译器看到模板定义的时候,它不立即产生代码.只有在看到用到模板时,如调用了函数目标那或者定义了类模板对象的时候,编译器才产生与特定类型的模板实参.
  模板编译的特例:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或者类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码.

19   C++中为编译模板代码定义的两种模型:
        ■包含编译模型:编译时,编译器必须看到用到的所有模板的定义.一般可以将所使用到的头文件,模板定义放在条件编译里面.
        ■分别编译模型:编译器会为我们跟踪相关的模版定义.但是我们必须让编译器知道要记住给定的模版定义,可以使用export关键字来做这件事.

在类的实现文件中使用export.导出类的成员将自动声明为导出的.(这与DLL类似)

20   模版中包含两种名字:独立于模板形参和依赖于模板形参的那些名字.
   设计者的责任是,保证所有不依赖于模板形参的名字在模板本身的作用域中定义.
   模板用户的责任是,保证与用来实例化模板的类型相关的所有函数、类型和操作符的声明可见的.在实例化模板的成员或函数模板的时候,用户必须保证这些声明是可见的.

21  通常使用类模板的名字的时候,必须指定模板形参.这一规则也有一个例外:在类本身的作用域中,可以使用类模板的非限定名.
  类模板成员函数的定义具有下面的形式:
        ▲必须以关键字template开头,后接类的模板形参表
        ▲必须指出它是哪个类的成员
        ▲类名必须包含其模板形参

22 类模板成员函数与其他模板函数的不同之处:在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定.(说白了就是在定义对象的时候就已经定义了成员函数的实参).另外类模板的成员函数只有为程序所用才进行实例化.如果某函数从未使用,则不会实例化该成员函数.
   非类型模板实参必须是编译时常量表达式.

23 在模板类中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:
   ■ 普通非模板类或者函数的友元声明,将友元关系授予明确指定的类或函数
   ■ 类模板或者函数模板的友元声明,将授予友元所有实例的访问权
   ■ 只属于对类模板或者函数模板的特定实例的访问权的友元声明
需要注意的是:当授予对给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板或者函数模板的声明.实际上编译器将友元声明也当作类或函数的声明对待.
  想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数(可以理解为无论是类模板还是函数模板必须遵循变量的使用规则:先定义后使用).

24   当在类模板作用域外部定义成员模板的时候,必须包含两个模板形参:类模板形参和自己的模板形参.(注意顺序)

25 类模板的静态成员:像非模板的成员函数一样使用,定义static成员的时候,必须在类外部出现数据成员的定义.

函数模板的特化是这样一个定义,该定义中一个或者多个模板形参的实际类型或者实际值是指定的,特化的形式如下:
     ● 关键字template后面接一对空的尖括号(<>);
     ● 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;
     ● 函数形参表;
     ● 函数体.
例子:
//special version of compare to handle C-sytle character strings

template <>

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

{

     return strcmp(v1,v2);

}

1        与任意函数一样,函数模板特化可以声明而无须定义.模板特化声明看起来与定义很像,但忽略了函数体.
   当定义非模板函数的时候,对实参应用常规转换;当特化模板的时候对实参类型不应用转换.在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参从模板定义实例化一个实例.
   与其他声明一样,应该在一个头文件中包含模板特化的声明,然后使用该特化的每个源文件包含该头文件.
   对于具有同一模板实参集的同一模板,程序不能既有显式特化又有实例化.

2         类模板特化应该与它所特化的模板定义相同接口,否则当用户试图是有那个未定义的成员时会感到奇怪.在特化外部定义成员时,成员之前不能加template<>标记.
  部分特化的定义域通用模板的定义完全不会冲突.部分特化可以具有与通用类模板完全不同的成员集合.类模板成员的通用定义永远不会用来实例化类模板部分特化的成员.

3         函数匹配与函数模板
如果重载函数中既有普通函数又有函数模板,确定函数的调用的步骤如下:

■ 为这个函数名建立候选函数集合,包括:

         ●与被调用函数名字相同的任意普通函数

         ●任意函数模版实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参.

        ■ 确定那些普通函数是可行的.候选集合中的每个模板实例都是可行的,因为模板实参推断保证函数可以被调用.

        ■ 如果需要转换来进行调用,根据转换的种类排列可行函数,记住,调用模板函数的实例所允许的转换是有限的.

                ● 如果只有一个候选函数可选,就调用这个函数

                ● 如果调用有二义性,从可行函数集合中去掉所有函数模板实例

        ■ 重新排列去掉函数模板实例的可行函数

                ● 如果只有一个函数可选,就调用这个函数

                ● 否则具有二义性.

30   设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为可能会使函数的用户感到奇怪,定义函数模板特化几乎总是比使用非模板版本更好.

没有更多推荐了,返回首页