C++的代码重用

C++的主要目标就是代码的重用。公有继承是一种方法,但不是唯一的方法。
有时候类成员本身是另一个类的对象。这种方法称为包含、组合或者层次化。包含、私有继承和保护继承可以实现has-a关系,即新的对象将包含另一个类的对象。
一:包含其他类对象成员的类
       使用公有继承时,派生类可以继承接口(派生类可直接调用基类方法),可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
      C++包含让程序员能够限制程序结构的特性------使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据,等等。
      构造函数使用成员初始化列表来初始化内置的数据成员(const或引用);
     派生类构造函数使用成员初始化列表来初始化派生类对象的基类部分,此时使用基类的构造函数;
     对于组合(包含关系),对于成员对象,构造函数使用成员名。(和处理内置类型一样)
     当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。
二:私有继承
      c++实现has-a关系的另一种方式-------私有继承。使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员,这意味着基类方法将不会成为派生类的公有接口的一部分,但可以在派生类方法中使用基类方法。
      使用公有继承,基类的公有方法将成为派生类的公有方法(派生类对象直接调用),总之,派生类将继承基类的接口;这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。总之,派生类不会继承基类的接口,这种不完全继承是has-a关系的一部分。
      使用私有继承,类将继承实现。
      包含将一个对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。获得实现,但不获得接口。
       要使用私有继承,就得使用关键字private来定义类(private是默认值)
1. 初始化基类组件
    包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。
    对于继承类,新版本的构造函数将使用成员初始化列表语法,它使用的是类名而不是成员名。不管是公有还是私有,都得使用基类的构造函数初始化继承的基类组件。
2.访问基类的方法
    使用私有继承时,只能在派生类的方法中使用基类的方法。总之,使用包含时使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法。
3.访问基类对象
    使用作用域解析运算符可以访问基类的方法,但是要使用基类对象本身,怎么办???答案是使用强制类型转换,指针this指向用来调用方法的对象,因此,*this为用来调用方法的对象。例:
    const string & Student :: Name() const
  {
      return (const string &)*this;
   }  //该引用指向用于调用该方法的Student对象中继承的string对象.
4.访问基类的友元函数
   用类名显式的限定函数名不适合于友元函数,这是因为友元不属于类。然而,可以通过显式的转换为基类来调用正确的函数。即在继承类的友元函数中将继承类对象强制转换为基类对象。
   在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。(公有继承可以)
通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。
保护继承
    保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected;使用保护继承时,基类的公有成员和保护成员都将称为派生类的保护成员。和私有继承一样,基类的接口在派生类中可以使用,但是不是继承(接口不是公有的),在继承层次结构之外是不可用的。
    使用私有继承时,第三代类将不能继承基类的接口了,(在第二代类已经变成私有接口);使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
使用using重新定义访问权限
      使用保护继承或私有继承时,基类的公有成员将变成派生类的保护成员或私有成员。对于公有继承,基类的方法是可以在派生类外面使用的(派生类对象直接调用);但是保护继承或私有继承都没有这种待遇,只能在派生类方法中使用基类方法,仅限于派生类内部。
      如果要使基类的方法在派生类外面使用,方法之一是定义一个使用该基类方法的派生类方法;使用作用域解析运算符::
     double Student :: sum() const
     {
     return std::valarray<double>::sum();
     } //Student类是派生类,valarray<double>是基类;
另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。这个声明的特定基类成员就相当于公有的了!!!派生类对象在派生类外部就可以直接调用了!!!
class Student : private std::string
{
    .........;
public:   //在public部分声明;
       using std::string::operator[];  //using声明只使用成员名----没有圆括号,函数特征标和返回类型。
       using std::string::length;
   .........;
};  
    using声明只能用于私有或保护继承,而不能是包含。
三. 多重继承
       MI描述的是有多个直接基类的类(不是像派生链那样,注意直接两字)。与单继承一样,公有MI表示的也是is-a关系。必须使用关键字public来限定每一个基类。
       产生的两个主要问题:
       1. 从两个不同的基类继承同名方法; 派生类对象不知道调用哪个基类的方法???
       2. 从两个或更多相关基类那里继承同一个类的多个实例。    worker----->singer、waiter----->singingwaiter。singingwaiter继承了singer和waiter类中的worker组件,这就产生了两个worker组件了。
       例: singingwaiter ed;
               worker * pw = &ed; //这将产生二义性;虽然可以将基类指针设置为派生类对象中的基类对象的地址,但是ed中包含两个worker组件,有两个地址可以选择,所以应使用类型转换来指定对象。
       worker * pw1 = (waiter *) &ed;
       worker * pw2 = (waiter *) &ed;
  这将使得基类指针来引用不同的对象(多态化)复杂化。
