第十四章 C++中代码重用

valarray类简介

valarrray类是由头文件valarray支持的。顾名思义,这个类用于处理数据。他支持诸如将数组中所有元素相加以及在数组中找到最大最小的值等操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。

模板特性意味着声明对象是,必须指定具体的数据类型。因此使用valarray类声明一个对象是需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型。

double x[3]={3.1,4.1,5.1};

valarray<int>q_values; // an array of int 

valarray<double>v1; //an array of double size 0

valarray<int> v2(8); //an array of 8 int elements;

valarray<int>v3(10,8) ;// an array of 8 int elements,each set to 10;

valarray<double>v4(x,3); // an array of 4 elements

                                          //initialized to the first 4 elements of x; 使用x前三个值赋给v4

valarray<int>v5 = {20,32,17,9};  //C++11

类的一些方法:

       operator[]()  能够访问各个元素;

       size()  返回包含的元素数;

       xum()  返回所有元素的总和;

       max()  返回最大的元素;

       min()  返回最小的元素。

接口与实现的概念

使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。

对于has-a关系而言,类对象不能获得包含对象的接口是一件好事。

https://www.cnblogs.com/dhm520/p/8423392.html

Student类示例

//studentc.h--defining a Student class using containment

#ifndef STUDENTC_H_

#define STUDENTC_H_

#include <iostream>

#include<string>

#include<valarray>

class Student

{

private:

       typedef std::valarray<double> ArrayDb;

       std::string name;

       ArrayDb scores;

       std::ostream & arr_out(std::ostream & os) const;

public:

       Student():name("Null Student"),scores() {}

       explicit Student(const std::string & s):name(s),scores() {}

       explicit Student(int n):name(s), scores() {}

       Student(const std::string & s, int n):name(s),scores(n) {}

       Student(const char * str, const double *pd, int n):name(str),scores(pd,n) {}

       ~Student() {}

       double Average() const;

       const std::string & Name() const;

       double & operator[] (int i);

       double operator[] (int i) const;

//friend

       friend std::istream & operator>>(std::istream & is, Student & stu);

       friend std::istream & getline(std::istream & is, Student & stu);

       friend std::ostream & operator<<(std::ostream & os, const Student & stu);

}

#endif

typedef std::valarray<double> ArrayDb;//这样在以后的代码中便可以使用表示ArrayDb,而不是std::valarray <double> ,因此类方法和友元函数可以使用ArrayDb类型。

代码中explicit回忆:C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。

C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。explicit构造函数的作用解析:explicit构造函数是用来防止隐式转换的。

https://baike.baidu.com/item/explicit/4941869?fr=aladdin

即可以用一个参数调用的构造函数将用作从参数类型到类类型的隐式转换函数,explicit用于关闭隐式转换。

注意:如果不使用explicit,可以编写如下的代码:

Student doh(“Homer”,10);

doh = 5;  //粗心的程序员键入了doh而不是doh[0],这会导致使用构造函数调用Student(5)将5替换为一个临时Student对象,并使用“Null name”来设置成员name的值。因此赋值操作将使用临时对象来替换原来doh值,使用了explicit之后,编译器将认为上述运算符是错误的。

使用explict防止但参数构造函数的隐式转换,使用const限制方法修改数据。这样做的根本原因是:在编译阶段出现错误由于在运行阶段出现错误。

初始化被包含的对象

构造函数使用您熟悉的成员初始化列表来初始化name和score成员对象。

Queue::Queue(int qs) : qsize(qs) {…}

还可以使用成员初始化列表来初始化派生对象的基类部分:

hasDMA::hasDMA(): baseDMA(hs) {…}

初始化列表中的每一项都调用与之匹配的构造函数。

初始化顺序:当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。如果代码使用一个成员的值作为另一个成员初始化表达式的一部分时,初始化顺序就非常重要了。

在类成员函数的声明和定义中,const的函数不能对其数据成员进行修改操作。const的对象,不能引用非const的成员函数。

1.使用被包含对象的接口

被包含对象的接口不是公有的,但可以在类方法中使用它。

