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. BZOJ2725 : [Violet 6]故乡的梦

    如果S==T,那么答案为0. 如果S与T不连通,那么答案为inf. 否则,S到T的最短路径上至少有一条边. 求出以S为源点的最短路图,是个DAG,随便抓一条S到T的最短路,记为P. 设dpS[x]表示 ...

  2. centos 安装网络错误

    yum install vnc-server 提示安装成功 rpm -q vnc-server 返回  package vnc-server is not installed 然而再重新安装时  yu ...

  3. Chromium源码--视频播放流程分析(拨开云雾)

    转载请注明出处: http://www.cnblogs.com/fangkm/p/3791964.html 在PC浏览器中播放视频,大部分视频网站都是采用flash播放器,这多亏了Adobe Flas ...

  4. imread() not working in OpenCV 2.4.11 Debug mode

    The OpenCV function imread() not working in OpenCV 2.4.11 Debug mode of VS2010 under Win32, the way ...

  5. 1. while循环(当循环) 2. do{}while()循环 3. switch cose(多选一) 例子:当选循环下求百鸡百钱 用 switch cose人机剪刀石头布

    1. while循环: 当选循环下求百鸡百钱:如下: 代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN ...

  6. Spark - RDD(弹性分布式数据集)

    org.apache.spark.rddRDDabstract class RDD[T] extends Serializable with Logging A Resilient Distribut ...

  7. IOS第六天(3:scrollView 图片轮播器)

    IOS第六天(3:scrollView 图片轮播器) #import "HMViewController.h" #define kImageCount 5 @interface H ...

  8. 【新产品发布】发布STM8S 核心板

    搞了一些STM8的核心板供大家把玩,先上几张图: 物品购买地址: http://item.taobao.com/item.htm?spm=686.1000925.1000774.17.5GMO5M&a ...

  9. 学习之痛(数据库->存储过程和函数)

    存储过程和函数作为数据库的一部分,为什么是学习之痛. 项目实际开发,考虑性能和代码维护,绝对不用存储过程. 如果单纯自己写个小程序糊弄人玩,还可以写写. [学习] 在数据库中定义一些SQL语句集合,然 ...

  10. Yii2 Format 如何使用

    $formatter = \Yii::$app->formatter; // output: January 1, 2014 echo $formatter->asDate('2014-0 ...