I 动态绑定、多态、虚函数、对象的静态类型与动态类型

  1、基类中有两种函数:

    • 派生类直接继承不做改变
    • 派生类重新定义成适合自身的版本覆盖掉基类的函数

  对于第一种就是普通的基类成员函数,第二种通常通过将该函数定义为虚函数来实现。

  

  2、对于基类对象的引用或指针,由于继承关系会有两种不同的类型:

    • 静态类型:指针或引用定义时的类型
    • 动态类型:指针或引用实际指向的对象类型

  对于对象的引用或者指针,可以实现从派生类向基类的类型转换,以下代码是合法的:

class Base{
virtual void print(){...}
}; class Derive:public Base{
void print(){...}
}; Derive d;
Base &b1 = d;
Base *b2 = &d;

  但是不存在从基类到派生类的类型转换,因为派生类都包含基类部分,说白了,就是基类有的派生类都有,派生类基类不一定有,可以说苹果是水果,不能说水果是苹果嘛。

  如果表达式既不是指针也不是引用时,则它的动态类型永远与静态类型一致,也不存在基类和派生类之间的类型转换。

  

  3、在使用基类的引用或者指针调用一个虚函数的时候,由于基类的引用或者指针的动态类型可能和静态类型不一样,在编译时并不能确定它调用的虚函数是基类的还是派生类的,直到运行时才能根据其动态类型来确定其虚函数版本的现象,被称作动态绑定,也叫运行时绑定。举个例子,一个基类Person,两个派生类Professor和student都继承自Person:

class Person {
public:
Person() = default;
Person(string s, int a) : name(s), age(a){} string name;
int age; virtual void getdata(){
cin >> name;
cin >> age;
}
virtual void putdata(){
cout << name << " " << age << endl;
}
}; class Professor :public Person{
public:
Professor() = default;
Professor(string s, int a) : Person(s, a){} int publications;
int cur_{ id }; static const int id = ; void getdata(){
cin >> name;
cin >> age;
cin >> publications;
} void putdata(){
cout << name << " " << age << " " << publications << " " << id << endl;
}
}; class Student :public Person{
public:
int marks[];
int cur_{ id }; static const int id = ; void getdata(){
cin >> name;
cin >> age;
for (int i = ; i < ; i++){
cin >> marks[i];
}
} void putdata(){
int sum = marks[] + marks[] + marks[] + marks[] + marks[] + marks[];
cout << name << " " << age << " " << sum << " " << id << endl;
}
}; int _tmain(int argc, _TCHAR* argv[])
{ //=====================================================================
// 静态类型为基类,动态类型为派生类
// 基类指针调用虚函数,发生动态绑定,按照其动态类型进行调用
// 如果基类没有定义虚函数,则调用基类函数
//======================================================================
int val;
Person *per[]; //基类指针数组
for (int i = ; i < ; i++){
cin >> val;
if (val == ){
per[i] = new Professor; //第一个元素动态类型是professor
}
else per[i] = new Student; // 第二个元素动态类型是student per[i]->getdata(); //第一次调用的是professor::getdata,第二次调用的是student::getdata
}
for (int i = ; i<; i++)
per[i]->putdata(); //第一次调用的是professor::putdata,第二次调用的是student::putdata //============================================================================
return ;
}

  4、当基类派生类有同名函数,但是基类中该函数不是虚函数(没有virtual),则不能实现覆盖,派生类直接继承基类函数,当基类引用或者指针调用函数时,不会发生动态绑定,调用的是基类函数。派生类和基类对象调用函数时,调用各自版本的函数


II 虚函数表,参考学习:http://blog.csdn.net/haoel/article/details/1948051

typedef void(*Fun)(void);

