继承和派生 —— 类与类之间的关系、继承的基本概念、继承的方式、继承中的构造和析构
1、类与类之间的关系
has-A,uses-A 和 is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
class A { public: void funcA() { } int a; }; //类B拥有类A的成员变量,B has A,类B 依赖于 类A class B { public: void funcB() { } A a; }; //类C的成员方法 需要类A的形参,C use A,类C 依赖于 类A class C { public: void funcC(A *a) { } void funcCC() { } }; //类D继承于类A, 类D如果是继承类A, 类D is 类A, 类C 继承于 类A,耦合度很高 class D:public A { public: void funcD() { cout << a << endl; } };
2、继承的基本概念
类的继承,是新的类从已有类那里得到已有的特性,或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
派生与继承,是同一种意义两种称谓。 is-A 的关系。
派生类的生成
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
几点说明:
1,全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
2,派生类有了自己的个性,使派生类有了意义。
只能在当前类中访问的成员设置为private
3、只能在当前类和子类中访问的成员设置为protected,protected成
员的访问权限介于public和private之间。
(1)语法
#include <iostream> #include <string> using namespace std; class Student { public: Student(int id,string name) { this->id = id; this->name = name; } void printS() { cout << "id=" << this->id << ",name=" << this->name << endl; } private: int id; string name; }; class Student2:public Student //类Student2继承类Student { public: Student2(int id, string name, int score):Student(id,name) { this->score = score; } void printS() { Student::printS(); cout << "score=" << this->score << endl; } private: int score; }; int main(void) { Student2 s(1, "zhang3", 80); s.printS(); return 0; }
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类, 称为单继承。
(2)protected 访问控制
protected 对于外界访问属性来说,等同于私有,但可以派生类中可见。
(3)派生类成员的标识和访问
pubilc公有继承
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中 不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无 论派生类的成员还是派生类的对象都无法访问基类的私有成员。
private私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保 护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都 会成为不可访问。因此私有继承比较少用。
protected保护继承
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类 中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。
private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。
如何恰当的使用public、protected和private为成员声明访问级别?
- 需要被外界访问的成员直接设置为public
- 只能在当前类中访问的成员设置为private
- 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
4、继承中的构造和析构
(1)类的赋值兼容原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。
类型兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
子类就是特殊的父类 (base *p = &child;)
(2)继承中的对象模型
类在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员得到的。
问题:如何初始化父类成员?父类和子类的构造函数有什么关系?
- 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化.
- 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理.
(3)继承中构造析构调用原则
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行结束后,执行子类的构造函数
- 当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
- 析构函数调用的先后顺序与构造函数相反
(4)继承和组合并存,构造和析构原则
- 先构造父类,再构造成员变量、最后构造自己
- 先析构自己,在析构成员变量、最后析构父类
(5)继承中同名成员变量处理方法
当子类成员变量与父类成员变量同名时,子类依然从父类继承同名成员,在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符),同名成员存储在内存中的不同位置。
(6)派生类中的static关键字
- 基类定义的静态成员,将被所有派生类共享;
- 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质(遵守派生类的访问控制);
- 派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问
对象名.成员
5、多继承
语法
一个类有多个直接基类的继承关系称为多继承。
6、虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
(1)多继承中二义性问题
(2)虚继承virtual
- 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性;
- 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象;
- 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
- 虚继承声明使用关键字 virtual