double Student::Avergae() const

{

 if (scores.size() > 0)

 return scores.sum() / scores.size();

 else

 return 0;

}

上述代码定义了可由Student对象调用的方法,该方法内部使用了valarray的方法size()和sum()。 这是因为scores是一个valarray对象,所以它可以调用valarray类的成员函数。 总之,Student对象调用Student的方法,而后者使用被包含的valarray对象来调用valarray类的方法。

同样,可以定义一个使用string版本的<<运算符的友元函数:

//使用string版本的<<运算符

std::ostream & operator<<(std::ostream & os, const Student & stu)

{

 os << "Scores for " << stu.Name << ":\n";

    ...

}

因为stu.name是一个string对象,所以它将调用函数operator<<(ostream &, const string &),该函数位于string类中。 注意,operator<<(ostream & os, const Student & stu)必须是Student类的友元函数,这样才能访问name成员。

私有继承

使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。 这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。

1)包含与私有继承的区别:

包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中(包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员);子对象来表示通过继承或包含添加的对象;对于继承类,使用类名而不是成员名来标识构造函数

包含与私有继承的相同点:获得实现,但不获得接口。

1、Student类示例(新版本)

  要进行私有继承,请使用关键字private而不是public来定义类(实际上,private是默认值,因此省略访问限定符也将导致私有继承)。

  Student类应从两个类派生而来,因此声明将列出这两个类:

    class Student:private std::string, private std::valarray<double>{

    public:

      ...

    };

  使用多个基类的继承被称为多重继承(multiple inheritance,MI)。

包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。这是两种方法的第一个主要区别。

下面我们对Student类进行重新设计,新版本的名字叫做Student1,他使用私有继承来实现包含关系。

(1)初始化基类组件

对于私有继承类,构造函数将使用成员初始化列表语法,它使用类名而不是成员名来标示构造函数:

  String1(const char * s, const double * pd, int n):std::string(s), ArrayDb(pd, n){}

私有继承与包含唯一不同的地方在于:私有继承省略了显式对象名称,并在內联构造函数中使用了类名,而不是成员名。

#include <iostream>

#include <string>

#include <valarray>

class Student1:private std::string, private std::valarray<double>{

private:

    typedef std::valarray<double> ArrayDb;

    std::ostream & arr_out(std::ostream & os) const;

public:

    Student1():std::string("Null Student"),ArrayDb(){};

    explicit Student1(const std::string & s):std::string(s),ArrayDb(){};

    explicit Student1(int n):std::string("Nully"),ArrayDb(n){};

    Student1(const std::string & s, int n):std::string(s), ArrayDb(n){};

    Student1(const std::string & s, const ArrayDb & a):std::string(s), ArrayDb(a){};

    Student1(const char * s, const double * pd, int n):std::string(s), ArrayDb(pd, n){};

    ~Student1(){};

    double Average()const;

    double & operator[](int n);

    double operator[](int n)const;

    const std::string & Name()const;

    friend std::istream & operator>>(std::istream & is, Student1 & stu);

    friend std::istream & getline(std::istream & is, Student1 & stu);

    friend std::ostream & operator<<(std::ostream & os, const Student1 & stu);

};

(2)访问基类的方法

  使用私有继承时,只能在派生类的方法中使用基类的方法。私有继承能够使用类名和作用域解析运算符来调用基类的方法。

  总之,使用包含时将使用对象名来调用方法,而使用私有继承的时候将使用类名和作用域解析运算符来调用方法。

  (3)访问基类对象

  使用作用域解析运算符可以访问基类方法,但如果要使用基类对象本身需要进行强制类型转换,即将派生类强制转换为基类对象,结果将为继承而来的基类对象。常用this指针。

  (4)访问基类的友元函数

  与公有继承一样,要想访问基类的友元函数,需要对派生类进行强制类型转换,才能正确匹配基类友元函数的原型。

  有一点需要注意,在私有继承中,未进行显式转换的派生类引用或指针,无法赋给基类的引用或指针。

