继承的概念
面对对象的语言具有四个特点:抽象,封装,继承和多态性。在上面的内容中我们已经分析了类和对象的概念,了解面对对象程序设计的两个重要特征--数据抽象与封装,已经能够设计出基于对象的程序,这个是面对对象设计的基础和需要掌握的思想。
如果要较好的面对对象进行程序的开发和设计,还必须掌握面对对象的另外两个重要的特征----继承性和多态性。
继承性是面对对象程序设计的重要特征,可以说,如果没有掌握继承性的概念,就相当没有掌握面对对象编程的思想。
类与类之间的关系
在面对对象中,类与类存在三种关系,简单说明
- has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据是另外一个已经定义的类。
- user-A 一个类部分地使用另外一个类。通过类之前成员函数的相互联系,定义友元函数对象参数传递实现。
- is-A 机制为成为”继承“。关系具有传递性,不具有对称性。
继承关系的举例说明
万物都有继承的关系,继承是一种重要的关系,比如对于植物而言
每种植物都属于低等植物和高等植物中一种,所以继承高等植物或者低等植物所具有的特性,然后也拥有自己独特的特点形成一类。
继承的相关概念
- 继承是类之间定义的一种重要的关系
- 一个B类继承A类,或者说类B为派生类
其实类的一些名词和阐述可以依靠下边的一副图来说明
类继承的语法形式
class 派生类名:基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1,访问控制,基类名2,...访问控制 基类名n
访问控制表示类对基类的继承方式,一共用三个关键词
public 公有继承
private 私有继承
protected 保护继承
注意:C++中的继承方式(public、private、protected)会影响子类的对外访问属性。
继承重要说明
- 子类拥有父类所有的成员变量和成员函数
- 子类可以拥有父类没有的方法和属性
- 子类说到底就是一种特殊的父类
- 子类对象可以当做父类对象进行使用
派生类的访问控制
派生类继承了基类的全部成员函数和成员方法(除了构造函数和析构函数除外),但是这些成员的访问属性,在派生的过程中是可以进行调整,那如何进行调整,可以通过对类对基类的继承方式来进行访问控制。
不同的继承方式会改变继承成员的访问属性
-
C++中的继承方式会影响子类的对外访问的属性
-
public继承:父类成员在子类中保持原有的访问级别
-
private继承:父类在子类中会变成private成员
-
protected继承:父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
-
-
private成员在子类中依然存在,但是却无法访问到。无论哪一种方式继承基类,派生类都无法直接的使用基类的私有成员。
继承的访问控制图
继承权限的三看原则
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问:
- 看调用语句,这句话写在子类的内部、外部
- 看子类如何从父类继承(public、private、protected)
- 看父类中的访问级别(public、private、protected)
派生类类成员访问级别设置原则
思考:如何恰当的使用public,protected和private为成员声明访问级别?
- 需要被外界访问的成员直接设置为public
- 只能在当前类中访问的成员设置为private
- 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间
继承中构造和析构
类型兼容原则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来代替。通过公共继承,派生类得到了基类中除构造函数和析构函数之外的所有成员。这样,公有派生类实际就具备基类所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容原则中所指的替代包括以下几种情况:
- 子类对象可以当作父类对象使用
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象进行使用,但是只能使用从基类继承的成员。类型兼容规则是多态性的重要基础之一。
但是对以上进行总结就是子类是特殊的父类(base *p = &child)
继承中对象模型
类在C++编译的内部都可以理解成结构体
子类是由父类成员叠加子类新成员得到的
//子类对象如何初始化父类成员
//继承中的构造和析构
//继承和组合混搭情况下,构造函数、析构函数调用顺序研究
#include <iostream>
using namespace std;
class Object
{
public:
Object(const char* s)
{
cout << "Object()" << " " << s << endl;
}
~Object()
{
cout << "~Object()" << endl;
}
};
class Parent : public Object
{
public:
Parent(const char* s) : Object(s)
{
cout << "Parent()" << " " << s << endl;
}
~Parent()
{
cout << "~Parent()" << endl;
}
};
class Child : public Parent
{
protected:
Object o1;
Object o2;
public:
Child() : o2("o2"), o1("o1"), Parent("Parameter from Child!")
{
cout << "Child()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
void run()
{
Child child;
}
int main(int argc, char* argv[])
{
cout << "demo_extend_construct_destory.cpp" << endl;
run();
system("pause");
return 0;
}
得到的结果如下所示:
继承中的构造析构调用原则
从上面的例子我们可以了解到:
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行结束后,执行子类的构造函数
- 当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
- 析构函数的调用的先后顺序和构造函数相反
继承和组合混搭的情况下,构造和析构的调用原则
原则: 先构造父类,在构造成员变量,最后构造自己
先析构自己,在析构成员变量,最后析构父类
感觉有点类型队列
继承中同名成员变量的处理方法
- 在子类成员变量与父类变量同名时
- 子类依然从父类继承同名成员
- 在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
- 同名成员存储在内存的不同位置
使用一张图来表示继承对于同名变量的处理方式
派生类的static关键字
如果继承和static关键字在一起会发生什么现象了
理论知识:
- 基类定义的静态成员,将被所有的派生类给共享
- 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中有不同的范文方式(遵守派生类的访问控制)
- 派生类中的访问静态成员,用以下形式显式的说明:
- 类名::成员
- 或者通过对象访问 对象名.成员
总结:
- static函数也要遵守三个访问原则
- static 容易没有分配内存(不但需要初始化,更重要的是显示的告诉编译器的内存分配)
- 构造函数默认为private
多继承
多继承的概念
- 一个类具有多个直接的基类的继承关系称之为多继承
- 多继承的声明语法
class 派生类:访问控制 基类名1,访问控制,基类名2...访问控制,基类名n
{
成员函数和成员变量;
};
- 类可以根据访问控制同时继承类A和类B的成员,并且在这个基础上增加自己的成员,如下图所示。
多继承的派生类构造和访问
- 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
- 执行顺序与单继承构造函数情况类似,多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
- 一个派生类的对象拥有对个直接或间接基类的成员。不同名成员访问不会出现二义性,但是不同的基类有同名的成员,派生对象在进行访问应该加以识别。
一个多继承的案例
虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性,比如以下的示意图所示:
- 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生 二义性
- 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
- 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
- 虚继承声明使用关键字 virtual
继承的总结:
- 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
- 单继承的派生类只有一个基类。多继承的派生类有多个基类。
- 派生类对基类成员的访问由继承方式和成员性质决定。
- 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
- C++提供虚继承机制,防止类继承关系中成员访问的二义性。
- 多继承提供了软件重用的强大功能,也增加了程序的复杂性。