C++ 继承与多态(二):虚函数与虚函数表

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;
}

解释说明:

  1. (long long*)(&base) : 表示base取地址值之后再按照(long long*)型 ,也就是每8个字节进行存取,这和指针的大小8字节一致,所以合理。下面一部分会对地址强转进行说明。
  2. 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进行取地址,然后再强制指针类型转换的过程中,强制转换的类型决定了读取的字节数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值