文章目录
前言
虚函数的重写实际上是函数实现的重写、对象切片和引用或指针的差异、虚函数表存在在哪里、派生类中自己的虚函数在基类的虚表中、多继承中两个基类中与派生类构成重写的虚函数表在监视窗口地址不同等的介绍
一、虚函数的重写实际上是函数实现的重写
如下情况:
- B类的指针调用test函数,但是B类中没有,它的基类中有test函数,所以可以找到。
- fun函数的调用时通过this指针调用的,这里的this指针是A*, 虽然使用B*调用test函数,但是派生类传给基类,会切片。
- fun函数在基类和派生类中构成虚函数的重写,并且是由A*调用,即基类的指针调用,构成多态。
- 但是虚函数的重写本质上是函数实现的重写,也就是派生类的虚函数的返回值类型、函数名、 形参列表都是基类中继承的,只有函数的实现是派生类中的虚函数的,所以val = 1 打印结果为 fun()----> B---->1
#include <iostream>
using namespace std;
class A
{
public:
virtual void fun(int val = 1) { cout << "test()----> A" << val << endl; }
void test() { fun(); }
};
class B : public A
{
public:
virtual void fun(int val = 0) { cout << "fun()----> B---->" << val << endl; }
};
int main()
{
B* pb = new B;
pb->test();
return 0;
}
二、对象切片和引用或指针的差异
为什么构成多态必须基类的指针或引用?
- 子类赋值给父类对象切片,不会拷贝虚表
- 如果拷贝虚表,那么父类对象虚表中是父类函数还是子类就不确定了,就乱套了
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << " 购票---- 全价 " << endl;
}
virtual void fun1(){}
virtual void fun2() {}
int _a = 0;
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << " 购票---- 半价 " << endl;
}
virtual void fun1() {}
virtual void fun2() {}
int _b = 1;
};
void fun(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
p = s;
Person* ps1 = &s;
Person& ps2 = s;
return 0;
}
- 使用基类的指针或引用,会拷贝虚表地址。
派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
三、虚函数表存在在哪里
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << " 购票---- 全价 " << endl;
}
int _a = 0;
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << " 购票---- 半价 " << endl;
}
int _b = 0;
};
int main()
{
int a = 0;
printf("栈区: %p\n", &a);
int* p = new int;
printf("堆区: %p\n", &p);
static int b = 0;
printf("静态区: %p\n", &b);
const char* c = "hello world";
printf("常量区: %p\n", c);
Person ps;
Student st;
printf("虚表1: %p\n", *((int*)&ps));
printf("虚表2: %p\n", *((int*)&st));
return 0;
}
- 由此可见,虚函数表存放在常量区(代码段)。
四、 派生类中自己的虚函数在基类的虚表中
派生类的虚函数在基类的虚表中,在vs下虚表一般以空(0)结尾
1. 单继承
// 派生类中自己的虚函数会放在基类的虚表中
#include <iostream>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "Person::全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "Stduent::半价" << endl;
}
virtual void func()
{
cout << "Student::func" << endl;
}
};
int main()
{
Person p;
Student s;
return 0;
}
- 验证第二个地址为派生类自己的虚函数的地址
2. 多继承
// 多继承
#include <iostream>
using namespace std;
class A
{
public:
virtual void func()
{
cout << "func::A" << endl;
}
};
class B
{
public:
virtual void func()
{
cout << "func::B" << endl;
}
};
class C :public A, public B
{
public:
virtual void func()
{
cout << "func::C" << endl;
}
virtual void test()
{
cout << "test::C" << endl;
}
};
typedef void(*FUNC_PTR)();
void print(FUNC_PTR* table)
{
for (size_t i = 0; table[i] != 0; i++)
{
printf("[%d]-->%p ", i, table[i]);
FUNC_PTR f = table[i];
f();
}
cout << endl;
}
int main()
{
C c;
int VFTptrA = *((int*)&c);
int VFTptrB = *((int*)((char*)&c + sizeof(A)));
print((FUNC_PTR*)VFTptrA);
print((FUNC_PTR*)VFTptrB);
return 0;
}
五、多继承中两个基类中与派生类构成重写的虚函数表在监视窗口地址不同
总结
虚函数的重写实际上是函数实现的重写、对象切片和引用或指针的差异、虚函数表存在在哪里、派生类中自己的虚函数在基类的虚表中、多继承中两个基类中与派生类构成重写的虚函数表在监视窗口地址不同等的介绍