C++ 继承
•什么是继承
当创建一个类时,我们不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。
这个已有的类称为 基类,也叫父类;新建的类称为派生类,也叫子类。
继承表示的是 is a 关系。
例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。
•为什么要用继承
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易;这样做,也达到了重用代码功能和提高执行效率的效果。
让我们来模拟一个场景,对于同一个博主的不同博文,如下图所示:
我们把这篇博文划分为三个部分,分别为 左侧菜单栏 , 中间内容蓝 , 右侧打赏栏;
其中,左侧菜单栏和右侧打赏栏是不会因为博文的不同而改变的,不同的只有中间的内容栏。
假设该博主攥写了三篇博文,分别是《Java学习记录》、《C++学习记录》、《Android学习记录》,让我们用一般的方法来编写一个代码,实现这三篇博问的这三块内容。
class Java { public: void left() { cout << "左侧菜单栏" << endl; cout << " 个人信息" << endl; cout << " 首页" << endl; cout << " 联系" << endl; cout << " 等等" << endl; } void mid() { cout << "中间内容栏" << endl; cout << " Java相关博文" << endl; } void right() { cout << "右侧打赏栏" << endl; cout << " 微信支付" << endl; cout << " 支付宝支付" << endl; cout << " 等等" << endl; } }; class CPP { public: void left() { cout << "左侧菜单栏" << endl; cout << " 个人信息" << endl; cout << " 首页" << endl; cout << " 联系" << endl; cout << " 等等" << endl; } void mid() { cout << "中间内容栏" << endl; cout << " C++相关博文" << endl; } void right() { cout << "右侧打赏栏" << endl; cout << " 微信支付" << endl; cout << " 支付宝支付" << endl; cout << " 等等" << endl; } }; class Android { public: void left() { cout << "左侧菜单栏" << endl; cout << " 个人信息" << endl; cout << " 首页" << endl; cout << " 联系" << endl; cout << " 等等" << endl; } void mid() { cout << "中间内容栏" << endl; cout << " Android相关博文" << endl; } void right() { cout << "右侧打赏栏" << endl; cout << " 微信支付" << endl; cout << " 支付宝支付" << endl; cout << " 等等" << endl; } }; int main() { cout << "------------------------------" << endl; Java java; java.left(); java.mid(); java.right(); cout << "------------------------------" << endl; CPP cpp; cpp.left(); cpp.mid(); cpp.right(); cout << "------------------------------" << endl; Android android; android.left(); android.mid(); android.right(); }有没有很臃肿的感觉?
代码大量重写,虽然 ctrl+c , ctrl+v 挺爽的,但,这会不会显得太 low 了呢?
•怎么用
下面,我们就看看如何通过继承优雅的实现上述功能。
继承的格式
class Derive: 【访问修饰符】 Base访问修饰符类型:public、protected 或 private 其中的一个;如果未使用访问修饰符 ,则默认为 private。
Base 是之前定义过的某个类的类名,称为Derive类 的基类(或父类)。
有了继承的知识支持,让我们看看如何通过继承优雅的实现上述代码的功能。
首先,创建一个Blog类作为基类:
class Blog { public: void left() { cout << "左侧菜单栏" << endl; cout << " 个人信息" << endl; cout << " 首页" << endl; cout << " 联系" << endl; cout << " 等等" << endl; } void mid() { cout << "中间内容栏" << endl; cout << " 相关博文" << endl; } void right() { cout << "右侧打赏栏" << endl; cout << " 微信支付" << endl; cout << " 支付宝支付" << endl; cout << " 等等" << endl; } };接着,让Java类、CPP类和Android类作为 Blog类的派生类,并重写父类的 mid() 方法,让他们各有特点。
class Java :public Blog { public: void mid() { cout << "中间内容栏" << endl; cout << " Java相关博文" << endl; } }; class CPP :public Blog { public: void mid() { cout << "中间内容栏" << endl; cout << " C++相关博文" << endl; } }; class Android :public Blog { public: void mid() { cout << "中间内容栏" << endl; cout << " Android相关博文" << endl; } };由于这三个类都是Blog类的派生类,所以Blog类中的成员函数 left() , mid() , right() 函数都会自动定义;
并且这三个类通过重写 mid() 类实现了各自的功能;这么一改,是不是简介了许多??
•访问修饰符
公有继承(public)
当一个类派生自公有基类时:
- 基类的公有成员也是派生类的公有成员
- 基类的保护成员也是派生类的保护成员
- 基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
class Base { public: string pubS = "Base类的公有成员"; protected: string proS = "Base类的保护成员"; private: string priS = "Base类的私有成员"; }; class Derive :public Base { public: void func() { cout << pubS << endl; //基类的公有成员也是派生类的公有成员 cout << proS << endl;//基类的保护成员也是派生类的保护成员 //cout << priS << endl;//基类的私有成员不能直接被派生类访问 } }; int main() { Derive derive; cout << derive.pubS << endl;//验证pubS是Derive的公有成员,类外可访问 //cout << derive.proS << endl;//验证proS是Derive的保护成员,类内可访问但类外不可访问 derive.func(); }保护继承(protected)
当一个类派生自保护基类时:
- 基类的公有和保护成员将会成为派生类的保护成员。
class Base { public: string pubS = "Base类的公有成员"; protected: string proS = "Base类的保护成员"; private: string priS = "Base类的私有成员"; }; class Derive :protected Base { public: void func() { cout << pubS << endl; //基类的公有成员变成派生类的保护成员 cout << proS << endl;//基类的保护成员也是派生类的保护成员 //cout << priS << endl;//基类的私有成员不能直接被派生类访问 } }; int main() { Derive derive; //cout << derive.pubS << endl;//验证pubS是Derive的保护成员,类外可访问 //cout << derive.proS << endl;//验证proS是Derive的保护成员,类内可访问但类外不可访问 derive.func(); }私有继承(private)
当一个类派生自私有基类时:
- 基类的公有和保护成员将成为派生类的私有成员。
class Base { public: string pubS = "Base类的公有成员"; protected: string proS = "Base类的保护成员"; private: string priS = "Base类的私有成员"; }; class Derive :private Base { public: void func() { cout << pubS << endl; //基类的公有成员变成派生类的保护成员 cout << proS << endl;//基类的保护成员也是派生类的保护成员 //cout << priS << endl;//基类的私有成员不能直接被派生类访问 } }; class Test :public Derive { public: void func() { //cout << pubS << endl; //报错,通过派生类不可访问基类的私有成员 验证pubS是Derive类的私有成员 //cout << proS << endl;//报错,通过派生类不可访问基类的私有成员 验证proS是Derive类的私有成员 } };图示
总之,不论什么类型的继承,基类的私有成员一律不可访问;
•不被子类继承的函数
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
因此,我们不能够在子类的成员函数体中调用基类的构造函数来为成员变量进行初始化,如下所示:
class Base { public: int a, b; Base(int a, int b) { this->a = a; this->b = b; } }; class Derive :public Base { public: Derive(int a, int b) { Base(a, b);//报错,不能在派生类的函数内调用基类的构造函数 } };但我们可以把基类的构造函数放在子类构造函数的初始化列表上,以此实现调用基类的构造函数来为子类从基类继承的成员变量初始化。
class Base { public: int a, b; Base(int a, int b) { this->a = a; this->b = b; } }; class Derive :public Base { public: Derive(int a, int b):Base(a, b) { } };
•继承中同名成员处理方式
访问子类同名成员,直接访问即可;
访问父类同名成员,需要加作用域。
访问父类同名成员
class Base { public: string s = "Base - string"; }; class Derive :public Base { public: string s = "Derive - string"; }; void test() { Derive derive; cout << derive.s << endl;//不加任何修饰,则访问Derive中的s cout << derive.Base::s << endl;//访问父类同名成员,需要添加作用域 Base:: }举一反三
class Base { public: void f() { cout << "Base - f()" << endl; } }; class Derive :public Base { public: void f() { cout << "Derive - f()" << endl; } }; void test() { Derive derive; derive.f();//不加任何修饰,访问Derive中的f() derive.Base::f();//访问父类同名成员函数,需要添加作用域 Base:: }通过作用域 Base:: 可访问到父类的同名成员函数;
若给父类的 f() 做个函数重载 void f(int a) ,然后直接调用 derive.f(100) 是不是就直接调用父类的 void f(int a) ?
直接调用父类同名成员函数的重载?
class Base { public: void f() { cout << "Base - f()" << endl; } void f(int a) { cout << "Base - f(int a)" << endl; } }; class Derive :public Base { public: void f() { cout << "Derive - f()" << endl; } }; void test() { Derive derive; derive.f(100); }很遗憾,编译都不通过......
这是为什么呢??
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数,所以,不管是否重载,在derive中都一律不能直接访问。
正确访问方式: derive.Base::f(100);
访问父类同名静态成员
class Base { public: static string s; }; string Base::s = "Base - static string";//静态成员类内声明,类外初始化 class Derive :public Base { public: static string s; }; string Derive::s = "Derive - static string";//静态成员类内声明,类外初始化 void test() { Derive derive; //通过对象访问静态成员 cout << derive.s << endl;//访问Derive中的s cout << derive.Base::s << endl;//访问Base下的s //通过类名访问静态成员 cout << Derive::s << endl; //第一个 :: 代表通过类名方式访问 , 第二个 :: 代表访问父类作用域下的 s cout << Derive::Base::s << endl; }访问父类的同名静态成员函数同理。
•参考资料
【1】:C++ 继承 | 菜鸟教程
【2】:C++ 继承 | 传智播客