C++多态: 抽象类 多态的原理 虚函数表 动态绑定和静态绑定

一.抽象类

如果在虚函数的后面加上=0,并且不进行实现,这样的虚函数就叫做纯虚函数.

而包含纯虚函数的类,也叫做抽象类或者接口类.

抽象类不能实例化出对象,因为它具有的信息不足以描述一个对象,派生类继承后也只有在重写纯虚函数后才能实例化出对象.

抽象类就像是一个蓝图,为派生类描述好一个大概的架构,派生类必须实现完这些架构,至于要在这些架构上做些什么,增加什么,都是派生类自己的问题.

class preson {
	virtual void print() = 0;
};

class student :public preson {
	virtual void print() {
		cout << "I am a student" << endl;
	}
};

class teacher :public preson {
	virtual void print() {
		cout << "I am a teacher" << endl;
	}
}

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现.

虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口.所以如果不实现多态,不要把函数定义为虚函数.

二.多态的原理

虚函数表

class preson {
public:
	virtual void print() {
		cout << "preson" << endl;
	}
	virtual void test1() {
		cout << "1test1" << endl;
	}
	void test2() {
		cout << "1test1" << endl;
	}
	int _age;
};

class student :public preson{
public:
	virtual void print() {
		cout << "student" << endl;
	}
	void test() {
		cout << "2test2" << endl;
	}
	int _stuNum;
};

int main() {
	preson p;
	cout << sizeof(p) << endl;
	return 0;
}

查看对象p的大小后发现为8个字节,和实际想象的只有一个变量_age大小应该为4字节不一致.

由上图可以看到对象p里面处理变量_age以外,还有个指针_vfptr.这个指针指向了一个函数指针数组,这个函数指针数组也就是虚函数表,其中的每一个成员指向的都是虚函数,这个_vfptr被称为虚函数表指针.

多态的实现也正是借助这个虚函数表

观察上面的虚函数表可以得知,如果派生类实现了某个虚函数的重写,那么在派生类的虚函数表中,重写的虚函数就会覆盖掉原有的虚函数,如:preson::print被替换为student::print.而没有完成重写的preson::test1则依旧保留在子类的虚函数表中.

总结: 派生类会继承基类的虚函数表,如果派生类完成了重写,则会将重写的虚函数覆盖掉原有的函数.所以指针或引用指向哪一个对象,就要调用对象中虚函数表中对应位置的虚函数,来实现多态.

为什么必须要通过指针或者引用才能构成多态?

如果将派生类对象赋值给基类对象,会因为对象分割,导致它的内存布局整个被修改,完全转换为基类对象的类型,虚函数表也与基类相同,所以不能实现多态.

而如果使用基类指针或者引用指向派生类对象,虽然指向的是派生类对象,但是它们的内存布局是兼容的,它不会像赋值一样改变派生类对象的内存结构,所以派生类对象的虚函数表得到了保留,所以它可以通过访问派生类对象的虚函数表来实现多态.

单继承中: 派生类虚函数表的生成过程:

  1. 首先派生类会将基类的虚函数表拷贝过来
  2. 如果派生类完成了对虚函数的重写,则用重写后的虚函数覆盖掉虚函数表中继承下来的基类虚函数
  3. 如果派生类自己又新增了虚函数,则添加到虚函数表的最后面

多继承中: 派生类虚函数表的生成过程:

  1.  首先派生类会将所有基类的虚函数表拷贝过来
  2. 如果派生类完成了对虚函数的重写,则用重写后的虚函数覆盖对应的基类虚函数
  3. 如果派生类自己又新增了虚函数,则添加到第一个基类拷贝过来的虚函数表后面

 虚函数表的存储位置

虚函数存在于虚函数表中,虚函数表又存储在哪里呢?

虚函数表指针存在于对象当中,虚函数存在于虚函数表中,虚函数表存在于代码段(编译阶段生成)

动态绑定和静态绑定

对象的静态类型: 对象在声明时采用的类型.是在编译期确定的.

对象的动态类型: 目前所指对象的类型,是在运行期决定的.对象的动态类型可以更改,但是静态类型无法更改.

静态绑定: 绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发送在编译期.

动态绑定: 绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发送在运行期.

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态.比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称之为多态

 常见问题解答:

  • 内联函数可以是虚函数吗?

        不可以,内联函数没有地址,无法放入到虚函数表中.

  • 静态成员函数可以是虚函数吗?

        不可以,静态成员函数没有this指针,无法访问虚函数表.

  • 构造函数可以是虚函数吗?

        不可以,虚函数表指针也是对象的成员之一,是在构造函数初始化列表初始化时才生产的,不可能是虚函数.

  • 析构函数可以是虚函数吗?

        可以,最好将基类的析构函数声明为虚函数,防止使用基类指针或者引用指向派生类对象时,派生类的析构函数没有调用,可能会导致内存泄漏.

  • 对象访问虚函数快还是普通函数快?

        如果不构成多态,虚函数和普通函数的访问是一样快的.但是如果构成多态,调用虚函数就得到虚函数表中查找,就会导致速度变慢,所以普通函数更快一点.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值