赞
踩
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。
多态性(polymorphism)是考虑在不同层次的类中,以及在同一类中,同名的成员函数之间的关系问题。函数的重载,运算符的重载,属于编译时的多态性(早期绑定)。以虚基类为基础的运行时的多态性(晚绑定,只有在运行时才知道和哪个函数有关系)是面向对象程序设计的标志性特征。体现了类推和比喻的思想方法。
层次概念是计算机的重要概念。通过继承的机制可对类分层,提供类型/子类型的关系
C++通过类派生的机制来支持继承。被继承的类称为基类(base class)或超类或父类,新派生的类称为派生类(derived class)或子类
基类和派生类的集合称作类继承层次结构,如果基类和派生类共享相同的接口,则派生类称作基类的子类型
派生反映了事物之间的联系,事物的共性与个性之间的关系。派生与独立设计的若干类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程
单一继承:一个派生类只有一个直接基类,如下:(single inheritance)
class 派生类名: 访问限定符 基类
{
private:
成员表1
};
多重继承:一个派生类同时有多个基类,如下:
class 派生类名: 访问限定符 基类1, 访问限定符 基类2...
{
private:
成员表1
};
注意:很多时候避免多重继承,多重继承会引入很多麻烦和其好处不成正比
继承方式:公有继承public、保护继承protected、私有继承private,继承方式如果省略,表示私有继承,即类的默认继承方式是私有的
继承机制优点:代码重用,基类被派生类继承后,派生类中就不需要重复编写基类中的成员
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
访问 | public | protected | private |
---|---|---|---|
同一个类(本类的成员函数) | yes | yes | yes |
派生类中(派生类的成员函数) | yes | yes | no |
外部的类(如该类的对象) | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
class Base
{...};
class Derived:private Base
{...};
私有继承时,基类中的public和protected成员在派生类中均变成了private成员,在派生类中可直接访问;而基类中的private成员虽然被继承下来了,但在派生类中不可直接访问
class Base
{...};
class Derived:protected Base
{...};
保护继承时,基类中的public和protected成员在派生类中均变成了protected成员,在派生类中可直接访问;而基类中的private成员虽然被继承下来了,但在派生类中不可直接访问
公有继承时,基类中的public和protected成员在派生类中仍为public和protected成员,在派生类中可直接访问;而基类中的private成员虽然被继承下来了,但在派生类中不可直接访问
公有继承代表是一个的意思:is a
学生继承自人类(Student is a person) ,人类是基类,学生类是派生类,“人类是个泛泛的概念,学生类是它的特例,学生是人,但是人不一定是学生”
派生类的内存结构
对于派生类Derived来说,它一共有四个成员,分别是:隐藏的父类Base、x、y、z
两层继承内存结构如下:
公有继承方式下,对于派生类的成员函数,本类的private,protected,public数据成员都可以访问,“自己可以访问自己的私有公有保护”,但是不可以访问父类的private成员,父类的protected和public成员可以访问
如下图:本类的成员函数不可以访问父类的私有数据成员,其余的都可以访问
如果是私有继承方式,仍旧不能访问父类的私有数据成员,其余的可以访问,私有继承只是把父类的保护和公有成员的属性改为私有的了,公有继承不改变父类保护和公有成员的属性
如果是保护继承方式,仍旧不能访问父类的私有数据成员,其余的可以访问,保护继承只是把父类的保护和公有成员的属性改为保护的了
总结:继承关系中,在派生类中可以访问继承来的对象的保护和公有,继承来的对象的私有不可访问
多层继承关系中也是一样的
私有继承来的则有一个私有的隐藏父类对象,保护继承来的则有一个保护的隐藏父类对象,公有继承来的则有一个公有的隐藏父类对象,继承方式决定了隐藏的父类的对象的可访问性
在派生类中指的是派生类的成员函数(Derived::set()),在派生类外则如该派生类的对象(Derived der;),在派生类中可以访问继承来的对象的保护和公有,派生类外只有公有成员可以直接访问
保护成员的优势:无论何种继承方式,父类的私有都不可访问,而对于保护成员,无论何种继承方式在派生类中都是可直接访问的
对于一个单独的类来说,private成员和protected成员都是在类内可以直接访问的,在类外不可以直接访问,在这方面没什么区别。
在有继承的情况下,无论何种继承方式,private成员都无法在派生类中直接被访问。而对于protected成员,无论何种继承方式,在派生类中都是可以直接访问的,这就是protected成员的优点
那么在什么情况下基类的protected成员的类内直接访问特性能传递到派生类的派生类中呢?这要根据不同的派生方式决定:
小结:protected成员的优点:既可以在本类中实现数据的隐藏(类内可直接访问,类外不可被直接访问),又可以将其类内直接访问特性传递到派生类中(在派生类中可以直接访问,类外不可直接访问)。而private成员不具备将类内直接访问特性传递到派生类中
若一个类是由多个基类派生出来的,则在定义派生类构造函数时,需要调用基类的构造函数,初始化基类成员
派生类构造函数的一般格式
//类外定义时
ClassName::ClassName(args) : Base1(arg_1),Base2(arg_2),...
{
派生类自身的构造函数体
};
派生类构造函数的执行顺序为:首先依次调用基类的构造函数Base1(arg_1), Base2(arg_2), … ,然后执行派生类自身的构造函数体
析构顺序为:首先执行派生类自身的析构函数体,然后按~Basen()…~Base2(), ~Base1(),的顺序调用基类的析构函数,即析构函数的执行顺序与构造函数相反
若在派生类中除了包含基类成员,还包含对象成员,则在派生类的构造函数的初始化成员列表中不仅要列出基类的构造函数,也要列举对象成员的构造函数。构造时,先调用基类的构造函数,再调用对象成员的构造函数,最后进入派生类自身的构造函数,析构顺序与之相反
C++中派生类继承基类的成员,包括数据成员和成员函数。例如基类的私有数据成员被派生类继承后,即使在派生类中不能直接访问,但它也被继承了。
但是基类的构造函数和析构函数不会被派生类继承,而只能通过派生类的构造函数或析构函数自动调用,完成对基类数据成员的构建或清理工作
二义性(访问冲突):在多重继承中,当在派生类中出现两个以上的同名可直接访问的基类成员函数时,便出现了二义性,即访问冲突,如下图:(A类和B类具有同名成员x和Show(),它们均被继承到C类中)
解决方法:
由于C++是通过作用域运算符来解决访问二义性问题,因此规定任一基类在派生类中只能被直接继承一次,否则访问冲突无法通过作用域运算符解决。
支配规则:在C++中,允许派生类中新增加的成员名与其基类的成员名相同,这种同名并不产生访问二义性。在派生类中访问同名成员时,若直接用成员名访问,则访问的是派生类自身的成员(就近);要访问基类的同名成员,可以使用作用域运算符来限定。即对同名成员的访问,派生类优先,这种优先关系称为支配规则
对于如下左图的多重继承关系中,在D类中包含了基类A的两个拷贝,所以一个D类对象包含了两份A类对象的数据成员。此时在D类的成员函数中,若欲访问A的成员x,则必须以B::x和C::x区分(注意:::不能嵌套使用)
若欲得到上图右边的继承关系,即要使得公共的基类在派生类中只有一个拷贝,则需要将A类说明成虚基类,说明虚基类的方法是在派生类B、C的定义中,在基类A的类名前加上关键字virtual,如下代码:
class A
{};
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B,public C
{};
说明成虚基类的一般格式:
class 派生类名: virtual 继承方式 基类名//virtual也可以放在继承方式和基类名中间,但一般放在前面
{...};
有虚基类时构造函数的调用顺序:若派生类中有多个基类,这些基类中有虚基类也有非虚基类,则先调用虚基类的构造函数,再调用非虚基类的构造函数。虚基类的构造函数按虚基类的继承顺序调用,非虚基类的构造函数按非虚基类的继承顺序调用,代码验证如下:
#include <iostream> using namespace std; class A { public: A() {cout<<" A ";} ~A() {cout<<" ~A ";} }; class B { public: B() {cout<<" B ";} ~B() {cout<<" ~B ";} }; class C { public: C() {cout<<" C ";} ~C() {cout<<" ~C ";} }; class D { public: D() {cout<<" D ";} ~D() {cout<<" ~D ";} }; class E:virtual public B, public A,public D,virtual public C { }; int main() { E e; cout << endl << endl; return 0; }
执行结果:
赋值兼容规则:在任何需要基类对象的地方都可以用公有派生类的对象来代替(只适合公有派生),即可以将公有派生类的对象赋值给基类对象,反之不允许
总结:派生类对象可以给基类对象赋值,base = derived;
,person = student;
子对象可以赋值给父对象的原因:公有派生代表”是一个“的意思,is a,Student is a person. 所以student可以赋值给person
例如下:人可以跳舞,学生是人,学生也就可以跳舞
class Person { private: int val; public: Person(int x = 0):value(x){} }; class Student: public Person { private: int num; public: Student(int x = 0):num(x),Person(x+10){} }; void dance(Person & per) { } int main() { Person se(23); Student st(20); Person &ps = st;//派生类对象可以初始化基类的引用 Person *p = &st;//派生类的对象的地址赋给其基类的指针变量 se = st;//派生类的对象可以赋值给基类的对象 dance(se);//人可以跳舞 //在任何需要基类对象的地方都可以用公有派生类的对象来代替 dance(st);//学生也可以跳舞 }
任何基类出现的地方,派生类都应当可以出现,但是反过来不行
C++切片现象:当子类对象去给父类对象赋值的时候,手起刀落,把子类中隐藏的父对象切出来赋值给父对象
派生类的对象的地址(st)赋给其基类的指针变量§时,p只能识别派生类隐藏的父对象那一部分(value),p不能识别num那一部分,因为p的类型是Person类型,它只能识别Person类型那么多的内容
Person *p = &st;//派生类的对象的地址赋给其基类的指针变量
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。