2、使用包含还是私有继承

  大多数C++程序员倾向于使用包含。首先,它易于理解。类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。其次,继承会引起很多问题,尤其从多个基类继承时,可能必须处理很多问题,如包含同名方法的独立的基类或共享祖先的独立基类。总之,实用包含不太可能遇到这样的麻烦。另外,包含能够包括多个同类的子对象,而继承只能使用一个这样的对象。

  然而,私有继承所提供的特性确实比包含多。例如,假设类包含保护成员(可以是数据成员,也可以是成员函数),则这样的成员在派生类中是可用的,但在继承层次结构外是不可用的。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承得到的将是派生类,因此它能够访问保护成员。

  另一种需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。

  提示:通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

保护继承

当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。

保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。

私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

 4、使用using重新定义访问权限

  使用保护派生和私有派生时,基类的公有成员将会成为保护成员或私有成员。如果要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。

  另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明(就像名称空间那样)来指出派生类可以使用特定的基类成员,即使采用的是私有派生。例如,假设希望通过Student类能够使用valarray的方法min()和max(),可以在Student的声明的公有部分加入如下using声明:

  class Student:private std::string, private std::valarray<double>{

  ....

  public:

      using std::valarray<double>::min;

      using std::valarray<double>::max;

      ...

  };

  上述using声明使得valarray<double>::min()和valarray<double>::max()可用,就像他们是Student的公有方法一样。

  注意,using声明只使用成员名——没有圆括号、函数特征标和返回类型。例如,为使Student类可以使用valarray的operator[]()方法,只需要在Student类声明的公有部分包含下面的using声明:

  using std::valarray<double>::operator[]();

  这将使两个版本(const和非const)都可用。这样,便可以删除Student::operator[]()的原型和定义。

  注意,using声明只适用于继承,而不适用于包含。


多重继承

1、在D中有多少个A?

  因为B和C都继承了一个A组件,因此D将包含两个A组件。

