博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[022]c++虚函数、多态性与虚表
阅读量:5170 次
发布时间:2019-06-13

本文共 3987 字,大约阅读时间需要 13 分钟。

原文出处:http://my.oschina.net/hnuweiwei/blog/280894

目录[-]

多态

多态性就是指同样的消息被类的不同的对象接收时导致的完全不同的行为的一种现象。这里所说的消息即对类成员函数的调用。多态实质是一个函数名称的多种形态。

C++支持两种不同类型的多态:一种是编译时的多态,另一种是运行时的多态。在编译时的多态是通过静态联编实现的;而在运行时的多态则是通过动态联编实现的。

函数联编:对一个函数的调用,在编译或运行时确定将其连接到相应的函数体的代码,实质是把一个标示名与一个存储地址联系在一起的过程。

在程序中可以把一个公有派生类对象当作其基类对象来处理,一个公有派生类的对象提供了基类对象的所有行为。

虚函数

C++中的动态联编是通过虚函数实现的,虚函数必须存在于继承的环境下。一个派生类用基类的指针标示,派生类和基类中有相同的函数名。如果将这个函数声明为虚函数,则用基类的指针调用这个函数,会表现出派生类的特征。

class 类名

{……

 virtual 类型 函数名(参数表);

 ……

};

当一个类的成员函数说明为虚函数后,就可以在该类的(直接或间接)派生类中定义与其基类虚函数原型相同的函数。这时,当用基类指针指向这些派生类对象时,系统会自动用派生类中的同名函数来代替基类中的虚函数。也就是说,当用基类指针指向不同派生类对象时,系统会在程序运行中根据所指向对象的不同,自动选择适当的函数,从而实现了运行时的多态性。

 

虚函数可以在一个或多个派生类中被重新定义,因此,属于函数重载的情况,但这种重载与一般的函数重载是不同的,要求在派生类中重新定义时,必须与基类中的函数原型完全相同,包括函数名、返回类型、参数个数和参数类型的顺序。这时无论在派生类的相应成员函数前是否加上关键字virtual,都将视其为虚函数,如果函数原型不同,只是函数名相同,C++将视其为一般的函数重载,而不是虚函数。只有类的成员函数才能声明为虚函数,全局函数及静态成员函数不能声明为虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using 
namespace 
std;
 
class 
Base
      
public
      
virtual 
void 
show() { cout<<
"base class \n"
; }   
};
 
class 
Der1: 
public 
Base
      
public
:
      
void 
show() { cout<<
"derived class 1 \n"
; }   
};
 
class 
Der2: 
public 
Base
      
public
      
void 
show() { cout<<
"derived class 2 \n"
; }  
};
 
int 
main()
{  
    
Base bobj;
    
Base *p;
    
Der1 dobj1;
    
Der2 dobj2;
    
p=&bobj; 
    
p->show();
    
p=&dobj1;
    
p->show();
    
p=&dobj2;
    
p->show();
    
system
(
"pause"
);
    
return 
0;
 
}

 

output:

base class

derived class 1

derived class 2

纯虚函数

纯虚函数是在基类中只声明虚函数而不给出具体的函数定义体,将它的具体定义放在各派生类中,称此虚函数为纯虚函数.通过该基类的指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现.

纯虚函数的声明如下:(注:要放在基类的定义体中)

   virtual 函数原型=0;

声明了纯虚函数的类,称为抽象类。

  • 抽象类中可以有多个纯虚函数

  • 不能声明抽象类的对象,但可以声明指向抽象类的指针变量和引用变量

  • 抽象类也可以定义其他非纯虚函数

  • 如果派生类中没有重新定义基类中的纯虚函数,则在派生类中必须再将该虚函数声明为纯虚函数

  • 从抽象类可以派生出具体或抽象类,但不能从具体类派生出抽象类

  • 在一个复杂的类继承结构中,越上层的类抽象程度越高,有时甚至无法给出某些成员函数的实现,显然,抽象类是一种特殊的类,它一般处于类继承结构的较外层

  • 引入抽象类的目的,主要是为了能将相关类组织在一个类继承结构中,并通过抽象类来为这些相关类提供统一的操作接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using 
