继承和虚函数
对象模型:关于vptr和vtbl
侯捷大神的课程(讲的巨TM好!有一定难度,但是很精彩,c++学习的小伙伴一定要看!)
我们知道,如果子类继承了父类,那么子类是能够继承父类的所有数据的(如果继承限制允许)
继承中使用虚函数:
不是虚函数:这样的函数在父类中已经设计好,而且是不想要子类继承的
不纯虚函数:这样的函数父类已经实现了,子类在继承父类此函数的时候,能够根据自己的需求使用或者完善这个功能
纯虚函数:所有的功能父类已经实现了,让子类继承这个功能(也是能修改的)
虚函数最经典的用法:
很巧妙的框架:
因为有些函数子类没法实现(或者父类所设计的功能是拿来卖钱,卖给子类),所以就继承父类,通过子类的对象来调用父类的函数 myDoc.OnFileOpen();
让子类定义所继承的父类,完成功能,也就是父类提出了功能,将其延缓了(几年后再用等),子类在用的时候在去定义它,这个方法就是(Template Method)
下面举一个栗子:
某厂面试题:
很经典的
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i )
https://zhidao.baidu.com/question/372005299.html
poorcowboy
回答:
typedef void( *Fun )( void ); //把Fun定义为一个没有参数,返回void类型的du函数指针
*( ( int* ) * ( int* )( &b ) + i )这一段,
(int*)*相当于没有进行zhi任何操作,所以等同于
*( ( int* )( &b ) + i )
这里先取b的地址dao,然后把地址转换成int*,之后+i是指针算术,也就是在b的地址上加一个int的长度,最后最前面的*是解指针,座椅这段最后返回的是“b的地址+i个int长度的值”
最前面的(Fun)是强制把“b的地址+i个int长度的值”转换为一个“没有参数,返回void类型的函数指针”,所以pFun就是一个函数指针,其指向的位置从一开始的b的地址,每次循环加一个int的长度
然后我们开看,b的地址,b是一个B类型,B类型的第一个函数是g(),而第一次循环pFun的地址就是b的地址,b又没有属性(私有或共有变量)所以b的地址就是b中第一个函数g的地址,所以第一次循环的pFun()相当于调用B::g
( Fun )*( ( int* ) * ( int* )( &b ) + i ); 这里*( ( int* ) * ( int* )( &b ) + i )最前面的*是对上面的结果进行解指针,也就取把b的地址+i个int长度的这个地址的值,并把它转换为Fun类型,也就是一个没有参数,返回void类型的函数指针,所以最后得到的就是一个函数指针。这个指针所指向的地址就是
然后我们来看循环,循环中3次pFun变量分别被赋了3次值,每次都是一个函数指针
由于B类型中有virtual函数,所以b的地址指向的是b的vtbl(如果你不知道这个,你面试就没戏了),vtbl可以看作一个保存了函数指针的数组,每个元素就是一个int长度,在vtbl中B::g,A::f,B::h是按照如上顺序排列的,所以第一次循环指向B::g,那么后两次就指向A::f和B::h了
至于为什么是按照这样的顺序排列的,是因为其声明顺序,首先是父类的virtual函数按照其生命顺序放入vtbl中,然后是子类的放进去,所以其顺序是:A::h,A::f(这是父类的声明顺序)子类中只有B::H是新声明的,所以顺序是A::g,A::f,B::h。
又因为b的类型是b,你知道什么事多态和动态绑定,就明白第一个调用的为什么是B::g而不是A::g了。
关于C++ 对象的内存布局
haoel
大神的文章:
https://blog.csdn.net/haoel/article/details/3081328
上边说的就是虚函数中的vptr和vtbl,以及继承和多态的使用
对象模型:关于vptr 和vtbl
通过指针p 调用C::vfunc1()
编译器看到一个调用的动作,在过去c的时代,是把它编译成一个特定的语法,call调用***一个地址,你要调用哪一个函数,编译器就跳到一个地方去,将来在return 回来,这就是静态绑定,精准到地址。
现在通过指针调用虚函数的时候,是面向对象设计的关键,避免繁琐的类型判断动态绑定(虚机制),逻辑意义就是走的vptr->vtbl->fun()
(
条件:
通过指针调用,
指针p需要向上转型,子类->父类,即new的对象是子类,声明时是父类,保证安全
必须要有虚函数)
虚函数的指一种用法就是多态,指针指向不同的类型list<A*>mylist
解析成c: (*(p->vptr)[n])(p);
(*p->vptr[n])(p);