` 正如预期的,这将会引起问题。通常可以将派生类对象的地址赋给基类指针,但现在将出现二义性:

    D d;

    A * pa = &d;//发生二义性

  通常,这种赋值将把基类指针设置为派生类对象中基类对象的地址,但是d中包含两个A对象,有两个地址可供选择,所有应使用类型转换来指定对象:

    A * pa = (B *)&d;

    A * pa2 = (C *)&d;


 这将使得使用基类指针来引用不同的对象(多态性)复杂化。

  包含两个A对象拷贝还会导致其他的问题。然而,真正的问题是:为什么需要A对象的两个拷贝?因为D公有继承自B和C,而B和C又都公有继承自A,即D既是B也是C,同时B、C、D又都是A,他们之间是is-a关系;那么D对象中包含两个A子对象将很奇怪,D对象中应该只有一个A子对象而不是两个或多个。C++引入多继承的同时,引入了一种新技术——虚基类(virtual base class),使MI成为可能。

  (1)虚基类

  虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象,这种技术是通过在类声明中加入关键字virtual来实现的(virtual和public的顺序无关紧要):

      class B:public virtual A{....};

      class C:virtual public A{....};

  然后,可以将D类定义为:

      class D:public B, public C{....};

现在D对象中将只包含A对象的一个副本。从本质上说,继承的B和C对象共享一个A对象,而不是各自引入自己的A对象副本。

(2)新的构造函数

  使用虚基类时,需要对类的构造函数采用一种新的方法。对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。但这些构造函数可能需要将信息传递给基类。例如,可能有下面的构造函数:

    class A{

      int a;

    public:

      A(int n = 0):a(n){};

      .....

    };

    class B:public A{

      int b;

    public:

      B(int a = 0, int n = 0):A(a), b(n){};

      ...

    };

    class E: public B{

      int e;

    public:

      E(int a = 0, int b = 0, int n = 0):B(a,b), e(n){};

      .....

    };

  在这里,E类的构造函数只能调用B类的构造函数,而B类的构造函数只能调用A类的构造函数。这里,E类的构造函数将使用n值,并将a和b传递给B类的构造函数;而B类的构造函数使用值b,并将a传递给A类的构造函数。

  但是,如果A是虚基类:

    class A{

      int a;

    public:

      A(int n = 0):a(n){};

      ...

    };

    class F:virtual public A{

      int f;

    public:

      F(int a = 0, int n = 0):A(a),f(n){};

      ....

    };

    class G:public virtual A{

      int g;

    public:

      G(int a = 0, int n = 0):A(a), f(n){};

      ....

    };

  则这种信息自动传递将不起作用。例如:

    class H:public F, public G{

      int h;

    public:

      H(int a = 0, int f = 0, int g = 0, int n = 0):F(a,f),G(a,g),h(n){};

      .....

    };

存在的问题是,自动传递信息时,将通过2条不同的途径(F和G)将a传递给A对象。未避免这种冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类因此,上面的构造函数将初始化成员f和g,但a参数中的信息将不会传递给子对象A。然而,编译器必须在构造派生对象前构造基类对象组件:在上述情况下,编译器将使用A的默认构造函数。

  如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。因此,构造函数应该这样:

      class H:public F, public G{

        int h;

      public:

        H(int a = 0, int f = 0, int g = 0, int n = 0):A(a),F(a,f),G(a,g),h(n){};

        ...

      };

  上述代码显式调用构造函数A(int )。请注意,这种用法是合法的,对于虚基类,必须这样做;但对于非虚基类,则是非法的。

  警告:如果类有间接虚基类,则除非只需要使用该虚基类的默认构造函数,否则必须显式调用该虚基类的某个构造函数。

多重继承可能会导致函数调用的二义性。

可以使用作用域解析运算符::来澄清编程者的意图。

更好的方法是在D中重新定义show()方法,并指出要使用哪个show()。例如,如果希望使用D对象使用B版本的show(),则可以这样做:

      void D::show(){

        B::show();

      }

对于单继承,可以让派生类方法调用基类方法显示基类信息,并添加自己的信息,即递增的方式来显示派生类对象的信息。但是这种方法对于多继承来说是有缺陷的。例如,在D类的show()方法中同时调用基类B和基类C的show()方法,并加上自己的信息:

      void D::show(){

        B::show();

        C::show();

        std::cout << d;

      }

   然而,像上面的方法将会显示虚基类对象的信息两次,即会显示两次a的信息。因为B::show()和C::show()都调用了A::show()。

  解决这种问题的一种方法是使用模块化,而不是递增方式,即提供一个只显示A组件的方法和一个只显示B组件或C组件(而不是B和C组件)的方法。然后,在D::show()方法中将组件合起来。例如,可以像下面这样做:

  void A::Data()const{

    std::cout << "a:" << a;

  }

  void B::Data()const{

    std::cout << "b:" << b;

  }

  void C::Data()const{

    std::cout << "c:" << c;

  }

  void D::Data()const{

    B::Data();

    std::cout << ", ";

    C::Data();

  }

  void D::show()const{

    A::Data();

    std::cout << ", ";

    Data();

  }

  与此相似,其他show()方法可以组合适当的Data()组件。

  采用这种方法,对象仍可使用show()方法。而Data()方法只在类内部可用,作为协助公有接口的辅助方法。然而,使Data()方法成为私有的将阻止B中的代码使用C::Data(),这正是保护访问类的用武之地。如果Data()方法是保护的,则只能在继承层次结构中的类使用它,在其他地方则不能使用。

  另一种方法是将所有的数据组件都设置为保护的,而不是私有的,不过使用保护方法(而不是保护数据)将可以更严格地控制对数据的访问。

  总之,在祖先相同时,使用MI(多重继承)必须引入虚基类,并修改构造函数初始化列表的规则。另外,如果在编写这些类时没有考虑到MI,则还可能需要重新编写他们。

 (1)混合使用虚基类和非虚基类

  通过多种途径继承一个基类的派生类的情况:

  *如果基类是虚基类,派生类将包含基类的一个子对象;

  *如果基类不是虚基类,派生类将包含多个子对象;

*当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。


  (2)虚基类和支配

  使用虚基类将改变C++解析二义性的方式。

  使用非虚基类时,规则很简单。如果类从不同的类那里继承了两个或更多的同名成员(数据或方法),则使用该成员名时,如果没有使用类名将导致二义性。

  但如果使用的是虚基类,则这样做不一定会导致二义性。在这种情况下,如果某个名称优先于其他所有名称,则使用它,即便不使用限定符,也不会导致二义性。

  派生类中的名称优先于直接或间接祖先类中的相同名称。

  3、MI小结

  (1)不使用虚基类的MI

   *这种形式的MI不会引入新的规则。然而,如果一个类从两个不同类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。

   *如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。在某些情况下,这可能正是需要的,但通常情况下,多个基类实例都是问题。

  (2)使用虚基类的MI

  当派生类使用关键字virtual来指示派生时,基类就称为虚基类。主要变化(同时也是使用虚基类的原因)是,从虚基类的一个或多个实例派生而来的类将只继承一个基类对象。为实现这种特性,必须满足其他要求:

  *有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的。

  *通过优先规则解决名称二义性。(优先规则:派生类中的名称优先于直接或者间接父类中的相同名称。)

  MI会增加编程的复杂度。然而,这种复杂性主要是由于派生类通过多条途径继承同一个基类引起的。避免这种情况后,唯一需要注意的是,在必要时对继承的名称进行限定。

类模板

模板提供参数化类型,既能够将类型名作为参数传递给接受方来建立类或函数。

1.定义类模板

模板类以下面这样的代码开头:

template<class type>

关键字template告诉编译器,将要定义一个模板。尖括号中的内容相当于函数的参数列表。可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把type看作是该变量的名称。

较新的C++实现允许在这种情况下使用不太容易混淆的关键字typename代替class;

template<typename type>

每个函数头都将一项头的模板声明打头

template<class type>

另外含蓄将类限定符Stack::改为Stack<Type>::

模板的具体实现——被称为实例化或具体化。

使用模板类

仅在程序包含模板并不能生成模板类,而必须请求实例化。为此需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。

Stack<int> clone; //clone 变量名

范式标识符——例如这个int——称为类型参数,这意味着他们类似于变量,但赋给他们的不能是数字,而只能是类型。

模板的多功能性

可以将用于常规类的技术同于模板类。模板类可用作基类,可用作组件类,还可用作其他模板的类型参数。

1.递归使用模板

Arraytp<Arraytp<int,5>,10>twodee;

这使得twodee是一个包含10个元素的数组,其中每个元素都是一个包含5个int元素的数组。与之等价的常规数组声明如下

int twodee[10][5];

模板可以包含多个类型参数

模板的具体化

1.隐式实例化

声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义

Arraytp<double,10>*pt;

编译器在需要对象之前,不会生成类的隐式实例化

pt=new Arraytp<double,10>;

第二条语句导致编译器生成类定义,并根据该定义创建一个对象。

2.显式实例化

当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。

在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

3.显式具体化

显式具体化是特定类型(用于替换模板中的泛型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下可以创建显式具体化。

格式如下

template<> class classname<specialized-type-name>{...};

4.部分具体化

C++还允许部分具体化,即部分限制模板的通用性。

//general template

template<class T1, class T2> class Pair{...};

//specialization with T2 set to int

template<class T1> class Pair<T2,int>{...};

关键字template后面的<>声明的是没有被具体化得类型参数。因此,上述第二个声明将t2具体化为int,但t1保持不变。注意,如果指定所有类型,则<>内为空这将导致显式具体化。

//specialization with T1 and T2 set to int

template<> class Pair<int,int> {...};

如果有多个模板可供选择,编译器将使用具体化程度最高的模板:

Pair<double,double> p1; //use general Pair template

Pair<double,int> p2;        //use Pair<T1,int> partial specialization

Pair<int,int> p3;            //use Pair<int,int> explicit specialization

部分具体化特性使得能够设置各种限制,例如:

//general template

template<class T1, class T2, class T3> class Trio{...};

//specialization with T3 set to T2

template<class T1, class T2> class Trio<T1,T2,T2>{...};

//specialization with T3 and T2 set to T1*

template<class T1> class Trio<T1,T1*,T1*>{...};

给定上述声明,编译器将作出如下选择:

Trio<int,short,char *> t1; //use general template

Trio<int,short> t2;                //use Trio<T1,T2,T2>

Trio<char,char *,char *> t3;      //use Trio<T1,T1*,T1*>

在类模板的具体化中,成员方法的实例化是不能带模板头template<>的。

成员模板

模板可用作结构、类或模板类的成员,要完全实现STL设计,必须使用这项特性

template<typename T>

class beta

{

private:

template<typename V>    //nested template class member

class hold

{

private:

V val;

public:

hold(V v = 0) :val(v) {}

void show() const { cout << val << endl; }

V Value() const { return val; }

};

hold<T> q;          //template object

hold<int> n;        //template object

public:

beta(T t, int i) :q(t), n(i) {}

template<typename U>    //template method

U blab(U u, T t) { return (n.Value() + q.Value())*u / t; }

void Show() const { q.show(); n.show(); }

};

如果所用的编译器接受类外面的定义,则在beta模板之外定义模板方法的代码如下:

template<typename T>

class beta

{

private:

    template<typename V>      //declaration

    class hold;

    hold<T> q;

    hold<int> n;

public:

    beta(T t, int i): q(t), n(i) {}

    template<typename U>      //declaration

    U blab(U u, T t);

    void Show() const {q.show(); n.show();}

};

//member definition

template <typename T>

    template<typename V>

      class beta<T>:hold

      {

      private:

        V val;

      public:

        hold(V v=0):val(v) {}

        void show() const {std::cout<<val<<std::endl; }

        V Value() const {return val;}

};

//member definition

template <typename T>

  template <typename U>

    U beta<T>::blab(U u, T t)

    {

      return {n.Value()+q.Value())*u/t;

};

将模板用作参数

模板可以包含类型参数(如typename T)和非类型参数(如int n),模板还可以包含本身就是模板的参数,这种参数是模板新增的特性,用于实现STL

可以混合使用模板参数和常规参数,例如,Crab类的声明可以像下面这样打头:

template<template <typename T> class Thing, typename U, typename V>

class Crab

{

private:

    Thing<U> s1;

    Thing<V> s2;

...

}

模板参数T表示一种模板类型,而类型参数U和V表示非模板类型.。

模板类和友元

模板类声明也可以有友元,模板的友元分3类:

非模板友元;

约束模板友元:友元的类型取决于类被实例化时的类型;

非约束模板友元:友元的所有具体化都是类的每一个具体化的友元

模板类的非模板友元函数:

在模板类中将一个常规函数声明为友元:

template<class T>

class HasFriend

{

public:

  friend void counts();          //friend to all HasFriend instantiations

  ...

};

模板类的约束模板友元函数:

首先,在类定义的前面声明每个模板函数:

template<typename T>void counts();

template<typename T>void report(T &);

其次,在函数中将模板声明为友元,这些语句根据类模板参数的类型声明具体化:

template<typename TT>

class HasFriendT

{

...

    friend void counts<TT>();

    friend void report<>(HasFriend<TT> &);

};

最后,还需为友元提供模板定义

模板类的非约束模板友元函数:

通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的

template<typename T>

class ManyFriend

{

...

  template<typename C, typename D> friend void show2(C &, D &);

};

模板别名(C++11)

template<typename T>

  using arrtype=std::array<T,12>;      //template to create multiple aliases

arrtype<double> gallons;            //gallons is type std::array<double,12>

arrtype<int> days;                  //days is type std::array<int,12>

arrtype<std::string>months;        //months is type std::array<std::string,12>

C++11允许将using=用于非模板。用于非模板时,这种语法与常规typedef等价:

typedef const char * pc1; //typedef syntax

using pc2=const char *;      //using= syntax

typedef const int *(*pa1)[10];    //typedef syntax

using pa2=const int *(*)[10];      //using=syntax

http://www.cnblogs.com/li-peng/p/3512887.html

(完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容