草庐IT

六、C++的继承与多态——深入掌握OOP最强大的机制

woden3702 2023-03-28 原文

这一部分内容可以直接看《C++ primer》第十五章,这里讲的基本上都是重复的。第十五章的最后一个小节还有一个综合性的代码案例,包含操作符重载、继承、多态等等。第十五章的笔记可以看我的另一篇随笔第十五章 面向对象程序设计

继承的基本意义

继承的本质(好处):

  1. 代码的复用;

  2. 在基类中给所有派生类提供统一的虚函数接口,让派生类进行重写,然后就能使用多态了。

类和类之间的关系:

  • 组合 一部分的关系
  • 继承 一种的关系

总结:1.外部只能访问对象public的成员,protected和private成员无法直接访问;2、在集成结构中,派生类从基类可以继承过来private的成员,但是派生类缺无法直接访问;3、protected和private的区别?在基类中定义的成员,想被派生类访问,但是不想被外部访问,那么就在基类中把相关成员定义为protected的;如果派生类和外部都不打算访问,那么在基类中就把相关成员定义为private的。

默认的继承方式是什么?要看派生类使用class定义的还是struct定义的。如果是class定义的派生类,默认继承方式是private的;如果是struct定义的派生类,默认继承方式是public的。

派生类的构造过程

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

  • 派生类从基类可以继承来所有的成员(变量和方法),但是不包含构造函数和析构函数
  • 通过调用基类相应的构造函数来初始化

重载、覆盖、隐藏

重载关系:

  • 一组函数要重载必须处在同一个作用域当中,且函数名字相同,参数列表不同

​ 在基类和派生类中的函数不能被重载,因为作用域不同。如果在基类中定义了重载函数,在派生类中可以直接调用,但是如果派生类中定义了与基类同名的函数,在调用派生类的时候只会产生派生类中的重载,不会与基类中的同名函数发生重载

隐藏关系:

  • 在继承结构中,派生类的同名成员会把基类的同名成员隐藏起来,

基类和派生类的 类型转化

只能访问蓝色部分的内存,后面红色部分的内存是不存在的,访问基类指针会报内存非法访问

在继承结构中,进行上下的类型转换,默认只支持从下到上的类型转换。

覆盖关系:

基类和派生类的方法、返回值、函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,他们之间称为覆盖关系。

虚函数、静态绑定和动态绑定

虚函数virtual。RTTI(run-time type information)运行时的类型信息。

总结1.如果类里面定义了虚函数,编译阶段编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。

总结2.一个类里定义了虚函数,那么这个类定义的对象运行时,内存中的开始部分会多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个的类型定义的n个对象,他们的vfptr指向的都是同一张虚函数表。


在这个例子里对象的大小是8个字节

总结3.一个类里面虚函数的个数不影响对象内存大小(Vfptr),影响的是虚函数表的大小

class Base{
public:
	Base(int data=10): ma(data){}
    //虚函数 virtual
	virtual void show(){ cout<<"Base::show()"<<endl;}
	virtual void show(int)(cout<<"Base::show(int)"<<endl;)
protected:
	int ma;
};

总结4.如果派生类中的方法和基类继承来的某个方法,返回值、函数名、参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法会自动处理成虚函数。


派生类中的虚函数表,如果派生类中有与基类中重复的函数,会在派生类的虚函数表中覆盖原来基类中的方法地址。

类中存在虚函数就会发生动态绑定。

静态绑定在汇编代码中会call具体的方法地址,而动态绑定call一个寄存器,寄存器会在运行时找到虚函数表中相应的方法的地址。

虚析构函数

问题1:那些函数不能实现成虚函数?

  • 虚函数依赖:

    1. 虚函数能产生地址,存储在vftable中
    2. 对象必须存在(vfptr->vftable->虚函数地址)对象存在才有vfptr,才能找到vftable,才有虚函数地址
  • 构造函数不能成为虚函数。构造函数中调用虚函数,不会发生动态绑定。构造函数调用的任何函数都是静态绑定的

  • 静态成员方法也不能实现成虚函数

问题2:虚析构函数

  • 析构函数调用的时候对象是存在的。
  • 基类的析构函数是虚析构函数,派生类的析构函数会自动定义为虚析构函数