namespace 
std;
 
class 
Shape
   
protected
:
      
double 
x,y;
   
public
:
      
void 
set(
double 
i, 
double 
j) { x=i; y=j;  }
      
virtual 
void 
area()=0;    
//声明纯虚函数  
};
 
class 
Triangle: 
public 
Shape
      
public
:
        
void 
area() {cout<< 
"三角形面积: " 
<<0.5*x*y<<endl;}  
};
 
class 
Rectangle: 
public 
Shape
public
:
        
void 
area(){ cout<<
"矩形面积:" 
<<x*y<<endl; }  
};
 
int 
main()
  
     
Shape *p;
     
Triangle t;
     
Rectangle r;
     
p=&t;
     
p->set(5.1,10);
     
p->area();   
     
p=&r;
     
p->set(5.1,10);
     
p->area();
     
system
(
"pause"
);  
     
return 
0;
}

 

output

三角形面积: 25.5

矩形面积:51

虚表

出自:  作者:

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class 
Base {
     
public
:
            
virtual 
void 
f() { cout << 
"Base::f" 
<< endl; }
            
virtual 
void 
g() { cout << 
"Base::g" 
<< endl; }
            
virtual 
void 
h() { cout << 
"Base::h" 
<< endl; }
  
};
  
typedef 
void
(*Fun)(
void
); 
  
Base b;
  
  
Fun pFun = NULL;
  
  
cout << 
"虚函数表地址:" 
<< (
int
*)(&b) << endl;
  
cout << 
"虚函数表 — 第一个函数地址:" 
<< (
int
*)*(
int
*)(&b) << endl;
  
  
// Invoke the first virtual function 
  
pFun = (Fun)*((
int
*)*(
int
*)(&b));
  
pFun();
   
  
(Fun)*((
int
*)*(
int
*)(&b)+0);  
// Base::f()
  
(Fun)*((
int
*)*(
int
*)(&b)+1);  
// Base::g()
  
(Fun)*((
int
*)*(
int
*)(&b)+2);  
// Base::h()

 

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

对于实例:Derive d; 的虚函数表如下

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

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

对于派生类的实例,其虚函数表为

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

多重继承(无虚函数覆盖)

对于子类实例中的虚函数表为

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

多重继承(有虚函数覆盖)

子类虚函数结构

1
2
3
4
5
6
7
8
9
10
11
 
Derive d;
            
Base1 *b1 = &d;
            
Base2 *b2 = &d;
            
Base3 *b3 = &d;
            
b1->f(); 
//Derive::f()
            
b2->f(); 
//Derive::f()
            
b3->f(); 
//Derive::f()
  
            
b1->g(); 
//Base1::g()
            
b2->g(); 
//Base2::g()
            
b3->g(); 
//Base3::g()

转载于:https://www.cnblogs.com/hustcser/p/4364736.html

你可能感兴趣的文章
计算机改名导致数据库链接的诡异问题
查看>>
Java8内存模型—永久代(PermGen)和元空间(Metaspace)(转)
查看>>
ObjectiveC基础教程(第2版)
查看>>
centos 引导盘
查看>>
Notes of Daily Scrum Meeting(12.8)
查看>>
Apriori算法
查看>>
onlevelwasloaded的调用时机
查看>>
lr_start_transaction/lr_end_transaction事物组合
查看>>
CodeIgniter学习笔记(四)——CI超级对象中的load装载器
查看>>
.NET CLR基本术语
查看>>
ubuntu的home目录下,Desktop等目录消失不见
查看>>
建立,查询二叉树 hdu 5444
查看>>
[Spring框架]Spring 事务管理基础入门总结.
查看>>
2017.3.24上午
查看>>
Python-常用模块及简单的案列
查看>>
LeetCode 159. Longest Substring with At Most Two Distinct Characters
查看>>
基本算法概论
查看>>
jquery动态移除/增加onclick属性详解
查看>>
JavaScript---Promise
查看>>
暖暖的感动
查看>>