面向对象编程基于的三个基本特征:封装,继承,多态。
一、代码重用
C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的的类来创建新类,而不是重新创建它们。
1、组合方式实现重用:将一个类作为另一个类的对象成员,委托
class A { public: void FunA() { ... } } class B { public: void FunB() { ... a_.FunA(); ... } private: A a_; }
二、继承
继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。
新类称为派生类或子类,原有类称为基类或父类。
派生类是基类的具体化
人类是基类,教师是派生类
在C++语言中,一个派生类可以从一个基类派生,称为单继承;也可以从多个基类派生,称为多继承。
1、单继承的定义格式如下:
class <派生类名> : <继承方式> <基类名> { <派生类新定义成员> }
2、示例
#include <iostream> using namespace std; class Base { public: int x_; protected: int y_; private: int z_; }; class PublicInherit : public Base { public: void Test() { x_ = 10; y_ = 20; //z_ = 30; error } private: int a_; }; class PublicPublicInherit : public PublicInherit { public: void Test() { y_ = 20; } }; class PrivateInherit : private Base { public: void Test() { x_ = 10; y_ = 20; //z_ = 30; error } }; int main(void) { Base b; b.x = 20; //b.y = 11; //error,不允许在外部访问保护成员 PublicInherit pub; pub.x_ = 20; PrivateInherit pri; //pri.x_ = 10; error return 0; }
三、公有、私有、保护继承
1、<继承方式>常用三种关键字给予表示:
public: 表示公有基类;
private: 表示私有基类;
protected: 表示保护基类;
2、公有/私有/保护成员
在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同。
——可见,不论哪种方式,基类中的私有成员都不允许派生类继承,即在派生类中不可见。
——派生类对基类成员的访问形式主要有以下两种:
(1)内部访问(派生类成员):由派生类中新增的成员函数对基类继承来的成员的访问
(2)对象访问(派生类对象):在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
3、公有继承
对于公有继承方式:
(1)父类的public成员成为子类的public成员,可以被该子类中的函数(内部访问)及其友元函数访问,除此之外,也可以由该子类的对象(外部访问)访问。
(2)父类的private成员仍旧时父类的private成员,子类成员不可以访问这些成员,包括子类中的函数及其友元函数、子类对象。
(3)父类的protected成员称为子类的protected成员,可以被该子类中的函数及其友元函数访问,除此之外,不可以由该子类的对象访问。
所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对象和派生类中的成员函数对基类的访问是不同的。
下表总结了公有继承的访问规则:
【例1】
类child从类parent公有派生,则类child可以访问类parent中的()成员?(多选)
A、public B、private C、protected D、数据成员
解答:AC。
【例2】
类child从类parent公有派生,若有"child c;",那么c可以访问parent中()成员?
A、public B、private C、protected D、都不能访问
解答:A。
4、私有继承
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。
【例1】
类child从类parent私有派生,则类child可以访问类parent中的()成员?(多选)
A、public B、private C、protected D、数据成员
解答:AC。
【例2】
类child从类parent私有派生,若有"child c;",那么c可以访问parent中()成员?
A、public B、private C、protected D、都不能访问
解答:D。
5、保护继承
保护继承的特点是基类的所有公有成员和保护成员都成员派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
【例1】
类child从类parent保护派生,则类child可以访问类parent中的()成员?(多选)
A、public B、private C、protected D、数据成员
解答:AC。
【例2】
类child从类parent保护派生,若有"child c;",那么c可以访问parent中()成员?
A、public B、private C、protected D、都不能访问
解答:D。
四、默认继承保护级别
class Base {};
struct D1 : Base {}; //公有继承
class D2 : Base {}; //私有继承
五、接口继承与实现继承
我们将类的公有成员函数称为接口。
公有继承,基类的公有成员函数在派生类中仍然是公有的,换句话说是基类的接口成为了派生类的接口,因而将它称为接口继承。
实现继承,对于私有、保护继承,派生类不继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。
六、继承与重定义
1、对基类的数据成员的重定义
2、对基类成员函数的重定义分为两种
在《操作符重载与转换(3)》中已经讲过重载与覆盖的区别:覆盖是子类和父类之间的关系,是垂直关系;重载时同一个类中不同方法之间的关系,是水平关系;
(1)overwrite(隐藏)
【1】与基类完全相同
【2】与基类成员函数名相同,参数不同
(2)override(覆盖)
要求虚函数,后续再讲
#include <iostream> using namespace std; class Base { public: Base() : x_(0), y_(48) { } int GetBaseX() const { return x_; } int GetBaseY() const { return y_; } void Show() { cout << "Base::Show ..." << endl; } int x_; private: int y_; //继承后无法被直接访问,可通过GetBaseY访问 }; class Derived : public Base { public: Derived() : x_(0) { } int GetDerivedX() const { return x_; } void Show(int n) //对基类成员函数的重定义,与下面的show 构成重载,基类的show被隐藏 { cout << "Derived::Show " << n << endl; } void Show() //如果没定义,d.Show()将会报错 { cout << "Derived::Show ..." << endl; } int x_; //对基类的数据成员的重定义,重定义x_,基类的x_被隐藏 }; //组合关系 class Test { public: Base b_; int x_; }; int main(void) { Derived d; d.x_ = 10; d.Base::x_ = 20; //访问被隐藏的基类x_; cout << d.GetBaseX() << endl; cout << d.GetDerivedX() << endl; cout << d.GetBaseY() << endl; d.Show(); //如果没定义Derived的Show()函数将会报错 d.Base::Show();//访问被隐藏的基类show cout << sizeof(Derived) << endl; cout << sizeof(Test) << endl; return 0; }
1、无论是继承与组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
2、组合通常是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
3、如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a),能够使用基类的地方,就能够使用新类替换,这就是里氏代换原则LSP,用于检验继承的质量。
八、下面总结一下overload/overwrite/override 之间的区别:
参考:
C++ primer 第四版
C++ primer 第五版