什么时候必须把基类的析构函数必须实现成虚函数?

​ 基类的指针(引用)指向堆上new出来的派生类对象的时候,delete pb(基类的指针),他调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用

为什么要使用虚析构函数?

​ 解释这个问题使用的代码:

//
// Created by 26685 on 2022-05-17 19:41.
// Description:ClassDerive.h  学习继承和多态
//

#ifndef C___CLASSDERIVE_H
#define C___CLASSDERIVE_H
#include <iostream>
using namespace std;

class Base{
public:
    Base(int data=10):ma(data){
        cout<<"Base()"<<endl;
    }
    ~Base(){
        cout<<"~Base()"<<endl;
    }

    virtual void show(){
        cout<<"Base::show()"<<endl;
    }
private:
    int ma;
};

class Derive:public Base{
public:
    Derive(int data=10): Base(data),mb(data),ptr(new int(data)){
        cout<<"Derive()"<<endl;
    }
    ~Derive(){
        cout<<"~Derive()"<<endl;
    }

    void show() override{
        cout<<"Derive::show()"<<endl;
    }
private:
    int mb;
    int* ptr;//会指向额外的空间,必须由析构函数释放
};

#endif //C___CLASSDERIVE_H

如果在堆上开辟内存存放派生类会发生不调用派生类的析构函数的情况,代码如下:

int main(){
    Base *pb=new Derive;
    pb->show();
    delete pb;
    return 0;
}

输出的结果只有基类的析构函数:

这里的pb是Base类型,会去Base类中找析构函数,此时的绑定就是静态绑定。

基类的析构函数如果是虚函数,派生类中的析构函数会自动生成为虚析构函数。所以要将析构函数定义为虚析构函数。

virtual Base::~Base(){
        cout<<"~Base()"<<endl;
}

Derive::~Derive(){
        cout<<"~Derive()"<<endl;
        delete ptr;
    }

此时就会正确的析构:

再谈虚函数和动态绑定

问题:是不是虚函数的调用一定就是动态绑定? 答:不是的

在类的构造函数当中调用虚函数是静态绑定。

用对象本身调用虚函数是静态绑定

动态绑定发生在指针调用虚函数的情况下:

理解多态是什么

面试中常见的问题:如何解释多态

动态的多态:

静态的多态:

函数的重载

理解抽象类

拥有纯虚函数的类叫做抽象类,抽象类不能再实例化对象,但是可以定义指针和引用变量

virtual void 函数名()=0;定义纯虚函数

一般把什么类设计为抽象类?

基类

