Open bosthhe1 opened 1 year ago
为什么我们将子类的引用或者指针传给父类,父类去调用的时候,会调用到子类的虚表,是因为以引用为例,引用时对子类的一部分取别名,但那一部分还是子类的,所以调用的时候,还是会去调用子类的虚函数,那为什么不是引用或者指针就不能构成重写了呐?是因为为了防止编译器紊乱,如果允许子类直接赋给父类,父类就可以调用子类的虚函数,那么就是父类继承子类,会把关系搞乱,所以不支持除了引用或者指针,其他都不能构成重写
namespace hxh
{
struct person
{
virtual person& fun(){ cout << "person::fun()" << endl; return *this; }
virtual person& count(){ cout << "person::count()" << endl; return *this; }
int a = 0;
};
class student : public person
{
public:
student& fun(){ cout << "student::fun()" << endl; return *this; }
int b = 0;
};
}
int main()
{
hxh::person a;
hxh::student b;
return 0;
}
我们看到以上的代码,我们将fun虚函数重写,但是count虚函数没有重写 我们通过调试看到,没有重写的部分继承下来,虚表中的函数地址是一样的,都是父类虚函数的地址,重写的部分则被子类覆盖
namespace hxh
{
struct person
{
virtual person& fun(){ cout << "person::fun()" << endl; return *this; }
virtual person& count(){ cout << "person::count()" << endl; return *this; }
int a = 0;
};
class student : public person
{
public:
virtual student& fun(){ cout << "student::fun()" << endl; return *this; }
virtual student& sum(){ cout << "student::sum()" << endl; return *this; }
int b = 0;
};
}
int main()
{
hxh::person a;
hxh::student b;
return 0;
}
在这种情况下,当子类存在新的虚函数,编译器没有将新的虚函数打印出来,但是我们看到底层,新的虚函数对应的地址还是在内存中存在
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main()
{
Derive a;
return 0;
}
我们看到多继承Derive类继承了Base1和Base2,所以子类里面会有两个虚函数表,且两个虚函数表中的func1(),都会被覆盖 我们看到内存中Base1表和Base2表,按照道理来说他们中的fun1()虚函数都应该指向同一块地址,但是内存上却不是显示同一块地址,其实这里是编译器对继承的第二个类的虚函数表进行了封装,且在这里需要注意,子类的个有的虚函数(fun3()),只会将fun3放入第一个继承的虚表中(Base1),第二个虚表中没有
在多继承这里构成重写,其实对于Base1和Base2底层都是指向同一块地址,但是为什么虚函数表中的地址不相同,是因为编译器对Base2的指针进行了封装多层,为什么需要多层封装呢?是需要修饰this指针,如果一直跳转,最终也是跳转到对应的虚函数 这里需要注意的点是:在调用类对象或者类对象函数的时候寄存器里面存的是this指针。
typedef void(*VF_prt)();
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1 = 1;
};
class Derive : public Base1{
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func2() { cout << "Derive::func2" << endl; }
private:
int d1 = 3;
};
void func4(VF_prt* P)
{
for (int i = 0; P[i] != nullptr; i++)
{
cout << "P[" << i << "]:" << &P[i] << endl;
}
}
int main()
{
Derive d;
//下面两种方法都可以,主要思想就是将头四个字节/八个字节的数据取出来,然后将传递过去
func4((VF_prt*)*(int*)&d);//32位下可以,64位下不行
cout << endl;
func4(*(VF_prt**)&d);
return 0;
}
这里我们需要将2传过去,如果是(VF_prt)&d,就是将1传过去,对方看的的还是d的地址,如果是(VF_prt)(int)&d传过去,对方看的是ptr的地址 这里就是为什么要进行一次解引用的原因,因为我们需要将虚表指针取出来,所以需要将前4个字节的虚表指针拿到,所以解引用的效果就是取到前4个字节,(VF_prt)(int)&d中的(int*)&d这就是拿到前4个字节,然后再强转为函数指针传过去
需要注意的是,构造函数不能是虚函数,构造函数里面的都是该类本身的函数,因为构造函数没有虚函数的概念,且需要体会到虚函数是接口继承
class A1
{
public:
void foo(){ printf("foo"); }
virtual void bar(){printf("bar");}
A1(){ bar(); }//构造函数没有虚函数概念,所以这里的bar(),调用的是A1()自己的bar
};
class B1:public A1
{
void foo(){ printf("b_foo"); }
virtual void bar(){printf("b_bar");}
};
int main()
{
Base1* b = new Derive;
b->test();
}
class Base1 {
public:
virtual void func1(int a = 1) { cout << a<<"Base1::func1" << endl; }//这里将func1()的接口继承下去a = 1也被继承下去
virtual void test()
{ func1();}//此时体现的是接口继承
private:
int b1 = 1;
};
class Derive : public Base1{
public:
virtual void func1(int a = 0) { cout << a << "Derive::func1" << endl; }//这里的a是被继承下来的1,不是0
private:
int d1 = 3;
};
int main()
{
A1* p = new B1;
p->foo();
p->bar();
}
首先我们需要明白虚函数和普通函数不一样,虚函数的调用时一个指针指向一个虚函数的数组,然后虚函数的数组是一个指针数组,指针数组里面的指针才指向对应的虚函数,所以在含有虚函数的类,实例化会多一个虚表指针,会比一般类多4个字节,同时也需要注意的是,同类型的对象,指向的虚表时同一个虚表,不同类型对象不管子类有没有重写或者虚函数,子类和父类都会建立自己的虚表。即使子类虚函数都是父类继承的,虽然虚函数的地址相同,但是虚表是各自一份 大致思路是下图这样 而重写在底层的叫法叫覆盖,编译器在底层子类和父类构成重写的函数,子类的虚函数就会覆盖父类的函数 我们调试以上代码发现,子类对象存有虚表指针和父类对象的虚表指针是不一样的,所以会调用不同类型的虚函数