c++的声明函数是哪_基础篇:继承,多态和虚函数

小小将:C++虚函数​zhuanlan.zhihu.com
2e26f0ee3c05fbb1112f816cddd98d69.png
LWenlong:C++ 多态与虚函数​zhuanlan.zhihu.com
C++ 继承、多态、虚函数 - 掘金​juejin.im

目录:

  • 继承
  • 多态性
  • 虚函数
  • 纯虚函数和抽象类

以下个人总结,如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢!

继承

所谓继承,是指在一个已存在类的基础上建立一个新类。已存在的类称为基类(base class)或父类,新建立的类称为派生类(derived class)或子类。
继承性是面向对象程序设计最重要的特征之一:
1) 继承抽取了类之间的共同点,减少了代码冗余,是实现软件重用的重要手段,继承减少了代码冗余;
2) 继承还是实现软件功能扩充的重要手段;
3) 继承反映了类的层次结构,并支持对事物从一般到特殊的描述,这符合人的认知规律和行动准则。

1. 派生类继承了基类的什么?

  • 所有成员变量,包括static静态成员变量
  • 成员方法,除构造和析构以外的所有方法
  • 作用域也继承了,但是友元关系没有继承

2. 编写派生类的4个步骤:

  • 吸收基类的成员, 除构造函数,析构函数,运算符重载函数,友元函数外所有的数据成员和函数成员全都成为派生类的成员
  • 改造基类成员,当有的基类成员在新的应用中不合适时,可以进行改造,如果派生类声明一个和某个基类成员同名的成员,派生类中的成员会屏蔽基类同名的成员,类似函数中的局部变量屏蔽全局变量。 如果是成员函数,参数表和返回值完全一样时,称为同名覆盖(override),否则为重载
  • 发展新成员,增加新的数据成员和函数成员,更适合新的应用
  • 重写构造函数和析构函数

3. 派生类生成的对象的内存布局是什么样的?

派生类对象构造时,基类数据在前,派生类自身数据在后。如下图所示:(B继承了A)

223f9640860994ef842066c79bc975cc.png
图1. 图片来源于:https://blog.csdn.net/IT_Quanwudi/article/details/88081934

4. 继承方式

  • 继承方式有三种,即:public(公有继承)、private(私有继承)、protected(保护继承),如果省略,则默认为private继承方式;
  • 多重继承方式下,各基类之间要用逗号分开,每一个基类都有自己的继承方式,它们互不影响;
  • 派生类只要写出其新增成员的声明或定义即可,基类的内容不必重复。

继承的访问修饰:

基类中      继承方式            子类中

public     & public继承        => public

public     & protected继承     => protected   

public     & private继承       => private

protected  & public继承        => protected

protected  & protected继承     => protected   

protected  & private继承       => private

private    & public继承        => 子类无权访问

private    & protected继承     => 子类无权访问

private    & private继承       => 子类无权访问

作者:锯齿流沙
链接:https://juejin.im/post/5a30f789f265da431c704cf8
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

5. 虚基类

如果一个派生类从多个基类中派生,而这些基类又有一个共同的基类,则在这个派生类将保留共同基类的多份副本。

如下图所示:

5b0c05939d4cb923116e93ca76a7b7c9.png
图2. 图片来源于:https://juejin.im/post/5a30f789f265da431c704cf8

要让派生类只保留共同基类的一份副本,可以让这个共同基类说明为虚基类。

虚基类的定义格式:

class 派生类 :virtual[继承方式] 基类名

多态性

多态(polymorphism)则是C++面向对象的基本思想之一,即抽象(abstraction),封装(encapsulation),继承(inheritance),多态(polymorphism)。如果我们希望仅仅通过基类指针就能操作它所有的子类对象,那这就是多态。
在面向对象方法中一般是这样表述多态性的:同样的消息被不同类型的对象接收时导致的不同行为。所谓消息是指对类的成员函数的调用,不同行为是指不同的实现,也就是调用了不同的函数。
多态的类型:
重载多态:普通函数或类的成员函数重载就属于这种类型
强制多态:强制数据类型的变化,以适用函数或操作的要求
包含多态:类族中定义于不同类的同名成员函数的多态行为,主要通过虚函数来实现
参数多态:类模板属于这种类型,使用实际的类型才能实例化

C++的多态的实现有两类:

  • 静态多态性:也就是静态联编下实现的多态性,即是在程序编译时就能确定调用哪一个函数,函数重载和运算符重载实现的就是静态多态性;
  • 动态多态性:也就是动态联编(虚函数)下实现的多态性,它只有在程序运行时才解决函数的调用问题,虚函数是实现动态多态性的基础。
什么是联编 ? 又称为关联或绑定,是描述编译器决定在程序运行时,一个函数调用应执行哪段代码的一个术语,它把一个标识符与一个存储地址联系起来。

虚函数

首先需要理解虚函数是怎么工作的:

虚函数是用来在运行时(runtime),自动把编译时未知的对象,比如用户输入的对象,和它所对应的函数绑定起来并调用。当一个类包含虚函数时,编译器会给这个类添加一个隐藏变量,即虚函数表指针(virtual table pointer),用来指向一个包含函数指针的数组,即虚函数表(virtual table)。当一个虚函数被调用时,具体调用哪个函数就可以从这个表里找了。

