C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的的类来创建新类,而不是重新创建它们。
关于“继承”是什么,只要是有过面向对象的编程基础的应该都很容易理解,接下来会详细对其进行介绍,而上面提到了一个“组合”,那它的表现形式是咋样的呢?
定义一个A类:
此时有一个B类,它的功能跟A类的很相近,没必要重头到尾编写这些功能,所以可以去调用A类的方法来实现重用,如下:
实际上组合也就是将一个类作为另一类的对象成员,来达到复用代码的目的。另外一种复用代码的方式则就是继承了,下面来了解C++的继承:
- 继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。
- 新类称为派生类或子类,原有类称为基类或父类。
- 派生类是基类的具体化。
- 派生类的声明语法为:
class 派生类名 : 继承方式 基类名
{
派生类新增成员的声明;
};
下面用代码来具体使用一下:
#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; } private: int a; }; int main(void) { return 0; }
以上代码没有什么意义,纯是学习语法,编译一下:
报错了,继承类无法使用基类的私有成员,关于这个修饰符跟java的类似,很容易理解。对于protected、public的区别,也差不多,前者只能在派生类中访问,而后者则可以在类的外部进行访问,如下:
#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; }; int main(void) { Base b; b.x_ = 20;//在类外部访问public成员 return 0; }
这时可以正常编译的,但是如果访问protected成员,则会报错:
编译:
对于这些修饰符有了初步的认识之后,下面来详细介绍一下它们:
- 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
- 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
- 在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同。
对于我们写的例子用的是公有继承:
跟java不一样的,它还有其它方式进行继承,下面用一张表来总结一下继承情况:
下面来使用一下私有继承的情况:
#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 PrivateInherit : private Base {//声明派车类,私有继承基类 public: void Test(){ x_ = 10; y_ = 20; } private: int a; }; int main(void) { return 0; }
先看下私有继承,能否对public和protect成员进行访问:
正常编译,那如果以对象的形式能否访问呢?
那如果以对象的形式去访问呢,又会如何?
#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 PrivateInherit : private Base {//声明派车类,私有继承基类 public: void Test(){ x_ = 10; y_ = 20; //z_ = 30; ERROR,派生类中无法访问基类的私有成员 } private: int a; }; int main(void) { PrivateInherit pi; pi.x_ = 10; return 0; }
编译:
经过私有继承之后,基类的公有成员也变为私有的了,无法在外部进行访问。
对于保护继承就不测试了,其结果可以从表中知道。
- class Base {};
- struct D1 : Base {}; // 对于结构体而言,默认就是公有继承
- class D2 : Base {}; // 对于类而言,默认就是私有继承
- 我们将类的公有成员函数称为接口。
- 公有继承,基类的公有成员函数在派生类中仍然是公有的,换句话说是基类的接口成为了派生类的接口,因而将它称为接口继承。
- 实现继承,对于私有、保护继承,派生类不继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。
- 对基类的数据成员的重定义。
这时重定义一下x_:
#include <iostream> using namespace std; class Base {//声明基类 public: Base() : x_(0) { } int getBaseX() const { return x_; } int x_; }; class Derived : public Base { public: Derived() : x_(0) { } int getDerivedX() const { return x_; } int x_;//重定义了x_ }; int main(void) { Derived d; d.x_ = 10; cout<<d.getBaseX()<<endl; cout<<d.getDerivedX()<<endl; return 0; }
这时打印一下看改变的是:
当然是自身重定义的x_喽。
- 对基类成员函数的重定义分为两种
①、overwrite【注意:它不是重载(overload),重载是作用域相同才可以,也就是在同一个类中发生的】
a、与基类完全相同
b、与基类成员函数名相同,参数不同
#include <iostream> using namespace std; class Base {//声明基类 public: Base() : x_(0) { } int getBaseX() const { return x_; } void show() { cout<<"Base::show ..."<<endl; } int x_; }; class Derived : public Base { public: Derived() : x_(0) { } int getDerivedX() const { return x_; } int x_;//重定义了x_ }; int main(void) { Derived d; d.x_ = 10; cout<<d.getBaseX()<<endl; cout<<d.getDerivedX()<<endl; d.show();//派生类没有重定义show函数,所以调用的肯定是基类的show了 return 0; }
编译运行:
接下来派生类重写一下show函数:#include <iostream> using namespace std; class Base {//声明基类 public: Base() : x_(0) { } int getBaseX() const { return x_; } void show() { cout<<"Base::show ..."<<endl; } int x_; }; class Derived : public Base { public: Derived() : x_(0) { } int getDerivedX() const { return x_; } void show(int n) { cout<<"Derived::show ..."<<n<<endl; } int x_;//重定义了x_ }; int main(void) { Derived d; d.x_ = 10; cout<<d.getBaseX()<<endl; cout<<d.getDerivedX()<<endl; d.show(); return 0; }
编译:
这说明基类的无参的show函数被隐藏了,这是带参数的重写,另外也有不带参数的重写,如下:
编译运行:
那如果想访问基类的show()方法,可以这样做:
编译运行:
同样的,如果想访问被重写的父类成员也一样:
②、override【覆盖,需要虚函数才可以,虚函数之后再学习,先了解一下】
- 无论是继承与组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
也就是说明继承与组合的内存模型是一样的,下面用代码来说明下:
#include <iostream> using namespace std; class Base {//声明基类 public: Base() : x_(0) { } int getBaseX() const { return x_; } void show() { cout<<"Base::show ..."<<endl; } int x_; }; class Derived : public Base { public: Derived() : x_(0) { } int getDerivedX() const { return x_; } void show(int n) { cout<<"Derived::show ..."<<n<<endl; } void show() { cout<<"Derived::show ..."<<endl; } int x_;//重定义了x_ }; int main(void) { Derived d; d.x_ = 10; d.Base::x_ = 20; cout<<d.getBaseX()<<endl; cout<<d.getDerivedX()<<endl; d.show(); d.Base::show(); cout<<sizeof(Derived)<<endl;//打印一下派生类的大小,实际上包含两个int类型,一个是基类的,一个是自身的 return 0; }
#include <iostream> using namespace std; class Base {//声明基类 public: Base() : x_(0) { } int getBaseX() const { return x_; } void show() { cout<<"Base::show ..."<<endl; } int x_; }; class Derived : public Base { public: Derived() : x_(0) { } int getDerivedX() const { return x_; } void show(int n) { cout<<"Derived::show ..."<<n<<endl; } void show() { cout<<"Derived::show ..."<<endl; } int x_;//重定义了x_ }; class Test { public: Base b_;//组合关系 int x_; }; int main(void) { Derived d; d.x_ = 10; d.Base::x_ = 20; cout<<d.getBaseX()<<endl; cout<<d.getDerivedX()<<endl; d.show(); d.Base::show(); cout<<sizeof(Derived)<<endl; cout<<sizeof(Test)<<endl; return 0; }
- 组合通中是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
举个现实中的例子来理解:
一部汽车(它有行驶的功能)有引擎(它有启动、停止、加速、减速的功能)和轮胎(它有滚动的功能),而汽车是利用引擎和轮胎来实现实驶的功能,所以汽车与引擎和轮胎是组合关系。 - 如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a)【实际上它是LSP(Liskov Substitution Principle里氏代换原则),用它可以检验继承的质量,这个在之后会学习到,了解一下~】