C++ 继承与多态(二):虚函数与虚函数表
文章目录
注意,这一个专题和虚基类与虚基类表
是两个不同的东西,但是如果是进行了虚继承之后,就不再需要使用虚函数表了,如下面的程序(原因是虚基表中已经存放了虚基类,并不会出现二义性):
一.虚函数的作用
我们先看没有虚函数的情况:
#include <iostream>
using namespace std;
class A {
public:
void func() {
cout << "A::func()" << endl;
}
};
class A1 :public A {
public:
void func() {
cout << "A1::func()" << endl;
}
};
class A2 :public A {
public:
void func() {
cout << "A2::func()" << endl;
}
};
int main() {
A *a = new A;
A *a1 = new A1;
A *a2 = new A2;
a->func();
a1->func();
a2->func();
return 0;
}
很明显,这并不能展现多态的特点,所以需要用到虚函数来展现多态。只需要将基类A中的func函数前面加上virtual即可。
class A {
public:
virtual void func() {
cout << "A::func()" << endl;
}
};
二.关于虚函数的内存布局
1. 没有虚函数的菱形继承
- 程序:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;
/*
虚函数与虚函数表:
*/
class A {
public:
void print() {
cout << "this is A" << endl;
}
};
class A1 :public A {
public:
A1() {
cout << "A1()" << endl;
}
~A1() {
cout << "~A1()" << endl;
}
void print() {
cout << "this is A1()" << endl;
}
};
class A2 :public A {
public:
A2() {
cout << "A2()" << endl;
}
~A2() {
cout << "~A2()" << endl;
}
void print() {
cout << "this is A2()" << endl;
}
};
class B :public A1, public A2 {
public:
B() {
cout << "B()" << endl;
}
~B() {
cout << "~B()" << endl;
}
void print() {
cout << "this is B" << endl;
}
};
int main() {
B *b = new B();
delete b;
return 0;
}
- 内存布局
2. 虚函数的菱形继承
- 程序:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;
/*
虚函数与虚函数表:
*/
class A {
public:
virtual void print() {
cout << "this is A" << endl;
}
};
class A1 :public A {
public:
A1() {
cout << "A1()" << endl;
}
~A1() {
cout << "~A1()" << endl;
}
void print() {
cout << "this is A1()" << endl;
}
};
class A2 :public A {
public:
A2() {
cout << "A2()" << endl;
}
~A2() {
cout << "~A2()" << endl;
}
void print() {
cout << "this is A2()" << endl;
}
};
class B :public A1, public A2 {
public:
B() {
cout << "B()" << endl;
}
~B() {
cout << "~B()" << endl;
}
void print() {
cout << "this is B" << endl;
}
};
int main() {
B *b = new B();
delete b;
return 0;
}
- 内存布局
三.虚函数相关问题
1.构造函数、析构函数能否是虚函数
- 问题:构造函数能否使用虚函数?
不行,下面构造函数执行顺序为 A(), B(); 如果将构造函数设为虚函数,则 B(), B(),将不再调用A()
总结来说,如果构造函数为虚函数,那么派生类生成中将不会调用基类的构造函数,则会发生错误。
class A{
public:
A(){}
};
class B:public A{
public
B(){}
};
A * a = new B();
- 问题:析构函数能够否是虚函数?
可以,而且是必须将析构函数设置为虚函数,因为在delete 的时候就能实际是delete 原来派生类new出来的对象,而不是基类的指针。如果不j将析构函数设置为虚函数,则会导致内存泄漏,因为有的类并没有释放。
注意:只要用到了多态,就必须将析构函数设置为虚析构函数,这样才不会内存泄漏。
class A{
public:
A(){
cout << "A()" << endl;
}
virtual ~A(){
cout << "~A()" << endl;
}
void print(){
cout << "this is A" << endl;
}
int a = 10;
};
class A1:virtual public A{
public:
A1(){
cout << "A1()" << endl;
}
~A1(){
cout << "~A1()" << endl;
}
void printA1(){
cout << "this is A1()" << endl;
}
int a = 1;
};
class A2:virtual public A{
public:
A2(){
cout << "A2()" << endl;
}
~A2(){
cout << "~A2()" << endl;
}
void printA2(){
cout << "this is A2()" << endl;
}
int a = 2;
};
class B:public A1, public A2{
public:
B(){
cout << "B()" << endl;
}
~B(){
cout << "~B()" << endl;
}
void display(){
//cout << A::a << endl;//ERROR, 此时A1中有A::a, A2中也有A::a,所以会出现二义性
cout << A1::a << endl;
cout << A2::a << endl;
}
void printB(){
cout << "this is B" << endl;
}
};
void test0(){
A *a = new B();
a->print();
delete a;
}
int main(){
B b;
b.print();
return 0;
}
2.如何定义抽象类?(即本身不能创建实例,但是派生类可以创建实例的类)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;
/*
问题:如何定义一个基类,此基类不能创建实例, 但是它的派生类却可以创建实例(可以把它当作第二种抽象类)
*/
//注意,派生类在进行继承的时候并不会继承构造函数,析构函数,所以如果class A中的构造函数是在protected的话,那么就无法从外部构造对象了
class A{
protected://可以将这个改为private试试
A(){}
int a = 5;
};
class B:public A{//注意在这里是需要调用A的构造函数的,由于并不是在A的外部进行调用,所以可行
public:
void display(){
//A aaa;//ERROR!
//cout << a << endl;
}
};
int main()
{
B b;
b.display();
return 0;
}
四.虚函数的原理
虚函数实质上就是创建了一张虚函数表,但是注意在基类中只是存放一个指向虚函数表的虚函数指针(VS中占8字节),并且注意,虚函数表总是会存放在类的开头位置方便我们进行内存操作。
#include <iostream>
using namespace std;
class Base {
public:
virtual void f1() {
cout << "Base::f1()" << endl;
};
virtual void f2() {
cout << "Base::f2()" << endl;
};
virtual void f3() {
cout << "Base::f3()" << endl;
};
int *i = new int;
long long *l = new long long;
double *d = new double;
};
typedef void(*FUNC)(void);
int main() {
Base base;
cout << "base的地址:" << &base << endl;
cout << "vptr的地址:" << (long long*)*(long long*)&base << endl;//可以将这里的vptr看做一个指针数组
cout << "i的地址:" << (long long*)*((long long*)&base + 1) << endl;
cout << "l的地址:" << (long long*)*((long long*)&base + 2) << endl;
cout << "d的地址:" << (long long*)*((long long*)&base+3) << endl;
//此处将vptr单独列出来成为指针数组是防止与上面的混淆
long long *vptr = (long long*)*(long long*)&base;
for (int i = 0; i < 3; i++) {
FUNC func= (FUNC)(long long*)vptr[i];
cout << (long long*)vptr[i] << "->";
func();
}
return 0;
}
解释说明:
- (long long*)(&base) : 表示base取地址值之后再按照(long long*)型 ,也就是每8个字节进行存取,这和指针的大小8字节一致,所以合理。下面一部分会对地址强转进行说明。
- vptr是一个指针,总是放在类的开头,指向了一个虚函数表,这个表实质上是一个指针数组,存放着指向虚函数的指针,派生类在进行继承的过程中实质上是继承这个虚函数表,虽然虚函数表存放的位置不同,但是其中虚函数指针指向的内存是相同的。
对比VS的内存布局与输出结果,发现两者一致。
(long long*)(&base) 强制地址转换的解释
首先来看这代码:
#include <iostream>
using namespace std;
void printBinary(int &argc) {
cout << "二进制数为:";
for (int i = 31; i >= 0; i--)//高位到低位输出
{
int a;
a = 0x01 & argc >> i;
cout << a;
if (i % 8 == 0 && i != 0)
{
cout << ",";
}
}
cout << endl;
}
int main() {
int val = 123456;
printBinary(val);
char *a = (char*)&val;
unsigned short *c = (unsigned short*)&val;
int *i = (int*)&val;
cout << "char* :" << (int)*a << endl;//64 恰好等于 二进制的01000000
cout << "unsigned short *:" << *c << endl;//57920恰好等于二进制的 11100010,01000000
cout << "int* :" << *i << endl;
return 0;
}
/*
总结:
在对val进行取地址,然后再强制指针类型转换的过程中,强制转换的类型决定了读取的字节数。
*/
总结:
在对val进行取地址,然后再强制指针类型转换的过程中,强制转换的类型决定了读取的字节数。