浅析C++多态----多态实现原理


目录

C++为什么要引入多态?

一.背景介绍

二.多态的实现原理

1.什么是多态?

2.多态的分类

3.多态的实现原理

4.如何证明vptr指针?

三.总结


一.背景介绍

虚函数重写:子类重新定义父类中有相同函数名,返回值和参数的虚函数

非虚函数重写:子类重新定义父类中有相同名称和参数的非虚函数

继承中的类型兼容性原则

1.子类对象可以作为父类对象使用

2.子类对象可以直接赋值给父类对象

3.子类对象可以初始化父类对象

4.父类指针可以直接指向子类对象

5.父类引用可以直接引用子类对象

当发生赋值兼容时,子类对象退化为父类对象,只能访问父类中定义的成员。

类型兼容规则是多态性的重要基础之一(子类就是特殊的父类)

#include <iostream>
 #include <string>

 using namespace std;

 class Parent
 {
public:
	    int mi;
	     Parent() : mi(0) {}
	
		     void add(int i)
		     {
		         mi += i;
		     }
	
		     void add(int a, int b)
		    {
		         mi += (a + b);
		     }
	 };

 class Child : public Parent
 {
 public:
	     int mi;
	     Child() : mi(0) {}
		 void add(int x, int y, int z)
		 {
		         mi += (x + y + z);
		 }
	 };

 int main()
 {
	     Parent p;
	     Child c;
		 c.mi = 100;
	     p = c;          // p.mi = 0; 子类对象退化为父类对象
	     Parent p1(c);   // p1.mi = 0; 同上
	     Parent & rp = c;
         Parent * pp = &c;
	
		 rp.add(5);
	     pp->add(10, 20);
	
		 cout << "p.mi: " << p.mi << endl;                    // p.mi: 0; 
	     cout << "p1.mi: " << p1.mi << endl;                  // p1.mi: 0;  
	     cout << "c.Parent::mi: " << c.Parent::mi << endl;    // c.Parent::mi: 35
	     cout << "rp.mi: " << rp.mi << endl;                  // rp.mi: 35
	     cout << "pp->mi: " << pp->mi << endl;                // pp->mi: 35
	
		 return 0;
	 }

  在面向对象的继承关系中,我们了解到子类可以拥有父类的所有属性和行为;但是,有时父类所提供的方法不能满足子类的要求,因此我们要在子类中重写父类的方法。尽管我们可以通过函数重写(非虚函数)解决这个问题,但是在类型兼容性原则当中也不能出现我们实际所期待的结果()不能够根据指针/引用所指向的实际对象类型去调用相应的重写函数),下面我们用代码去复现这个问题。

 #include <iostream>
 #include <string>

 using namespace std;

 class Parent
 {
 public:
	     void print()
		 {
		         cout << "I'm Parent." << endl;
		 }
	 };

 class Child : public Parent
 {
 public:
	     void print()
		     {
		         cout << "I'm Child." << endl;
		     }
	 };

 void how_to_print(Parent * p)
 {
	     p->print();
	 }

 int main()
 {
	     Parent p;
	     Child c;
		 how_to_print(&p);   // I'm Parent    // Expected to print: I'm Parent.
	     how_to_print(&c);   // I'm Parent    // Expected to print: I'm Child.
	
		     return 0;
	 }

  为什么上述用父类指针指向子类对象时,子类对象调用父类的成员函数呢?  这是因为在编译器编译期间,编译器只能根据指针的类型去判断所指向的对象;根据赋值兼容,编译器认为父类指针指向的是父类的对象,所以编译结果可能是调用父类中定义的同名函数。

  

 在调用这个函数时,编译器不可能知道指针究竟指向了什么。但是编译器没有理由报错,于是编译器认为最安全的做法就是调用父类中的print()函数。

要解决这个问题,就需要用到C++中的多态。那么如何实现C++的多态呢?

二.多态的实现原理

1.什么是多态?

概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。 简单的说:就是用基类的引用指向子类的对象。

