C++多态

1.多态的概念
当不同的对象去完成某个行为时,会产生出不同的状态;通俗来说就是多种形态。
eg:买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

2.多态的定义及实现
2.1 多态定义的构成条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
继承中构成多态的两个条件:
1.调用函数的对象必须是指针或者引用。
2.被调用的函数必须是虚函数,且完成了虚函数的重写。在这里插入图片描述
虚函数:指在类的成员函数的前面加virtual关键字

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

虚函数的重写:派生类中有一个跟基类的完全相同的虚函数,我们就称子类的虚函数重写了基类的虚函数。其中,完全相同是指:函数名,参数,返回值都相同。另外,虚函数的重写也叫做虚函数的覆盖。

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

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

void Func(Person & people)
{
	people.BuyTicket();
}

void Test()
{
	Person per;
	Student st;

	Func(per);
	Func(st);

	return 0;
}

虚函数重写例外:协变(了解!用于选择题辨析)
协变:重写的虚函数的返回值可以不同,但是必须分别是基类指针和派生类指针或者基类引用和派生类引用。

class A{};

class B : public A {};
 
class Person { 
public:
    virtual A* f() {return new A;}
   };

class Student : public Person{
public:
    virtual B* f(){return new B;}
};

提示:在派生类中重写的成员函数可以不加Virtual关键字,也能构成重写。因为继承后基类的虚函数被继承下来了,在派生类依旧保持虚函数的属性,我们只是重写了它。(但这种行为非常不规范,不建议平时这样使用。)

class Person { 
public: 
   virtual void BuyTicket() {cout << "买票-全价" << endl;}
  };
 
class Student : public Person {
 public: 
   void BuyTicket() {cout << "买票-半价" << endl;}
  };

注意:基类的析构函数最好写成虚函数。因为,如果基类中的析构函数是虚函数,那么派生类的析构函数就重写了基类的析构函数。虽然他们的函数名不相同,看起来违背了重写的规则,其实可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

class Person { 
public: 
   virtual ~Person() {cout << "~Person()" << endl;} 
 };
 
class Student : public Person {
 public:    
    virtual ~Student() { cout << "~Student()" << endl; } 
 };
 
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多 态,才能保证p1和p2指向的对象正确的调用析构函数。
int main() {    
Person* p1 = new Person;    
Person* p2 = new Student;

 delete p1;    
 delete p2;
 
 return 0; 
 }

普通函数与类型有关(实现继承:派生类继承了基类函数,可以使用函数,继承的是函数的实现。)
多态调用与对象有关(接口继承:派生类继承的是基类函数的接口,目的是为了重写,达成多态,继承的是接口)虚函数的继承也是一种接口继承,所以,如果不实现多态,不能把函数定义为虚函数。

2.2 重载,覆盖(重写),隐藏(重定义)的对比

重载:两个函数在同一作用域;函数名/参数相同。
重写(覆盖):两个函数分别在基类和派生类的作用域;
函数名/参数/返回值都必须相同(协变例外);
两个函数必须是虚函数。
重定义(隐藏):两个函数分别在基类和派生类的作用域;
函数名相同;两个基类和派生类的同名函数不构成重写就是重定义。

3.抽象类
***在虚函数的后面写上 =0 ,则这个函数为纯虚函数。***包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
***只有重写纯虚函数,派生类继承后才能实例化出对象。***纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

4.C++11中的override和final
虚函数的意义是实现多态,如果没有重写,虚函数就没有意义。所以,C++11提供override 和 final 来修饰虚函数。
建议使用纯虚函数+override(检查是否构成重写函数)的方式来强制重写虚函数。

// 1.final 修饰基类的虚函数不能被派生类重写 
class Car 
{ 
public:    
	virtual void Drive() final {} 
};

class Benz :public Car 
{ 
public:    
	virtual void Drive() { cout << "Benz-舒适" << endl; }
}
class Car{ 
public:    
	virtual void Drive(){} 
}; 
// 2.override 修饰派生类虚函数强制完成重写,如果没有重写会编译报错 
class Benz :public Car { public:   
	virtual void Drive() override {cout << "Benz-舒适" << endl;} 
};

5.多态的原理
5.1虚函数表与多态的原理
虚函数表本质是一个存虚函数指针的指针数组。
虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。
派生类的虚表生成步骤:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基 类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在 派生类中的声明次序增加到派生类虚表的最后。
满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中取的。不满足多态的函数调用时编译时确认好的。

5.2 动态绑定与静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数 重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用 具体的函数,也称为动态多态。
  3. 本小节之前(5.2小节)买票的汇编代码很好的解释了什么是静态(编译器)绑定和动态(运行时)绑定。

6.多态的常见面试题

  1. 什么是多态?答:当不同的对象去完成某个行为时,会产生出不同的状态。
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?答:参考上面的内容
  3. 多态的实现原理?答:虚函数表
  4. inline函数可以是虚函数吗?答:不能,因为inline函数没有地址,无法把地址放到虚函数表中。
  5. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。
  6. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始 化的。
  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义 成虚函数。具体参考上面的(2.多态的定义)。
  8. 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是 引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
  9. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数是在编译阶段就生成的,一般情况下存在静态区 的。
  10. C++菱形继承的问题?虚继承的原理?答:参考继承。注意这里不要把虚函数表和虚基表搞混了
  11. 什么是抽象类?抽象类的作用?答:参考(3.抽象类)。抽象类强制重写了虚函数,另外抽象类体现出 了接口继承关系。

封装–>管理
继承–>复用
多态–>灵活性

**声明:**本次博客中的实例是以vs2013下的x86程序中,涉及的指针都是4bytes。如果在其他 平台下,部分代码可能需要改动。例如,在x64程序中,则需要考虑指针是8bytes问题等等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值