有关六、C++的继承与多态——深入掌握OOP最强大的机制的更多相关文章

  1. ruby-on-rails - Rails 单表继承 : How to override the value written to the type field - 2

    在我的系统中,我已经定义了STI。Dog继承自Animal,在animals表中有一个type列,其值为"Dog"。现在我想让SpecialDog继承自dog,只是为了在某些特殊情况下稍微修改一下行为。数据还是一样。我需要通过SpecialDog运行的所有查询,以返回数据库中类型为Dog的值。我的问题是因为我有一个type列,rails将WHERE"animals"."type"IN('SpecialDog')附加到我的查询中,所以我不能获取原始的Dog条目。所以我想要的是以某种方式覆盖rails在通过SpecialDog访问数据库时使用的值,使其表现得像Dog。有没有办法覆盖用于类型

  2. ruby-on-rails - rails 多态关联(遗留数据库) - 2

    我使用的是遗留数据库,所以我无法控制数据模型。他们使用了很多多态链接/连接表,就像这样createtableperson(per_ident,name,...)createtableperson_links(per_ident,obj_name,obj_r_ident)createtablereport(rep_ident,name,...)其中obj_name是表名,obj_r_ident是标识符。因此链接的报告将按如下方式插入:insertintoperson(1,...)insertintoreport(1,...)insertintoreport(2,...)insertint

  3. ruby-on-rails - 为什么 DataMapper 使用混合与继承? - 2

    所以我只是对此感到好奇:DataMapper为其模型使用混合classPostincludeDataMapper::Resource虽然active-record使用继承classPost有谁知道为什么DataMapper选择这样做(或者为什么AR选择不这样做)? 最佳答案 它允许您从另一个不是DM类的类继承。它还允许动态地将DM功能添加到类中。这是我正在处理的模块中的类方法:defdatamapper_classklass=self.dupklass.send(:include,DataMapper::Resource)klass

  4. ruby-on-rails - Ruby on Rails 单表继承(STI)和单元测试问题(使用 PostgreSQL) - 2

    我正在使用带有单个“帐户”表的STI模型来保存用户和技术人员的信息(即用户...8)错误:test_the_truth(用户测试):ActiveRecord::StatementInvalid:PGError:ERROR:关系“技术人员”不存在:从“技术人员”中删除...从本质上讲,标准框架不承认Technicians和Users表(或PostgreSQL称它们为“关系”)不存在,事实上,应该别名为Accounts。有什么想法吗?我对RoR比较陌生,不知道如何解决这个问题而又不完全删除STI。 最佳答案 原来问题是由于存在:./te

  5. ruby - 为什么 Ruby 模块继承不像类继承那样工作? - 2

    假设我有一个名为Flight的模块,其中包含类方法和实例方法。我可以使用include、extend或两者将其方法放入类中:classBatinclude会将Flight添加到Bat.ancestors,但extend不会。我的问题是,为什么模块与类不同?当我对Mammal进行子类化时,我总是同时获得类和实例方法。然而,当我混入一个模块时,我不能同时获得类和实例方法(除非我使用self.included钩子(Hook)或类似ActiveSupport::Concern的东西)。这种差异背后是否存在语言设计问题? 最佳答案 Modul

  6. ruby - 为什么 Object 在 Ruby 中既包含内核又继承它? - 2

    在Ruby(1.8.X)中为什么Object既继承了内核又包含了内核?仅仅继承还不够吗?irb(main):006:0>Object.ancestors=>[Object,Kernel]irb(main):005:0>Object.included_modules=>[Kernel]irb(main):011:0>Object.superclass=>nil请注意,在Ruby1.9中情况类似(但更简洁):irb(main):001:0>Object.ancestors=>[Object,Kernel,BasicObject]irb(main):002:0>Object.included

  7. Ruby 获取继承类 - 2

    我正在为Rails创建我的第一个插件。我对ruby​​还是很陌生,我想知道是否有可能获得继承类?例如,我正在尝试创建一个插件,在您不使用迁移时允许进行单元测试和功能测试。我要做的是初始化一个名为controller的类变量,以初始化为正在测试的Controller类型。如果我有一个基类ControllerTest:classControllerTest所以我目前坚持的是获取继承类的名称。这可能吗?如果没有,有没有人知道我可以如何着手实现它的另一种方式?提前致谢。 最佳答案 非常简单:使用“继承”回调。来自Class类的RDoc:in

  8. ruby - ActiveRecord,通过多态属性查找 - 2

    有这个:classEventtrueenduser=User.create!我可以:Event.create!(:historizable=>user)但我不能:Event.where(:historizable=>user)#Mysql2::Error:Unknowncolumn'events.historizable'in'whereclause'我必须改为这样做:Event.where(:historizable_id=>user.id,:historizable_type=>user.class.name)更新重现问题的代码:https://gist.github.com/fg

  9. ruby-on-rails - 是吗? Rails 3 中的单表继承失败 - 2

    Object#is_a?在Rails3中以最奇怪的方式失败。我将单表继承设置如下(为简洁起见进行了简化):#resource.rbclassResource在我的Controller中,我有这个:defcreate@resource=Resource.findparams[:resource_id]logger.info'@resourceclass:'+@resource.class.namelogger.info'@resourcesuperclass:'+@resource.class.superclass.namelogger.info'@resourceis_a?(Video

  10. ruby-on-rails - Rspec Controller 在 Rails 中测试继承自 AbstractController::Base 的 Controller - 2

    我正在为我未构建的应用程序编写Controller测试,因此这绝对是一个学习过程。这是我第一次遇到直接继承自AbstractController::Base的Controller。显然,它的行为与其他Controller不同。其格式大致为:classSchwadGenericController我尝试了正常测试,这是我目前要让任何事情发生的地方。require'rails_helper'describeSchwadGenericControllerdo#before(:each)do#SchwadGenericController.skip_authorize_resource#end

随机推荐