浅析C++中的虚函数(一)

 在C++中虚函数是很重要的一个知识点,对于它,还是感觉似懂非懂,这次写下来当作总结了吧。

虚函数的思想是,根据指向或引用的对象类型调用虚函数,而不是根据指针本身或引用本身的类型,虚拟函数在运行时后期解析。换句话说,存在虚函数的类创建一个指向子类的指针或引用,当该指针指向子类时,可以调用派生类中重载父类的方法,没必要管派生类对象是什么,引用也是如此。具体的代码如***释说的很清楚了:

#include <iostream>
using namespace std;
class A
{
public:
	virtual void f()
	{
		cout << "这是 A" << endl;
	}
};
class B :public A
{
public:
	void f()
	{
		cout << "这是 B" << endl;
	}
	void f1()
	{
		cout << "okokok" << endl;
	}
};

int main()
{
    /*
    这是引用的方式
    B b;
	A &a = b;
	a.f();  
    */

	B *b = new B();
    A *a = b;
	a = b;  //B类型的指针赋值给A类型的指针,a指向类B
	a->f();  
	//这里只能调用被重写的f(),想要调用f1()需要用到其他方法
	system("pause");
	return 0;
}

虚函数是如何做到这点的,我从其他地方找到一个比较好的解释,编译器维护了两个表,vptr和vtable,其中vptr是用来指向每个不同类中vtable,每个类只有一个vtable,在每个vtable中有虚函数的地址,vptr是针对每个对象来说的,每个对象都会有一个vptr,所以,只有虚函数没有其他的类的对象大小为4(32位),一般是在对象的地址的开头,和第一个变量之间空出来的空间就是这个指针vptr,当基类对象的vptr被子类对象的vptr赋值时,基类对象的指针就可以指向子类的vtable,这样就实现了动态绑定(或者成为后期绑定或运行时的动态)。如图所示,感觉越说越不明白……

                                        浅析C++中的虚函数(一)

因为通过vptr指向每个类中的虚函数,所以虚函数是不能为static的,static类型的不属于任何一个类。同时因为虚函数是在后期绑定的,当然不能成为内联函数,因为内联函数在编译时期就已经插入到调入点了。

虚函数除此之外还有一些其他有意思的现象,虚函数中的默认参数问题,上代码。

#include <iostream>
using namespace std;
class A
{
public:
	int a = 1;
	int b = 2;
	virtual void f(int x = 0)
	{
		cout << "这是 A 的 x=" << x << endl;
	}
};

class B :public A
{
public:
	void f(int x)
	{
		cout << "这是 B 的 x=" << x << endl;
	}
};

int main()
{
	B *b = new B();
	A *a = b;
	a->f(); 
	system("pause");
	return 0;
}

这里的运行结果是

浅析C++中的虚函数(一)

类B中的虚函数参数使用了类A中的默认值,因为默认参数不参与函数签名(函数签名由参数个数与其类型构成。因此,基类和派生类中f()的签名被认为是相同的,因此覆盖了基类的f()。此外,默认值在编译时使用。当编译器发现函数调用中缺少参数时,它会替换给定的默认值。因此,在上面的程序中,x的值在编译时被替换,并且在运行时调用派生类的f()。因为a是A类型的指针,即使类B中的x存在默认值,也会在编译时被基类的替换。因为虚函数中存在这些套路,所以一般在虚函数中不使用默认值。

C++中虚函数也可以是private的,这个我之前也没有仔细看过想过,也不知道这个性质有什么特别的用处,总之先了解一下吧。比如:

#include <iostream>
using namespace std;
class A
{
private:
	int a = 1;
	int b = 2;
	void f()
	{
		cout << "这是 A" << endl;
	}
	friend int main();
};

class B :public A
{
public:
	void f()
	{
		cout << "这是 B" << endl;
	}
};

int main()
{
	B *b = new B();
	A *a = b;
	b->f(0); 
	system("pause");
	return 0;
}

因为main函数是A类的朋友,所以可以访问,如果不是,编译就不会通过,这个不算是解释,但现在先不急着追究了。

以上的东西大部分都是总结网上看到的,书上写的结构全,但总感觉有些细节的地方没说,网上的全倒是全,就是零零散散的,把我知道的总结一下吧。

这只是虚函数的一小部分,还有没写完的,接着写写。