1. 虚基类
    虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。在类声明中使用关键字virtual,可以使得worker被看作singer和waiter的虚基类;
   class singer : public virtual worker{.....};
   class waiter : virtual public worker{.....};  //virtual与public次序无关紧要;singer类和waiter类共享worker类;
2. 新的构造函数规则
   对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。A类派生出B类,B类派生出C类。构造函数初始化过程:C类的构造函数只能调用B类的构造函数,B类的构造函数只能调用A类的构造函数。
  对于虚基类,则这种信息自动传递将不起作用。
  为避免这种冲突,C++在基类是虚的,禁止信息通过中间类自动传递给基类。
  如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式的调用该虚基类的某个构造函数。
3. 使用哪个方法
    对于单继承,如果没有重新定义函数,则将使用最近祖先中的定义。而对于多重继承,每个直接祖先都有一个同名函数,这将使得调用产生二义性。
    singingwaiter newwhire(“wahac schj”,2005,6,sj);
    newwhire.Show();
   可以使用作用域解析运算符来澄清编程者的意图:
   
  singingwaiter newwhire(“wahac schj”,2005,6,sj);
   newwhire.singer::Show();
对于单继承来说,派生方法调用基类方法是可以的,其中每个派生类使用基类显示信息,并添加自己的信息;
但是会产生调用同一个方法两次,如何解决呢???
一种方式是使用模块化方式,而不是递增方式,即提供一个只显示worker组件的方法和一个只显示waiter组件或singer组件的方法。
另一种方法是将所有的数据组件都设置为保护的,而不是私有的,不过使用保护方法就可以更严格的控制对数据的访问。
四. 类模板
     继承(公有、私有和保护)和包含并不总是能够满足重用代码的需要。容器类设计用来存储其他对象或数据类型。模板提供参数化类型即能够将类型名作为参数传递给接收方来建立类或函数。

  1. 定义类模板

      template <class Type>

      关键字template告诉编辑器,将要定义一个模板。尖括号中的内容相当于函数的参数列表。可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把type看作是该变量的名称。当模板被调用时,Type将被具体的类型值(int或string)代替。在模板定义中,可以使用泛型名来标识要存储在栈中的类型。如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。模板的具体实现-------如用来处理string对象的栈类-----被称为实例化或具体化。

      由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。A: 每个函数头都将以相同的模板声明打头;B: 还需将类限定符从Stack::修改为Stack<Type>::。

   2. 使用模板类

       仅在程序包包含模板并不能生成模板类,而必须请求实例化。为此,需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换类型名。泛型标识符----Type-----称为类型参数。注意,必须显示的提供所需的类型。

    3. 深入探讨模板类

       栈的任务是管理指针,而不是创建指针。

    4. 数组模板实例和非类型参数

        建立一个允许指定数组大小的简单数组模板。一种方法是在类中使用动态数组和构造函数参数来提供元素数目。另一种方法是使用模板参数来提供常规数组的大小。例:template<class T, int n>-----n即为个数。这种参数是非类型或表达式参数。表达式参数可以是整型、枚举、引用或指针。模板代码不能修改参数的值,也不能使用参数的地址。实例化模板时,用作表达式参数的值必须是常量表达式。

         表达式参数表示法的缺点是:每种数组大小都会生成的自己的模板;而数组动态生成和构造函数表示法,只会生成一个类声明。

     5. 模板多功能性

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

      在模板语法中,维的顺序与等价的二维数组相反。

      模板可以包含多个类型参数。例如:template <class T1, class T2>

      可以为类型参数提供默认值,例:template <class T1, class T2=int>; 虽然可以为类模板类型提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数提供默认值,这对于类模板和函数模板是通用的。

       6. 模板的具体化

       隐式实例化、显式实例化和显式具体化,它们统称为具体化。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。

      a: 隐式实例化即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义;

      b: 显式实例化-----当使用关键字template并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中。例: template class ArrayTp<string, 100> 虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。

      c: 显式具体化------特定类型(用于替换类模板中的泛型)的定义。当具体化模板和通用模板都与实例化请求匹配时,编译器都将使用具体化版本。template <> class Classname<specialized-typr-name> {...};

       d: 部分具体化-----部分限制模板的通用性,可以给类型参数之一指定具体的类型;

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

       7. 成员模板

       嵌套使用: template <typename T>

                              template <typename U>


       

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值