class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
}; int _tmain(int argc, _TCHAR* argv[])
{
Base b; cout << (int*)(&b) << endl; //获取虚函数表地址
cout << (int)*(int*)(&b) << endl; //虚函数表第一个函数地址 Fun pFunB = (Fun)*((int*)*(int*)(&b));
Fun pFunB1 = (Fun)*((int*)*(int*)(&b) + );
Fun pFunB2 = (Fun)*((int*)*(int*)(&b) + );
pFunB(); //Base::f
pFunB1(); //Base::g
pFunB2(); //Base::h return ;
}

1、无虚函数覆盖,派生类直接继承基类的虚函数,虚函数表访问的仍然是基类的虚函数

Base::f Base::g Base::h
class Derive :public Base{
public:
void f1() { cout << "Derive::f" << endl; }
void g1() { cout << "Derive::g" << endl; }
void h1() { cout << "Derive::h" << endl; }
}; int _tmain(int argc, _TCHAR* argv[])
{
Derive d;
Fun pFunD = (Fun)*((int*)*(int*)(&d));
Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + );
Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + );
pFunD(); //Base::f
pFunD1(); //Base::g
pFunD2();   //Base::h return ;
}

2、无虚函数覆盖,但是派生类有自己的虚函数,则派生类的虚函数在虚函数表中按照定义顺序排在基类虚函数后面

Base::f Base::g Base::h Derive::f Derive::g Derive::h
class Derive :public Base{
public:
virtual void f1() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g" << endl; }
virtual void h1() { cout << "Derive::h" << endl; }
}; int _tmain(int argc, _TCHAR* argv[])
{
Derive d;
cout << (int*)(&d) << endl; //获取虚函数表地址
cout << (int)*(int*)(&d) << endl; //虚函数表第一个函数地址 Fun pFunD = (Fun)*((int*)*(int*)(&d));
Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + );
Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + );
pFunD(); //Base::f
pFunD1(); //Base::g
pFunD2(); //Base::h Fun pFunD3 = (Fun)*((int*)*(int*)(&d)+);
Fun pFunD4 = (Fun)*((int*)*(int*)(&d) +);
Fun pFunD5 = (Fun)*((int*)*(int*)(&d) + );
pFunD3(); //Derive::f
pFunD4(); //Derive::g
pFunD5(); //Derive::h system("pause");
return ;
}

3、派生类中有虚函数覆盖了基类的虚函数(如例中f),则在虚函数表中,派生类的虚函数放在了父类虚函数的位置,没被覆盖的保持原样

Derive::f Base::g Base::h Derive::g Derive::h
class Derive :public Base{
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g" << endl; }
virtual void h1() { cout << "Derive::h" << endl; }
}; int _tmain(int argc, _TCHAR* argv[])
{
Derive d; Fun pFunD = (Fun)*((int*)*(int*)(&d));
Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + );
Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + );
pFunD(); //Derive::f
pFunD1(); //Base::g
pFunD2(); //Base::h Fun pFunD3 = (Fun)*((int*)*(int*)(&d)+);
Fun pFunD4 = (Fun)*((int*)*(int*)(&d) + );
pFunD3(); //Derive::g
pFunD4(); //Derive::h return ;
}

4、多重继承,无虚函数覆盖:

  • 每个父类都有一张虚函数表,按照父类定义顺序排列
  • 派生类的虚函数排在第一个父类虚函数的后面
Base1::f Base1::g Base1::h Derive::f Derive::g Derive::h
Base2::f Base2::g Base2::h
Base3::f Base3::g Base3::h

5、多重继承,有虚函数覆盖:

  派生类中的虚函数放在被覆盖虚函数位置,其他不变

