第十三章 类继承
2016-11-25 04:31
134 查看
继承很好理解了,如果我们开发了一个水果的类,我们可以继续开发一个苹果的子类或者橙子的子类。苹果和橙子的子类都可以继承水果类的特性和方法。那么这个继承是怎么实现的,哪些可以继承哪些不能继续,苹果和橙子之间又是什么关系,让我们带着这些问题来看这章。
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++使用类的很多要点,可以细看,这里就不重复了
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++使用类的很多要点,可以细看,这里就不重复了
相关文章推荐
- 第十三章 类继承
- [学习笔记] C++ primer plus 第十三章 类继承
- C++ Primer Plus学习:第十三章 类继承(1)
- 【C++ Primer】第十三章 类继承
- 《C++ Primer Plus(第六版)》(27)(第十三章 类继承 编程题答案)
- C++ Primer Plus学习:第十三章 类继承(2)
- 【C++ Primer】第十三章 类继承
- 【C++ Primer】第十三章 类继承
- 第十三章:方法 第十四章:构造与析构 第十五章:类继承(322)
- C++读书笔记之类的继承1(第十三章)
- 继承和动态内存分配(C++ Primer Plus 第十三章)
- 第十三章:类继承
- 【C++ Primer】第十三章 类继承
- 第十三章 类继承
- C++ Primer Plus学习:第十三章 类继承(3)
- 2012/1/31 《C++ Primer Plus》第十三章:类继承 学习笔记
- 2012/1/31 《C++ Primer Plus》第十三章:类继承 学习笔记
- 《C++ Primer Plus(第六版)》(25)(第十三章 类继承 笔记)
- 《C++ Primer Plus(第六版)》(26)(第十三章 类继承 复习题答案)
- 继承和动态内存分配(C++ Primer Plus 第十三章)