C++:虚函数、虚表和纯虚函数

1.虚函数

定义:

用virtual修饰的成员函数称为虚函数;

重写(覆盖):

当在子类中定义了一个与父类完全相同的虚函数时,则称这个子类的函数重写(或覆盖)了父类的函数;

例:

#include<iostream>
using namespace std;

class Person
{
public:
	virtual void BuyTicket()
	{
		cout<<"Price:全票"<<endl;
	}
};

class Student
{
public:
	virtual void BuyTicket()
	{
		cout<<"Price:半票"<<endl;
	}
};

int main()
{
	Person p;
	Student s;
	cout<<"Person:"<<endl;//成人买全票
	p.BuyTicket();
	cout<<"Student:"<<endl;//学生买半票
	s.BuyTicket();
	return 0;
}

总结:

  • 完全相同指函数名参数列表返回值相同;
  • 存在一种特殊情况:协变,子类虚函数父类虚函数返回值分别为子类指针父类指针;
  • 只有类的成员函数才能定义为虚函数;
  • 静态成员函数不能定义为虚函数,因为static成员函数不属于任何对象;
  • 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual;
  • 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但最好不要这样做,因为使用时容易引起混淆;
  • 内联函数不能为虚函数,如果内联函数被virtual修饰,计算机会忽视inline将它变成纯粹的虚函数;
  • 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为;
  • 最好把基类的析构函数定义为虚函数;(如果没有定义为虚函数,当基类指针指向派生类,并且删除指针时,会析构基类而不会析构派生类,造成内存泄漏)

2.虚表

定义:

存放虚函数地址的表

例:

#include<iostream>
using namespace std;

typedef void (*Fun)();

class Base
{
public:
	virtual void a()
	{
		cout<<"a()"<<endl;
	}
	virtual void b()
	{
		cout<<"b()"<<endl;
	}
	virtual void c()
	{
		cout<<"c()"<<endl;
	}
	int _i;
};

int main()
{
	Base b;
	b._i = 1;
	Fun pFun = NULL;

	cout<<sizeof(b)<<endl;

	cout<<"虚表指针:"<<(int*)(&b)<<endl;

	cout<<"a()地址:"<<(int*)*(int*)(&b)<<endl;//打印a()地址
	pFun = (Fun)*((int*)*(int*)(&b));
	pFun();

	cout<<"b()地址:"<<(int*)*(int*)(&b)+1<<endl;//打印b()地址
	pFun = (Fun)*((int*)*(int*)(&b)+1);
	pFun();

	cout<<"c()地址:"<<(int*)*(int*)(&b)+2<<endl;//打印c()地址
	pFun = (Fun)*((int*)*(int*)(&b)+2);
	pFun();

	return 0;
}

运行结果(vs2008):


在内存中查看:


b对象模型结构:


3.虚表在继承中的情况

(1)一般继承,无虚函数覆盖

代码:

class Base
{
public:
	virtual void f1()
	{
	}
	virtual void h1()
	{
	}
	int _b;
};
class Derive:public Base
{
public:
	virtual void f2()
	{
	}
	virtual void h2()
	{
	}
	int _d;
};

实例对象Base b;Derive d;模型:


(2)一般继承,有虚函数覆盖

代码:

class Base
{
public:
	virtual void f()
	{
	}
	virtual void h1()
	{
	}
	int _b;
};
class Derive:public Base
{
public:
	virtual void f()
	{
	}
	virtual void h2()
	{
	}
	int _d;
};

实例对象Base b;Derive d;模型:


(3)多继承,无虚函数覆盖

代码:

class Base1
{
public:
	virtual void f1()
	{
	}
	int _b1;
};

class Base2
{
public:
	virtual void g2()
	{
	}
	int _b2;
};

class Derive:public Base1,public Base2
{
public:
	virtual void h3()
	{
	}
	int _d;
};

实例对象Base1 b1;Base2 b2;Derive d;模型:


(4)多继承,有虚函数覆盖

代码:

class Base1
{
public:
	virtual void f1()
	{
	}
	virtual void g1()
	{
	}
	int _b1;
};

class Base2
{
public:
	virtual void f2()
	{
	}
	virtual void g2()
	{
	}
	int _b2;
};

class Derive:public Base1,public Base2
{
public:
	virtual void f1()//覆盖Base1的函数f1()
	{
	}
	virtual void g2()//覆盖Base2的函数g2()
	{
	}
	int _d;
};

实例对象Base1 b1;Base2 b2;Derive d;模型:


(5)总结

1.单继承

子类和父类有各自的虚表,子类虚表拷贝父类虚表中的内容,并会更新构成覆盖的函数地址;

2.多继承

子类中包含多个虚表(取决于继承的个数),各自拷贝父类虚表中的内容,并更新构成覆盖的函数地址,对于子类中没有构成覆盖的虚函数,将其地址添加到最先继承类的虚表中;

4.纯虚函数

定义:

在虚函数后面赋值0;

例:

class A
{
   virtual void func() = 0; // 纯虚函数
protected :
   string _a ;
};
class B : public A
{};
注意: 
(1)纯虚函数没有函数体; 
(2)最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是虚函数”; 
(3)这是一个声明语句最后有分号; 
(4)纯虚函数只有函数的名字而不具备函数的功能不能被调用。 
(5)纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对他进行定义。如果在基类中没有保留函数名字,则无法实现多态性。 
(6)如果在一个类中声明了纯虚函数,在其派生类中没有对其函数进行定义,则该虚函数在派生类中仍然为纯虚函数。

5.抽象类(接口类)

定义:

含有纯虚函数的类称为抽象类(接口类),抽象类不能实例化出对象

注意:

(1)凡是包含纯虚函数的类都是抽象类;

(2)抽象类不能实例化出对象;

(3)纯虚函数在派生类中重新定义以后,派生类才能实例化出对象;

抽象类/接口类的作用:

抽象类的存在,使得子类必须重写虚函数才能实例化出对象;

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值