2.多态的分类

  在编译器把函数或模板连接生产执行代码的过程中,有两种联编方式,一种是静态联编,另外一种是动态联编。
  静态联编是在编译阶段就把函数连接起来,就可以确定调用哪个函数或者模板,而动态联编是指在程序运行时才能确定函数和实现的连接,才能确定调用哪个函数。

  由编译器的联编方式,我们可以把多态分为静态多态和动态多态

 #include <iostream>
 #include <string>

 using namespace std;

 class Parent
 {
 public:
	         virtual void func()
		     {
		         cout << "Parent::void func()" << endl;
		     }
	
		    virtual void func(int i)
		     {
		        cout << "Parent::void func(int i) : " << i << endl;
		    }
	
		     virtual void func(int i, int j)
		    {
		        cout << "Parent::void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
		     }
 };

 class Child : public Parent
 {
 public:
	     void func(int i, int j)
		    {
	         cout << "Child::void func(int i, int j) : " << i + j << endl;
		    }
	
		     void func(int i, int j, int k)
		    {
		         cout << "Child::void func(int i, int j, int k) : " << i + j + k << endl;
	     }
	 };

 void run(Parent * p)
 {
	    p->func(1, 2);     // 展现多态的特性
	                       // 动态联编
 }


int main()
 {
	    Parent p;
		p.func();         // 静态联编
	    p.func(1);        // 静态联编
	    p.func(1, 2);     // 静态联编
	
		cout << endl;
	
		Child c;
	
		c.func(1, 2);     // 静态联编

		cout << endl;
	
		run(&p);
	    run(&c);
	
		return 0;
	 }
 

3.多态的实现原理

虚函数表与vptr指针

  1. 当类中声明虚函数时,编译器会在类中生成一个虚函数表;

  2. 虚函数表是一个存储类成员函数指针的数据结构;

  3. 虚函数表是由编译器自动生成与维护的;

  4. virtual成员函数会被编译器放入虚函数表中;

  5. 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。

  多态执行过程:

  1.  在类中,用 virtual 声明一个函数时,就会在这个类中对应产生一张 虚函数表,将虚函数存放到该表中;

       2.  用这个类创建对象时,就会产生一个 vptr指针,这个vptr指针会指向对应的虚函数表;

       3.  在多态调用时, vptr指针 就会根据这个对象 在对应类的虚函数表中 查找被调用的函数,从而找到函数的入口地址;

             如果这个对象是 子类的对象,那么vptr指针就会在 子类的 虚函数表中查找被调用的函数

             如果这个对象是 父类的对象,那么vptr指针就会在 父类的 虚函数表中查找被调用的函数

 

   如何证明vptr指针?

  由类的大小==类中定义成员变量的大小。在有虚函数的类中,类的大小 == 成员变量的大小 + vptr指针大小。


#include <iostream>
 #include <string>

 using namespace std;

 class Demo1
 {
 private:
	     int mi; // 4 bytes
	     int mj; // 4 bytes
 public:
	    virtual void print() {}  // 由于虚函数的存在,在实例化类对象时,就会产生1个 vptr指针
	 };

 class Demo2
 {
 private:
	     int mi; // 4 bytes
	     int mj; // 4 bytes
 public:
	     void print() {}
	 };

 int main()
 {
	     cout << "sizeof(Demo1) = " << sizeof(Demo1) << " bytes" << endl; // sizeof(Demo1) = 16 bytes
	     cout << "sizeof(Demo2) = " << sizeof(Demo2) << " bytes" << endl; // sizeof(Demo2) = 8 bytes
	
		    return 0;
	 }

 // 64bit(OS) 指针占 8 bytes
 // 32bit(OS) 指针占 4 bytes

三.总结

  多态实现面向对象重要的特性之一,掌握多态的具体实现原理,以及为什么引入多态,可以为我们日后使用多态提供基础。

  本文参考:c++中的多态机制 - PRO_Z - 博客园


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值