博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
继承与多态
阅读量:3941 次
发布时间:2019-05-24

本文共 5980 字,大约阅读时间需要 19 分钟。

继承

一个类是另一个类的一种(a kind of)

// 人class People{
public: void eat(string foot) {
cout << "eat:" << foot << endl; } void sleep() {
cout << "sleep" << endl; }private:};//学生class Student:public People{
public: void learn() {
cout << "learn" << endl; }private:};//对于Student来说,People 的每一个特征Student都要拥有,//不仅是Student 如果现在需要一个teacher类,对于People 的特征也要拥有,//这样我们可以将People定义为一个基类,所有拥有这些特征的都可以继承这个类,//这样就减少了不必要的代码重复int main(){
Student st; st.eat("红烧肉"); return 0;}

派生类中从基类中继承来的成员的访问限定

  • 派生类公有继承基类
基类 派生类 类外
public public 可访问
protected protected 不可访问
private 不可访问 不可访问
  • 派生类保护继承基类
基类 派生类 类外
public protected 不可访问
protected protected 不可访问
private 不可访问 不可访问
  • 派生类私有继承基类
基类 派生类 类外
public private 不可访问
protected private 不可访问
private 不可访问 不可访问

派生类怎么初始化从基类继承来的成员

class People{
public: People(int m):ma(m){
cout << "People()" << endl; } ~People(){
cout << " ~People()" << endl; } void eat(string foot) {
cout << "eat:" << foot << endl; } void sleep() {
cout << "sleep" << endl; }private: int ma;};class Student:public People{
public: //派生类必须通过调用基类的构造函数对基类的成员进行初始化 //如果基类不存在默认的构造函数,则在派生类的初始化列表中进行初始化 //如果基类存在默认的构造函数,如果不在初始化列表中初始化, //则默认调用基类的默认构造函数 Student(int m):People(m) {
cout << "Student()" << endl; } ~Student(){
cout << "~Student" << endl; } void learn() {
cout << "learn" << endl; }private:};int main(){
Student st(1); st.eat("红烧肉"); return 0;}

在这里插入图片描述

上图可以看出,先调用基类的构造函数,再调用派生类的构造函数,
析构的时候顺序相反。

基类对象能不能赋给派生类对象? 不可以
派生类对象能不能赋给基类对象? 可以
基类指针指向派生类对象? 可以
派生类指针指向基类对象? 不可以

派生类与基类的内存

class Base{
public: void Show() {
cout << "Base::Show()" << endl; } void Show(int a) {
cout << "Base::Show(int)" << endl; }private: int ma; int mb;};class Derived:public Base{
public: void Show() {
cout << "Derived::Show()" << endl; }private: int mc;};int main(){
cout << "Base size:" << sizeof(Base) << endl; cout << "Derived size:" << sizeof(Derived) << endl; return 0;}

输出结果

在这里插入图片描述
同一个类的所有对象都有自己的成员变量,所有对象共享一套成员方法。
所以sizeof()计算类的大小的时候,只计算成员变量的大小,在上面的例子中,派生类继承了基类的所有的成员变量域成员方法,所以计算派生类的大小的时候,要加上基类的大小。

重载、隐藏、覆盖

  1. 重载

如果两个函数的 函数名相同,参数列表不同,在同一个作用域中,则这两个函数叫重载函数

class Base{
public: //这两个函数为重载函数 void Show() {
cout << "Show()" << endl; } void Show(int a) {
cout << "Show(int)" << endl; }};

问题: 为什么c++ 中支持重载,c语言中不支持重载?重载实现的原理是什么?

因为在c语言中,编译阶段产生符号表的时候,每个函数产生一个符号,符号的产生是根据函数名来确定的,而c++ 中,符号的产生是根据函数名和参数列表共同确定的,所以,函数名相同,参数列表不同在编译阶段中产生的符号是不同的,在链接的过程中连接器根据符号表查找函数的的定义,不会产生冲突。

  1. 隐藏
    在基类和派生类中,函数名相同,叫做隐藏
