做网站如何使用网页插件,分析某个网站建设,没后台的网站怎么做优化,官方网站建设报价表封装#xff0c;继承#xff0c;多态不只是C的三大特性#xff0c;而是面向对象编程的三大特性。
什么是多态#xff1a;
不同的对象做同一件事情#xff0c;结果会出现多种形态。
1.满足多态的几个条件
1.父子类完成虚函数重写#xff08;需要满足三同#xff1a;函…封装继承多态不只是C的三大特性而是面向对象编程的三大特性。
什么是多态
不同的对象做同一件事情结果会出现多种形态。
1.满足多态的几个条件
1.父子类完成虚函数重写需要满足三同函数名参数返回值都需要相同。
2.父类的指针或引用去调用虚函数。指向谁就调用谁的虚函数
既然有了父类和子类那么说明多态发生的前提是继承。
例子 不同身份的人买票的价格是不同的。
但如果不满足多态的条件就达不到我们想要的结果 2.多态的坑
2.1虚函数重写的两个例外 强调一下
1.返回值只能是父类返回父类的指针或引用子类返回子类的指针或引用顺序不能反过来。
2.返回值可以是其他不相关的父子类也可以是自己的父子类。 关于析构函数的重写
如果我们正常的写析构函数看看它们调用的情况 可见两次析构都调用的是基类的这是正常现象两个Person的指针自然调用Person的析构。但这并不是我们的目的我们想要的是指针指向谁就应该调用谁的析构也就是说我们想让P2调用Student的析构只有这样才满足多态的规则。
但如果我们用virtual修饰这两个析构函数呢 加上virtual就达到了我们的目的也就是说父子类的析构函数构成虚函数重写。
但有些奇怪这两个析构函数的名字明明不同不符合多态的语法为什么依然可以构成重写呢
其实这里编译器会把析构函数的名字全部换成destructor这样就满足多态的语法了
那C的语法为什么不把析构函数的名字直接定义成destructor而是私下换名字呢这样不麻烦嘛
其实这也是无奈之举因为析构函数的概念早于多态在多态之前已经把析构函数的名字设计好了祖师爷也没想到后面设计多态的语法时会在这个地方有坑只能自己私下改名字了。
结论建议把析构函数写成虚函数防止内存泄漏的发生。
其实还有一个例外就是派生类可以不写virtual。 这也是C语法常常被吐槽的点但还是建议写上不然容易被人吐槽。
2.2一道杀人诛心的面试题 这道题目曾被多家大型公司百度腾讯等当作面试题。
先说答案B。是不是有点匪夷所思
思路
继承只是一个形象的说法实际上在继承时并没有把父类的成员拷贝到子类中而是用了一套查找规则在P指针调用父类函数时编译器会先在子类中找没找到再去父类去找所以父子类的同名函数会构成隐藏。
所以在给test函数传this指针时this的类型是A*。但用this调用func时依然构成多态调用因为this依然指向的是B类型所以会直接调B中的func。
下面就到这个题目特别坑的点多态的虚函数重写重写的只是函数体的实现。意思就是
子类的func是对父类func的重写但只是重写了函数体的实现函数的结构部分依然用父类的。所以val的值是1。
那咱们回过头来想既然子类的函数体结构部分没有调用那可以省略virtual好像也有些道理。
3.关键字override和final
3.1final
如果让你设计一个不能被继承的类其实有两种方法。
方法一
把基类的构造函数定义为私有。C98 原因是基类的构造函数在派生类中不可访问那么派生类就无法对象实例化。
方法二
利用关键字final。C11 方法二很简单就是用final修饰基类之后就无法继承了。
3.2override
override是加到派生类的重写虚函数中用于检查是否完成重写。 成功重写时是没有任何报错的。 没有成功重写就会报错所以我们在写代码时尽量把这个关键字加上。
4.对比重载/重写/隐藏 5.多态的底层
5.1虚函数表指针 这道题的答案是12。因为Base类中有一个虚函数所以在成员对象中就会多出一个指针叫虚函数表指针简称虚表指针。
这个指针指向了一张表这个表里存放了虚函数的指针。 在x86平台下
通过对比监视窗口和内存窗口可以看到在地址0x00E17B34位置存放了00e112df指针在地址0x00E17B38位置存放了00e1124e指针。
5.2虚函数表指针的作用
下面我们来看一看编译器是如何通过虚表指针实现多态的。 这是Mike的对象模型和监视窗口 这是John的对象模型和监视窗口 通过对比Mike和John的对象模型发现
在John的对象模型中004a9b54和下面的1是继承Mike的1下面的2是John的成员变量。
虽然是继承下来的但有些不一样
继承下来的虚表指针和Mike的虚表指针不一样一个是004a9b34一个是004a9b54。
既然虚表指针不一样那虚表指针里面的函数指针也应该不一样观察监视窗口我们发现Mike的虚表指针中存放的是Person的BuyTickt函数John的虚表中存放的是Student的BuyTickt函数。
所以多态的实现过程就是通过虚表指针当Student对象传给Person对象时通过切片把虚表指针切过去然后通过Student的虚表指针调用Student的虚函数。当Person对象传给Person对象时通过Person的虚表指针调用Person的虚函数。
但是当不满足多态语法时编译器先检查如果不满足多态语法编译器就直接通过对象类型去调用成员函数就不会通过虚表指针调用了。
补充一下
每个对象都有一张虚表同类型的对象共用一张虚表不同类型的对象虚表不同。
6.单继承中的虚表 Derive继承Base后通过监视窗口查看它们的虚表发现Base的虚表是正常的里面有两个函数指针Func1和Func2。但是Derive的虚表有问题有Derive的Func1和Base的Func2。
其实在继承后派生类的虚表可以形象的说把基类的虚表拷贝下来如果构成重写那么派生类的重写的函数把基类的覆盖掉。所以Func1是Derive的Func2是Base的。
但问题是Func3和Func4哪里去了呢
这也是VS的bug其实VS的监视窗口有些时候并不准确还要去内存窗口看一下 通过对比监视窗口和内存窗口我们发现Derive的虚表中好像存了4个函数指针 前两个002b1410和002b1389和监视窗口中的一致但后两个还不能确定只能说比较像所以我们需要写一个程序来验证我们的猜想。 验证结果Derive的虚表中存了4个函数指针
解释一下这个程序写这个程序要求对指针的理解程度极高如果你能看懂这个程序那么你在C语言指针方面的掌握非常好如果能写出这个程序那么你对指针的理解已经达到优秀了
首先虚表本质上是一个函数指针数组。我们平时传参传数组时C语言考虑到效率问题往往传的是首元素的地址比如一个int型的数组传参时传int*的指针。那么虚表中存放的全是函数指针所以我们传参的时候应该传函数指针的地址也就是二级指针。
那这个二级指针如何获得呢在C语言部分大家都知道数组名就是首元素的地址所以我们只需要得到虚表的名字是d的成员变量就可以了也就是Derive d中的前4个字节。
难道将d强制类型转换成int就可以了嘛这是不可以的两个完全不想关的两个类型是不能强转的。这里给大家总结一下1.int和float可以互相强转char本质上也属于int型2.任何类型的指针都可以相互强转 3.任何类型的指针可以和int相互强转。
知道这个知识后就可以取d的地址前强转成int*再解引用这样就拿到了d的前4个字节但这4个字节的类型是int它真正的类型应该是虚表中首元素的地址也就是Func1的指针的地址强转过去后直接函数传参然后采用数组下标的方式变量整个虚表就可以访问到虚表中所有的函数指针然后再通过函数指针调用对应的函数就可以确认猜想