类继承-虚函数(1)

关于基类的引用和指针与派生类的指针和引用:

基类的引用可以初始化为派生类的对象,可以通过基类引用调用派生了新增的公有方法,也可以调用基类的方法,指针也是一样。反之,将派生了的引用初始化为基类对象是不允许的。

Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = derived;//ok
Based*p1 = &derived;//ok
Derived&r2 = based;//error
Derived*p2 = &based;//error

在某些派生了中,可能要修改基类的方法实现新的功能,我们可以在派生类中使用相同的方法名。但是存在一个问题是什么时候调用基类和派生类同名的方法,用什么方式进行区分?

对于没有任何关键字修饰的同名方法,通过对象类型确定使用哪个版本是一种区分方式,但是很多时候我们用的是指针或者引用,这时候是根据引用或者指针的类型进行调用:基类的引用或指针调用的是基类的方法,派生类的引用或者指针调用的是派生了的同名方法,但是基类的指针可能指向的是派生类对象,我们希望的是通过基类的指针或者引用调用派生类对象的同名方法。

Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = based;
Derived&r2 = derived;
r1.Show();//Based::show()
r2.Show();//Derived::Show()

我们更希望的是通过引用或者指针指向的对象来区分调用的同名方法,将函数声明为虚函数是解决这个问题的方法

虚成员函数:

class Based
{
	private:
		int n;
	public:
		Based(int x) :n(x)
		{
			cout << "based constructed" << endl;
		}
		virtual ~Based()
		{
			cout << "Based disconstructed" << endl;
		}
		virtual void Show()
		{
			cout << "n=" <<n<<endl;
		}
};
class Derived:public Based
{	
	private:
		int m;
	public:
		Derived(int x, int y) :Based(x), m(y)
		{
			cout << "Derived constructed" << endl;
		}
		virtual ~Derived()
		{
			cout << "Derived disconstructed" << endl;
		}
		virtual void Show()
		{
			Based::Show();
			cout << "m=" << m << endl;
		}
};

通过将同名成员函数声明为虚函数,实现了基类引用或者指针调用派生类的方法(当然基类引用和指针指向的是派生类对象),这就是多态,也叫做动态联编。多态机制可以简单地概括为“一个接口,多种方法”。

小结:如果没有使用virtual关键字,程序将根据引用类型或者指针类型选择方法;如果使用了virtual,程序将根据引用或者指针指向的对象类型选择方法。

Based based = Based(2);
Derived derived = Derived(1, 2);
Based&r1 = based;
Based&r2 = derived; 
r1.Show();//Based::Show()
r2.Show();//Derived ::Show()
cout << sizeof(based) << endl;//8
cout << sizeof(derived) << endl;//12

关于虚函数表:
(前提:无虚函数继承,但有虚函数的情况)
sizeof(Based)=整型变量n(4字节)+一个虚表指针(4字节)=8字节。
sizeof(Derived)=整型变量m(4字节)+一个虚表指针(4字节)+类Based中的整型变量n(4字节)=12字节

需要注意的一点是:派生类无法直接访问基类的数据成员,我们希望在派生类的显示函数show()中访问基类的数据成员,使用到了作用域解析符,由于派生类的显示函数show()除了要显示新添的数据成员还要实现基类的数据成员,因此直接调用基类的显示函数:

virtual void Show()
{
	Based::Show();
	cout << "m=" << m << endl;
}

虚析构函数:

基类声明了一个虚的析构函数,这样是为了确保释放派生类对象时,按正确的顺序调用析构函数。
派生类对象析构的顺序是,先析构派生类对象,在析构基类对象,和构造的时候的顺序相反。

如果析构函数不是虚的,则将调用对应于指针类型的析构函数。如果析构函数是虚的,将调用相应的对象类型的析构函数(即如果指针指向的是派生类的对象,将调用派生类的析构函数,然后自动调用基类的析构函数)。

总结:
如果派生类中重新定义了基类的方法,通常将基类的方法声明为虚的。这样,程序根据对象类型而不是引用或者指针的类型选择方法。为基类声明一个虚析构函数是一种惯例。

猜你喜欢

转载自blog.csdn.net/qq_29689907/article/details/84558049