第13章 类继承

下面出现的例子中,RatedPlayer类为派生类,TableTennisPlayer类为基类。

 

1.基本知识

      使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有成员也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。

      一个举例:

2.派生类的构造函数

      派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。所以派生类构造函数只能通过基类构造函数来初始化属于基类的数据成员。创建派生类对象时,程序将首先创建基类对象。这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表句法来完成这种工作。

       如果省略成员初始化列表,即如果不调用基类构造函数,程序将使用默认的基类构造函数。所以除非要使用默认构造函数,否则应该显式调用正确的基类构造函数。在派生类构造函数中,也有使用复制构造函数的时候,如上面第2个函数。如果需要使用复制构造函数,而又没有定义,编译器将自动生成一个。

      派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。除虚拟基类外,类只能将值传递回相邻的基类,但后者可以使用相同的机制将信息传递给相邻的基类,依次类推。成员初始化列表只能用于构造函数。

3.派生类和基类的一些关系

1)派生类对象可以使用基类的方法,条件是方法不是私有的。

2)基类指针或引用可以在不进行显式类型转换的情况下指向派生类对象。这一规则不是可逆的,即不可以将基类对象和地址赋给派生类引用和指针。如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。

3)编写的函数中,参数列表有基类引用,可指向基类对象或派生类对象。

4)编写的函数中,参数列表有基类指针,可指向基类对象或派生类对象。

5)引用兼容性属性将基类对象初始化为派生类对象。

      要初始化player,匹配的构造函数原型为:TableTennisPlayer(const RatedPlayer &); 类定义中没有这样的构造函数,但存在隐式复制构造函数: TableTennisPlayer (const TableTennisPlayer &); 形参是基类引用,前面说过,它可以引用派生类。因此,将player初始化为rplayer时,将要使用该构造函数。

6)将派生对象赋给基类对象。

      要初始化player,程序将使用隐式重载赋值操作符:TableTennisPlayer & operator= (const TableTennisPlayer &) const; 形参也是基类引用,它可以引用派生类。因此,将player初始化为rplayer时,将要使用该赋值操作符。

7)另外,如果派生类包含了这样的构造函数,即对将基类对象转换为派生类对象进行了定义,则可以将基类对象赋给派生对象。如果派生类定义了用于将基类对象赋给派生对象的赋值操作符,则也可以这么做。否则,只能进行显式强制类型转换后才能将基类对象赋给派生类对象。

     上述语句将转化为:temp.operator=(gp);  因为temp是BrassPlus对象,它调用函数为BrassPlus::operator=(const BrassPlus &)函数。因此有定义以下两种函数之一才能解决此问题:

     BrassPlus(const BrassPlus & ba, double m1=500, doublle r-0.1)  { ... }   

     BrassPlus & BrassPlus::operator=(const BrassPlus &) { ... } 

8)将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting),这使公有继承不需要进行显式类型转换。如5和6所提到的。但是相反的过程,----将基类指针或引用转换为派生类指针或引用----称为向下强制转换(downcasting),是不允许的,除非使用显式类型转换,尽管可能带来不安全的操作。

4.多态

      如果没有使用关键字virtual,程序根据引用类型或指针类型选择方法;如果用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。

       非构造函数不能使用成员初始化列表句法,但派生类方法可以调用公有的基类方法。在派生类方法中,标准的技术是使用作用域解析操作符来调用基类方法。如:

      如果写成void BrassPlus::ViewAcct() const{   ...   ViewAcct();   ...}将创建一个不会终止的递归函数。不过如果派生类没有重新定义某基类的方法,则代码不必对该方法使用作用域解析操作符。

      假设要同时管理Brass和BrassPlus账户,如果能使用同一个数组来保存Brass和BrassPlus对象,将很有帮助,但这是不可能的,数组中所有元素的类型必须相同。不过,可以创建指向Brass的指针数组,这样,每个元素的类型都相同,但由于是公有继承模型,因此Brass指针既可以指向Brass对象,也可以指向BrassPlus对象。因此,可以使用一个数组来表示多种类型的对象,这就是多态性。

      下面是一个多态的例子:

要使用虚拟析构函数:

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/1.jpg

不适当的代码将组织动态联编:

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/1633912209176117500.jpg

5.虚函数的工作原理

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/2.jpg

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/3.jpg

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/4.jpg

6.重新定义隐藏方法(虚方法参数列表不匹配时情况)

      如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外的情况是基类版本是隐藏的。

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/5.jpg

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/6.jpg

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/7.jpg

7.protected成员与单设计模式

      protected与private相似,在类外只能用公有类成员来访问protected部分中的类成员。他们之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员的行为与公有成员相似。

      关于单设计模式:

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/8.jpg

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/9.jpg

8.抽象基类

       abstract base class,ABC。当类声明中包含纯虚函数时, 则不能创建该类的对象。纯虚函数声明的结尾处为=0。包含纯虚函数的类只用作基类,要成为真正的ABC,必须至少包含一个纯虚函数。

9.继承和动态内存分配

      如果基类使用动态内存分配,那么这将怎样影响派生类的实现呢。当派生类不使用new时,不需要为派生类定义显式析构函数,复制构造函数和赋值操作符。但是,当基类和派生类都采用动态内存分配时,派生类的析构函数,复制构造函数和赋值操作符都必须使用相应的基类方法来处理基类元素,这种要求是通过3种不同的方式来满足的。

1)对于析构函数,这是自动完成的。派生类析构函数自动调用基类的析构函数,故其自身的职责是对派生类构造函数执行工作的进行清理。

2)对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成的,如果不这样做,将自动调用基类的默认构造函数。

hasDMA复制构造函数只能访问hasDMA的数据,因此它必须调用baseDMA复制构造函数来处理共享的baseDMA数据。没有参数类型为hasDMA引用的baseDMA构造函数,也不需要这样的构造函数。因为基类引用可以指向派生类型。

3)对于赋值操作符,这是通过使用作用域解析操作符显式地调用基类的赋值操作符来完成的。

baseDMA::operator=(hs);  是函数表示法,相当于:  *this=hs;

10.派生类友元如何访问基类的友元

        因为友元不是成员函数,所以不能使用作用域解析操作符来指出要使用哪个函数。解决方法是使用强制类型转换,以便匹配原型时能够选择正确的函数。

11. 类方法返回对象还是返回引用

       在编码方面,直接返回对象与返回引用之间唯一的区别在于函数原型和函数头:

      直接返回对象与按值传递对象相似:它们都生成临时拷贝。同样,返回引用与按引用传递对象相似:调用和被调用的函数对同一个对象进行操作。大部分情况下,返回引用可节省时间和内存,不过并不可以总是返回引用,函数不能返回在函数中创建的临时对象的引用。通用的规则是:如果函数返回在函数中创建的临时对象,则不要使用引用。如果函数返回的是通过引用或指针传递给它的对象,则应按引用返回对象。

12.类函数小结

https://p-blog.csdn.net/images/p_blog_csdn_net/xuanya0214/EntryImages/20091015/11.jpg

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值