虚函数,虚函数表

一、虚函数:

  • 虚函数——类成员函数前面加virtual关键字,则这个成员函数称为虚函数
  • 虚函数重写:当在子类里定义了一个和父类完全相同的虚函数时,则称子类的这个函数重写(覆盖)了父类的这个虚函数。
  • 重写就是将子类里面虚函数表里的被重写的父类函数地址全都改成了子类函数的地址

虚函数覆盖(重写)

class person
{
public:
	virtual void BuyTickets()
	{
		cout << "买票" << endl;
	}
};

class student :public person
{
public:
	virtual void BuyTickets()
	{
		cout << "买票——半价" << endl;
	}
};

总结:

  1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)
  2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
  3. 只有类的成员函数才能定义为虚函数
  4. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual
  5. 静态成员函数不能定义为虚函数
  6. 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,萨博事故遭遇过不要将operator定义为虚函数,因为容易使用时一起混淆
  7. 不要在构造函数和析构函数里调用虚函数,在构造函数和虚构函数中,对象时不完整的,可能发生未定义的行为
  8. 最好把基类的析构函数声明为虚函数,why?(析构函数比较特殊,因为派生类的析构函数和基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)

纯虚函数:

纯虚函数:

  • 在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)
  • 抽象类不能实例化出对象
  • 纯虚函数在子类里重新定义以后,子类才能实例化出对象
class person//不能实例化出对象
{
public:
	virtual void Display() = 0; //纯虚函数
protected:
	string _name;
};

class student : public person
{
	virtual void Display();
};

 为什么要引入抽象基类和纯虚函数?

  • 为了方便使用多态
  • 在很多情况下,基类本身生成对象是不合理的
  • 如果程序函数在基类中没有定义,必须在子类中加以实现
  • 如果一个基类含有一个或多个纯虚函数,那么他就属于抽象基类,不能被实例化

二、虚函数表

  • 一个有虚函数的类
class Base
{
public:
	virtual void fun1()
	{}
	virtual void fun2()
	{}
private:
	int a;
};

void Test()
{
	Base b;
}

我们可以看到对象b里面有一个_vfptr,这个_vfptr指向的内容就是虚函数表。无论是单继承还是多继承都会有这个虚函数表

 单继承的虚函数表:

class Base
{
public:
	virtual void fun1()
	{
		cout << "Base::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base::fun2()" << endl;
	}
private:
	int a;
};

class Derive :public Base
{
public:
	virtual void fun1()
	{
		cout << "Derive::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "Derive::fun3()" << endl;
	}
	virtual void fun4()
	{
		cout << "Derive::fun4()" << endl;
	}
private:
	int b;
};

我们来看这个单继承的虚函数表: 

我们预想的是fun1(),fun2(),fun3(),fun4() 的地址应该都在这个虚函数表中,但是为什么这里只有fun1(),fun2()的虚函数表呢?

因为编译器在这里做了一些处理,所以我们需要手动的去打印这些函数的地址

class Base
{
public:
	virtual void fun1()
	{
		cout << "Base::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base::fun2()" << endl;
	}
private:
	int a;
};

class Derive :public Base
{
public:
	virtual void fun1()
	{
		cout << "Derive::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "Derive::fun3()" << endl;
	}
	virtual void fun4()
	{
		cout << "Derive::fun4()" << endl;
	}
private:
	int b;
};

typedef void(*FUNC)(void);
void PrintVTable(int* VTable)
{
	cout << "虚表地址" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d个虚函数的地址:0X%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Derive d;
	PrintVTable((int*)(*(int*)(&d)));
	system("pause");
	return 0;
}

运行后结果为下:

这里就可以看出来,在Derive中重写了Base中的fun1(), 虚函数表中就将的被重写的父类函数地址全都改成了子类函数的地址。

多继承的虚函数表

class Base1
{
public:
	virtual void fun1()
	{
		cout << "Base1::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base1::fun2()" << endl;
	}
private:
	int a;
};

class Base2
{
public:
	virtual void fun1()
	{
		cout << "Base2::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "Base2::fun2()" << endl;
	}
private:
	int b;
};

class Derive :public Base1, public Base2
{
public:
	virtual void fun1()
	{
		cout << "Derive::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "Derive::fun3()" << endl;
	}
private:
	int d;
};

typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
	cout << "虚表地址" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d个虚函数的地址:0X%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}

void Test()
{
	Derive d1;
	int* VTable = (int*)(*(int*)&d1);
	PrintVTable(VTable);
	int* VTable2 = (int*)(*(int*)&d1);
	PrintVTable(VTable2);
}

int main()
{
	Test();
	system("pause");
	return 0;
}

Dervie类中的fun3()在Base1的徐哈桑农户表中,而Base1是先继承的类

  • 子类的虚函数会存在自己先继承的那个父类的虚函数表中
  • 子类如果重写父类的虚函数,则父类虚函数表中父类的地址都换成了子类虚函数的地址

三、

1.为什么静态成员哈桑农户不能定义为虚函数?

  • 因为静态成员函数是所有对象共享的资源,但是这个静态成员函数没有this指针,而且虚函数只要对象才能调用,但是静态成员函数不需要对象就可以调用,所以这里有冲突

2.为什么不要再构造函数和析构函数里面调用虚函数?

  • 构造函数:在构造函数中,对象是不完整的,还没有为“虚函数表”分配内存,所以这个调用也是违背先实例化,后调用的准则
  • 析构函数:一般析构函数先析构的是子类对象,而在父类中调用一个重写的fun()函数,虚函数表里存放的实际是子类的fun()函数,这时候子类也叫析构了,当你调用的时候就会调用不到

猜你喜欢

转载自blog.csdn.net/audience_fzn/article/details/81475584
今日推荐