【C++】虚函数的更多相关文章

  1. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  2. 匹夫细说C#:从园友留言到动手实现C#虚函数机制

    前言 上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编 ...

  3. 【C++】多态性(函数重载与虚函数)

    多态性就是同一符号或名字在不同情况下具有不同解释的现象.多态性有两种表现形式: 编译时多态性:同一对象收到相同的消息却产生不同的函数调用,一般通过函数重载来实现,在编译时就实现了绑定,属于静态绑定. ...

  4. 虚函数的使用 以及虚函数与重载的关系, 空虚函数的作用,纯虚函数->抽象类,基类虚析构函数使释放对象更彻底

    为了访问公有派生类的特定成员,可以通过讲基类指针显示转换为派生类指针. 也可以将基类的非静态成员函数定义为虚函数(在函数前加上virtual) #include<iostream> usi ...

  5. C++ 系列:虚函数

    Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...

  6. EC笔记,第二部分:9.不在构造、析构函数中调用虚函数

    9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #; } 上述程序会产生什么样的输出呢? 你一定会以为会输出: cls2 make cls2 delete ...

  7. C++构造函数中不能调用虚函数

    在构造函数中调用虚函数,并不会产生多态的效果,就跟普通函数一样. c++ primer 第四版中497页15.4.5构造函数和析构中的虚函数讲到,如果在构造函数或析构函数中调用虚函数,则运行的是为构造 ...

  8. C#虚函数和接口的区别

    接口只能声明不能实现,虚函数可以. 接口:对外提供可以访问的函数叫接口.虚函数不需要被强制重写,其本身含有实现部分. 抽象类:指派了派生类必须实现的函数(纯虚函数),不然编译不通过. 虚函数的限制:  ...

  9. c++ 虚函数

    class A { public: virtual void f();//希望派生类重写 void fun();//绝大多数情况下不要重新定义基类的非虚函数,那样会打破公有继承Is-A的关系,而且行为 ...

  10. 为何JAVA虚函数(虚方法)会造成父类可以"访问"子类的假象?

      首先,来看一个简单的JAVA类,Base. 1 public class Base { 2 String str = "Base string"; 3 protected vo ...

随机推荐

  1. ios 适应屏幕

    标准做法:auto layout 其中:也可以用代码判断  1.如果不是hardcode的话,又怎么会不兼容呢..  2.如果你全写320*480也是兼容吗 3.写320嘛的就是hardcode啊.  ...

  2. 【BZOJ】1452: [JSOI2009]Count

    http://www.lydsy.com/JudgeOnline/problem.php?id=1452 题意:n×m的矩阵上每个点有个颜色,现在有q个操作:1 x y c 将点(x,y)的颜色改为c ...

  3. 深入理解JVM—性能监控工具

    (转自:http://yhjhappy234.blog.163.com/blog/static/31632832201222691738865/) 我们知道,在JVM编译期和加载器,甚至运行期已经做了 ...

  4. js控制页面的全屏展示和退出全屏显示

    <!DOCTYPE html> <html> <meta http-equiv="Content-Type" content="text/h ...

  5. 【转】【Asp.Net MVC】asp.net mvc Model验证总结及常用正则表达式

    本文属转载,来源: http://www.byywee.com/page/M0/S868/868615.html 关于Model验证官方资料: http://msdn.microsoft.com/zh ...

  6. TinyMCE在线编辑器使用方法初探

    首先,下载TinyMCE包,地址:http://www.tinymce.com/ 然后将下载后的包解压,放置到一个文件夹下,创建一个html文件,并在其中书写如下代码: <!DOCTYPE ht ...

  7. 如何把in_array 的第三个参数strict设置为 true

    var_dump(in_array(0, array('s' )); 这句话的结果是bool(true). 因为in_array会将0 和's' 进行比较,0是number类型,'s'是string类 ...

  8. HDU 1166敌兵布阵+NOJv2 1025: Hkhv love spent money(线段树单点更新区间查询)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  9. PHP正则表达式及实例

    PHP正则表达式及实例 博客分类: Php / Pear / Mysql / Node.js 正则表达式PHPWordPressFPApache  关联: 正则表达式 去除连续空白 + 获取url + ...

  10. 百度BAE环境下WordPress安装教程

    不了解代码的童鞋慎重使用这种方法哦,安装过程中可能会出现一些简单的错误. 前两天有位网友在QQ上联系我,他告诉我自己在百度BAE上安装WordPress程序总是出错.我让他按照网络上的教程逐步安装,但 ...