C++学习笔记04-多态公有继承与虚函数

①多态公有继承

作用;
子类public的部分中拥有父类的public属性和public方法
子类的private部分拥有父类的private属性和private方法,,但只能通过父类的public和protected方法访问

初始化:通过调用父类的构造器和子类的构造器完成

指针和引用的特殊用法:
可以指向子类或者父类对象,
父类指针和引用可以指向子类的对象,
子类指针和引用不可以指向父类

②虚方法

(Ⅰ)virtual修饰方法,

如果方法被声明为virtual,那么系统将根据指针,引用指向的对象的类型(也就是实际类型)来选择方法,
如果方法被没有virtual,那么根据
指针,引用类型
选择调用的方法,也就是根据编译类型
图示说明:
virtual方法的调用
如果希望子类重写父类方法,则可以在父类方法声明前面加上virtual
虚方法的一些语法:
① 父类virtual方法在子类中自动加上virtual修饰,也就是说在派生类中自动成为虚方法
通常应该将父类析构函数声明为virtual的,这是一个好习惯!!

(Ⅱ)virtual修饰析构函数

为何需要virtual析构函数

将析构函数声明为虚的可以根据实际类型来销毁对象,而不是根据指针类型
确保了对象将会被正确析构,内存资源正常释放

(Ⅲ)静态联编和动态联编

函数名联编

将源代码中函数调用解释为特定的代码块执行,就是函数名联编

静态联编

c++中函数调用是根据函数名称形参列表来选出最佳候选函数,
因此,在编译过程中可以确定的函数调用就是静态联编,也叫早期联编
对象调用和非virtual方法属于静态联编

动态联编

由于有子类方法和父类方法,要根据对象的类型来选择方法,
编译器并不知道运行时产生的对象的类型,只知道指针和引用的类型
所以这个函数调用不能在编译时期确定下来
要等到运行时才能确定
,这就是动态联编,也就是晚期联编
编译器对虚方法采用动态联编

动态联编和静态联编的区别与联系:
效率方面

编译器会采取一些办法来追踪父类指针或者引用指向的对象类型
因此,这增加了额外的开销
因此:静态联编比动态联编效率高!!,默认的函数调用是静态联编!!

概念模型方面

如果不希望子类重新定义该方法,就不要将该方法声明为virtual
仅将那些希望被子类重新定义的函数声明为virtual

子类如何访问父类方法,变量?

使用作用域解析运算符 :: 指出父类方法变量即可
举个例子:
Base::fun();
Base::variable;
父类名称+ :: +具体方法/变量就可以了

虚函数表的工作原理

通常,编译器处理虚函数的方法是:

为每个对象定义一个隐藏成员,这个隐藏成员指向了一个存储虚函数地址的指针数组的首地址
这种数组就是虚函数表
虚函数表的一些基本操作
①:父类有一个隐藏成员指针,这个指针指向了父类所有虚函数地址表,
派生类对象将包含另外一个独立的地址表的指针。
因此父类和子类的虚函数表的首地址是不同的,指向的是两个不同的内存空间

如果子类中重新定义了该virtual方法,那么该方法在子类的虚函数表中就会有另外的新地址,
覆盖父类的那个virtual方法的地址,
如果子类还是用的父类的virtual方法,将在子类虚函数表中保存原始版本的地址
③子类增加的新的virtual函数,那么该函数地址也将被添加到虚函数表中
虚函数表的工作原理

调用虚函数

调用虚函数时,将会在虚函数表里面遍历指针数组,不断的寻址,找到后就去
特定的地址里面执行函数代码块

虚函数表的额外开销

内存和指向速度都有一定成本;
①对象的内存空间增大,增大量为存储地址的空间,就是指针数组的大小和隐藏的成员指针
②对于每个类,都将创建一个指针数组
③对于每次虚函数的调用,都要不断的寻址,执行了额外的操作

虚函数的注意事项

①原则:

仅将在子类中重新定义的函数声明为virtual

②构造函数

构造函数不能声明为virtual,子类不能继承父类的构造器,因此virtual修饰构造器没有意义

③析构函数

应该将析构函数声明为virtual,除非该类不派生子类,
有子类的话,声明了virtual析构就是为了保证对象能够被正确的销毁,并且正确的释放内存

④友元函数

不可以把友元函数声明为virtual,因为virtual不是类成员
所以继承的时候子类不会获得友元函数,
这就好比是儿子继承父亲财产的时候,不能够继承父亲的人缘关系

⑤方法的覆盖

如果子类重新定义了方法,那么子类中新定义从方法将覆盖父类中所有的同名方法,
编译器不管你形参列表怎么样,只要你方法名字和父类的相同了,
就是直接覆盖父类中所有的同名方法
,在用代码调用方法时时不可以使用父类的方法的
并不可以说我子类的重新定义的方法形参列表和父类不同,那我应该是函数重载啊,
但是实际上就是只有子类中的方法可以用了,

如果不想繁琐的重写所有父类的方法,使用using语句导入父类方法即可!!
私有继承public父类方法时,该using可以用父类的方法
如果是private方法,using父类方法也访问不了了
举例说明:

#include<iostream>
using namespace std;
class Object
{
	private :
		int height;
	public  ://假若注释public,那么using也不管用了
		Object(int h_) : height(h_){}
		double  getHeight(double i)	{return i;}
        int getHeight()
		{
			return height;
		}
};
class SubClass : private Object//私有继承
{
	public :
		using Object::getHeight;//使用父类的方法,导入父类同名的所有方法
		int getHeight(long a)
		{
			return (int)a;
		}
		SubClass(int h_) : Object(h_){}
};
int main()
{
	SubClass obj(188);
	cout << obj.getHeight(8.0);
	cout << obj.getHeight(999L);
}

运行效果
在这里插入图片描述

(Ⅰ)返回型类型协变

重新定义virtual方法后如果返回的是一个父类的指针或者引用,
则【可以】把返回值类型声明为指向子类的指针或者引用,
因为允许返回值类型随类类型的变化而变化

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿维的博客日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值