class Base{
public: void Show() {
cout << "Base::Show()" << endl; } void Show(int a) {
cout << "Base::Show(int)" << endl; }};class Derived:public Base{
public: void Show() {
cout << "Derived::Show()" << endl; }};int main(){
Derived d; d.Show(); //访问派生类的Show() d.Show(10); //编译错误,派生类中没有函数参数的Show()函数 d.Base::Show(); //调用基类的Show() d.Base::Show(10); //调用基类的Show(int) return 0;}

因为派生类中Show() 将基类中的Show函数隐藏了,所以通过对象访问只能访问派生类的Show ,要访问基类的Show只能通过加上作用域。

  1. 覆盖
    基类与派生类中,函数名,函数的返回值与参数列表都相同,基类的函数的是虚函数
    (覆盖虚函数表)
    先来了解一下c++ 中的静态绑定域动态绑定
  • 静态类型:对象在声明时采用的类型,在编译期既已确定;
  • 动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
  • 静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
  • 动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
    我们来看一个例子
class Base{
public: void Show(){
cout << "Base::Show()" << endl;} virtual void Display(){
cout << "Base::Display()" << endl;}};class Derived:public Base{
public: void Show(){
cout << "Derived::Show()" << endl;} virtual void Display(){
cout << "Derived::Display()" << endl;}};int main(){
Derived *d = new Derived; Base *b = d; b->Show(); d->Show(); cout << "=====================" << endl; b->Display(); d->Display();}

在这里插入图片描述

虽然b和d都指向同一个对象。因为函数Show是一个no-virtual函数,它是静态绑定的,也就是编译器会在编译期根据对象的静态类型来选择函数。d的静态类型是Derived*,那么编译器在处理d->Sow()的时候会将它指向Derived::Show()。同理,b的静态类型是Base*,那b->Show()调用的就是Base::Show();

因为Display是一个虚函数,它动态绑定的,也就是说它绑定的是对象的动态类型,b和d虽然静态类型不同,但是他们同时指向一个对象,他们的动态类型是相同的,都是Derived*,所以,他们的调用的是同一个函数:Derived::Display()。

对于虚函数特别要注意的地方

虚函数+缺省参数
虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。

class Base{
public: virtual void Display(int a = 10) {
cout << "Base::Display() a = " << a <

在这里插入图片描述

可以看到因为缺省参数属于静态绑定,所以当b->Display() 默认的参数是10;
d->Display() 默认的参数是20 这是我们不愿意看到的,在使用虚函数的时候,一定要考虑号缺省参数的问题。

我们来看一下虚函数是怎样实现动态绑定的?

先来看一下这样一个例子

class Animal // 有纯虚函数的类  =》  抽象类{
public: Animal(string name) :_name(name) {
} virtual void bark() = 0;// 纯虚函数protected: string _name;};class Cat : public Animal{
public: Cat(string name):Animal(name){
} void bark() {
cout << _name << " bark:喵喵!" << endl; }};class Dog : public Animal{
public: Dog(string name) :Animal(name) {
} void bark() {
cout << _name << " bark:旺旺!" << endl; }};int main(){
Animal *p1 = new Cat("猫"); Animal *p2 = new Dog("二哈"); int *p11 = (int*)p1; int *p22 = (int*)p2; int tmp = p11[0]; p11[0] = p22[0]; p22[0] = tmp; //Cat 与 Dog 都继承了 Animal 类,而Animal 有一个虚函数bark 两个派生类中都重写了 //bark函数,两个派生类中的bark自动声明为虚函数 //按我们的理解输出的结果为: /* 猫 bark:喵喵 二哈 bark:旺旺 calse Cat */ p1->bark(); p2->bark(); cout << typeid(*p1).name() << endl; return 0;}

实际的输出结果

在这里插入图片描述
分析一下这段代码

Animal *p1 = new Cat("猫");	Animal *p2 = new Dog("二哈");	//p11 指向 p1 的首地址	int *p11 = (int*)p1;	//p22 指向 p2 的首地址	int *p22 = (int*)p2;	//交换了p11指向的内容与p22 指向内容的前四个字节	int tmp = p11[0];	p11[0] = p22[0];	p22[0] = tmp;

对象的前四个字节中存储了什么?交换之后对象的方法都交换了,而且对象的类型都变了 ?

每个类的虚函数都有一个对应的虚函数表,该表存储在只读数据段,即.rodata段,在每个含有虚函数类中就多了一个数据成员,vfptr 指向了虚函数表。通过vfptr 可以动态访问类中的虚函数

class Base{
public: virtual void Display(int a = 10) {
cout << "Base::Display() a = " << a <

在这里插入图片描述

可以看到类中不止有int ma,这就验证了上面我们说的。

虚函数表的内容

在这里插入图片描述
其中 RTTI (Run-Time Type Identification) ,RTTI 指向了.rodata 段的一个常量字符换,该常量字符串表示类型,例如上面的Base 类的虚函数表中的RTTI指向的就是"Base" 字符串,偏移量表示虚函数在对象中的位置,一般都是0 ,即在对象的起始位置。这样对于这段代码就好理解了

Animal *p1 = new Cat("猫");	Animal *p2 = new Dog("二哈");	//p11 指向 p1 的首地址	int *p11 = (int*)p1;	//p22 指向 p2 的首地址	int *p22 = (int*)p2;	//交换了p11指向的内容与p22 指向内容的前四个字节	//也就是交换了两个对象的虚函数表的地址,	int tmp = p11[0];	p11[0] = p22[0];	p22[0] = tmp;		//p1此时指向存储的是p2的虚函数表的地址,所以调用p2的bark	p1->bark();	//p2指向的p1 的虚函数表的地址,所以调用的p1 的bark	p2->bark();	//p1 此时的RTTI是 原来p2的类型。	cout << typeid(*p1).name() << endl;

转载地址:http://txnwi.baihongyu.com/

你可能感兴趣的文章
印刷工艺流程
查看>>
印刷业ERP启蒙
查看>>
Java8 Lambda表达式使用集合(笔记)
查看>>
Java魔法师Unsafe
查看>>
spring cloud java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest
查看>>
Centos系统安装MySQL(整理)
查看>>
postgresql计算两点距离(经纬度地理位置)
查看>>
postgres多边形存储--解决 Points of LinearRing do not form a closed linestring
查看>>
postgresql+postgis空间数据库总结
查看>>
spring 之 Http Cache 和 Etag(转)
查看>>
基于Lucene查询原理分析Elasticsearch的性能(转)
查看>>
HttpClient请求外部服务器NoHttpResponseException
查看>>
springCloud升级到Finchley.RELEASE,SpringBoot升级到2.0.4
查看>>
Spring boot + Arthas
查看>>
omitted for duplicate jar包冲突排查
查看>>
如何保证缓存与数据库的双写一致性?
查看>>
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy排查
查看>>
深浅拷贝,深浅克隆clone
查看>>
Java基础零散技术(笔记)
查看>>
Mysql优化sql排查EXPLAIN EXTENDED
查看>>