第十三章 类继承

继承很好理解了,如果我们开发了一个水果的类,我们可以继续开发一个苹果的子类或者橙子的子类。苹果和橙子的子类都可以继承水果类的特性和方法。那么这个继承是怎么实现的,哪些可以继承哪些不能继续,苹果和橙子之间又是什么关系,让我们带着这些问题来看这章。


1. 一个简单的基类

从一个类(水果)派生出另一个类(苹果,橙子)时,原始类(水果)称为基类,继承类称为派生类(苹果,橙子)。书中给了一个简单的基类:乒乓球会员TableTennisPlayer。这个基类很简单,包括名字和有没有球桌三个数据成员,构造函数,显示名字,返回有没有球桌等方法成员。这里构造函数用了成员初始化列表语法,就是在构造函数定义时,通过这样来初始化数据成员:

TableTennisPlayer::TableTennisPlayer( const string & fn, const string & ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht) {}

注意初始化的位置。


然后书中从这个类派生类一个RatePlayer子类,怎么派生呢,通过这个语句:

class RatePlayer : pubic TableTennisPlayer

{

};

这个冒号就表示他们的继承关系了。使用public表示它是TableTennisPlayer的公有基类,被称为公有派生。这个时候基类的公有成员将成为派生类的公有成员:基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。


派生类需要自己的构造函数,然后既然是继承,派生类可以添加自己的特性,表现为自己的数据成员和成员函数。


因为派生类不能直接访问基类的私有成员,所以书中的例子中要想通过派生类初始化基类的私有部分,必须调用基类的构造函数,所以派生类的构造函数这样写的:

RatePlayer::RatePlayer(unsigned int r, const string & fn, const string & ln, bool ht) : TableTennisPlayer(fn, lb, ht)

{ rating = r; }


同时要记住的是创建派生类对象时,程序首先创建基类对象。

如果上面省略: TableTennisPlayer(fn, lb, ht),程序会自动调用基类的默认构造函数: TableTennisPlayer(), 这就要求基类必须提供默认构造函数


也可以像书中一样用一个基类的对象来初始化派生类,这个时候调用的就是基类的复制构造函数了。


总的来说就是创建派生类对象是,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化心中的数据成员。相对应的是调用完毕时也会先运行派生类的析构函数,然后是基类的析构函数。


使用派生类和使用基类就超不多了,派生类即可使用基类的公共方法,也可以使用自己添加的方法。


另外,基类指针可以指向派生类对象,基类引用也可以引用派生类对象,但是这个时候基类指针或引用只能用于调用基类方法。同时这个规则也是单向的,反过来就不行。


2. 继承: is-a关系

C++有三种继承方式:公有继承,保护继续和私有继承。公有继承就是上面例子中的继承,也是最简单的一种,可以看出is-a 关系:派生类is a kind of 基类。与之对应的是has a关系,is implemented as a关系,uses a 关系等,这个在后面的练习中肯定会好好体会的。


3. 多态公有继承

有的时候派生类需要修改一下基类的方法,这个时候就需要多态公有继承,有两种方法可以实现:

在派生类中重新定义基类的方法

使用虚方法


比如书中举的的两种不同银行账户的例子:Brass, BrassPlus. BrassPlus继承了Brass,但添加了一下特性,比如withdraw方法就不一样了。这个时候基类和派生类的withdraw方法定义是前面都加了一个virtual关键字表示其为虚方法,之后便可以独立定义两个类的withdraw方法。


其实这个时候也可以不添加virtual关键字,仍可以独立定义,却别是什么呢。前面说了基类指针可以执行派生类对象,基类引用也可以引用派生类对象,如果方法是通过引用或指针而不是对象调用的,程序要确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了,程序将根据引用或指针指向的对象的类型来选择方法。


方法在基类被声明为virtual后,派生类中将自动称为虚方法。另外virtual只用于类声明的方法原型中,方法定义的时候不用写上去。


在书中例子的ViewAcct方法中,BrassPlus调用了Brass的ViewAcct方法,语法为

Brass::ViewAcct();

标准技术就是使用作用域解析运算符来调用基类方法.


4. 静态联编和动态联编

将源代码中的函数调用解释为特定的函数代码块被称为函数名联编(binding)。在没有虚函数时候,编译器在编译过程中就可以完成这种联编,即便有函数重载是也可以看调用函数使用的参数来决定,这种模式叫静态联编,也叫早期联编。有了虚函数时候,就得看虚函数被谁调用了,这个时候编译器必须在运行时选择正确的虚方法的代码,这时候就叫动态联编。


将派生类引用或指针转换为基类引用或指针被称为向上强制转换,这使公有继承不需要进行显示类转换。


在大多数情况下,动态联编很好,因为它让程序能够选择为特点类型设计的方法。但其效率没有静态联编高。


此外,还要注意:

析构函数不能是虚函数,没有意义

析构函数应当是虚函数,除非类不用做基类。这也意味着即使基类不需要显示析构函数提供服务,也不应依赖于默认构造函数,而应提供虚构函数,即使它不执行任何操作。

友元函数不是类成员,不能成为虚函数

在派生类重新定义了虚函数时,派生类对象将不能调用基类的同名虚函数


这引出两天经验规则:

如果重新定义继承的方法,应确保与原来的类型相同,但如果返回类型是基类的引用或指针,则可以修改为指向派生的引用或指针

如果基类声明被重载了,则应在派生类中重新定义所以的基类版本。


5. 访问控制protected

类里面可以有public或者private成员,也可以定义protected保护成员。

如果成员是保护成员,外部不能访问,但派生类可以直接访问。

最好对类数据成员采用私有访问,不要使用保护访问控制;同时通过基类方法使派生类能够访问基类数据。但对于成员函数来说,保护访问控制很有用,它让派生类能偶访问工作不能使用的内部函数。


6. 抽象基类

书中举了椭圆和圆的例子说明了我们需要一个非常抽象的类,包含椭圆和圆的特性。将类的某个方法设为纯虚函数则表示这个类是抽象基类,访问为在纯虚函数声明是加上=0.

抽象基类不能声明对象,抽象基类的唯一目的就是派生出可以声明对象的类


7. 继承和动态内存分配

第一种情况:派生类不使用new

比如基类某个数据成员需要动态内存分配,这个时候就需要注意析构函数,复制构造函数和重载复制运算符。如果这个基类的派生类没有额外的数据成员需要动态内存分配,则不需要显示定义析构函数,复制构造函数和赋值运算符,使用程序默认的即可。

第二章情况:派生类使用new

这个时候就需要显示定义析构函数,复制构造函数和赋值运算符,来处理额外添加需要动态分配内存的成员。


8 总结了C++使用类的很多要点,可以细看,这里就不重复了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值