1) 只有类的成员函数才能声明为虚函数,普通函数不存在继承关系,不能声明为虚函数;

2) virtual关键字出现在虚函数的声明处,在虚函数的类外定义时不加virtual;

3) 子类重写的函数默认是虚函数,也可以显式的加上 virtual,也可以不加;

4) 静态成员函数不能声明为虚函数;

5) 虚函数不能是内联函数,加上 inline 是没有效果的;

6) 构造函数也不能声明为虚函数,因为它是在对象产生之前运行的函数;

7) 析构函数可以是虚函数而且通常声明为虚函数。

为什么要引入虚析构函数 ?

用new命令建立派生类对象时返回的是派生类指针,根据赋值兼容规则,可以把派生类指针赋给基类指针。当用delete 基类指针 来删除派生类对象时,只调用基类的析构函数, 不能释放派生类对象自身占有的内存空间。

虚析构函数的声明格式 

虚析构函数与一般虚函数的不同之处 :

  • 当基类的析构函数被声明为虚函数时,它的派生类的析构函数也自动成为虚函数,这些析构函数不要求同名;
  • 一个虚析构函数的版本被调用执行后,接着就要调用执行基类版本,依此类推,直到执行到派生序列的最开始的那个析构函数的版本为止,也即说派生类析构函数、基类析构函数能够依次被执行。

其实作为一个多态的基类,不仅仅析构函数要声明为虚函数,如果想让不同的派生类用不同的方法实现同一个函数,这个函数也要被声明为虚。换言之,大多数情况下,如果没有虚函数,这个类就不应该被用作一个基类,但也有少数例外。

但并不是所有的基类都是被用来实现多态的,比如Uncopyable类,单纯只是为了实现某个功能,而不是希望通过它的指针来操作某个对象,那么就不需要将析构函数声明为虚函数。以及某些类就不是用来当做基类的,比如标准库的string类和STL容器类,也不需要将析构函数声明为虚函数。

但是!如果不用来当做基类,随便把它的析构函数声明为虚函数不是不必要,而是不好!

因为标准库的功能实在强大,直接拿过来用再加一点自己的功能岂不美哉?比如:

class 

这样的写法同样会导致一开始讲的内存泄漏,因为标准库的字符串并没有把析构函数定义为虚函数,它们并不是用来拿去继承的,所以不能随便继承,包括STL。

虚析构

  • 在多态中,如果释放父类指针(指向子类的父类指针),只会调用父类的析构函数,将父类的析构函数声明为虚函数(虚析构,加 virtual 修饰的析构函数),就会先调用子类的析构函数再调用父类的析构函数,所以在多态中,要用虚析构。
  • 父类的析构函数加了 virtual 修饰,delete 会调用子类和父类的析构函数,子类可以显式的加 virtual ,也可以不加, 默认是有的 virtual。
  • 还有一点需要注意的,delete 谁的指针就会调用谁的析构函数。
参考: https:// zhuanlan.zhihu.com/p/10 4379345

纯虚函数和抽象类

纯虚函数

1)在某些情况下, 在基类中不能为虚函数提供具体定义, 这时可以把它说明为纯虚函数。它的定义留给派生类来完成。

2)纯虚函数的声明格式:

class 

1) 纯虚函数没有也不允许有函数体,如果强行给它加上将会出现错误;

2) 最后的“ = 0”并不表示函数的返回值为0,它只是形式上的作用,告诉编译系统“这是纯虚函数”;

3) 是一个纯虚函数的声明语句,它的末尾应有分号;

4) 纯虚函数的作用:在基类中为派生类保留一个虚函数的名字,以便派生类根据需要进行定义。如果在基类没有保留虚函数的名字,则无法实现多态性。

5) 如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数进行定义,则该虚函数在派生类中仍然为纯虚函数。抽象类

对于抽象类(abstract class),抽象类是包含至少一个纯虚函数的类(pure virtual function),而且它们不能被实例化,只能通过指针来操作,是纯粹被用来当做多态的基类的。

1) 当一个类具有一个纯虚函数,这个类就是抽象类

2) 抽象类不能实例化对象

3) 子类继承抽象类,必须要实现纯虚函数,如果没有,子类也是抽象类

抽象类的作用:为了继承约束,根本不知道未来的实现抽象类不能用作参数类型、函数返回类型或强制类型转换,但可以声明抽象类的指针或引用。

因为多态的基类需要有虚析构函数,抽象类又需要有纯虚函数,那么在抽象类中就要把析构函数声明为纯虚函数:

class 

同时注意,当在继承层级中某一类的析构函数被调用时,它下一级类的析构函数会被随后调用,最后一直到基类的析构函数,因此作为析构函数调用的终点,要保证有一个定义,否则链接器会报错。

AWSL

Reference

  • C++继承和多态(虚函数、纯虚函数、虚继承)
  • c++ 继承与多态, 虚基类,虚函数,纯虚函数
  • C++ 继承、多态、虚函数 - 掘金
  • Effective C++读书笔记(7): 析构函数虚或实
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值