先上概念,C++的多态性:系统在运行时根据对象类型,来确定调用哪个重载的成员函数的能力。

多态性是通过虚函数实现的。成员函数之前加了virtual,即成为虚函数。

有虚成员函数的类,编译器在其每个对象的开始处自动加一个指针,称为虚表指针,因为它指向一个表,称为虚函数表,表的元素是函数指针,指向该类的虚成员函数代码块。

该类的所有对象共享一张表。关于虚表指针和虚函数表的具体信息,可以参考皓叔的  虚函数表解析

虚函数的定义要遵循以下规则:

1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数或者返回类型不同,那么即使加上了virtual关键字,也不会实现多态的。
【此时基类/派生类对象只能直接访问各自定义的函数,虽然派生类对象的虚表里有基类的虚函数指针,但是派生类对象不能直接调用】

2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

3.静态成员函数不能是虚函数,因为静态成员函数是属于类的,不属于任意对象,只作用在类的静态变量上。
【访问虚函数需要通过对象的虚表指针访问虚表,来获得虚函数入口】
【静态成员函数也不能是const成员函数,因为编译器会在对象的const函数中自动插入一个const T *this参数,而静态成员函数不属于对象】

4.内联(inline)函数不能是虚函数。即使虚函数在类的内部定义定义,编译的时候系统仍然将它看做是非内联的。
【内联函数在编译时可能会展开代码,这样内存的代码区就没有该函数的代码了,已经不是一个函数的概念了,自然虚表里面也没法保存函数指针了】

5.构造函数不能是虚函数,因为构造的时候,对象还是一片位定型的空间,只有构造完成后,对象才是具体类的实例。

6.析构函数可以是虚函数,而且通常声名为虚函数。
【有派生类的基类,其析构函数必须为虚函数,这样析构的时候会先析构派生类对象,再析构基类对象,否则派生类的部分就没被析构】

看到上面的规则1,有三个概念要注意:

Overload(重载):将语义、功能相似的几个函数用同一个名字表示,但<参数>或<参数与返回值都>不同(参数个数、类型或顺序不同),即函数重载。
(1)相同的范围(在同一个类中或同一个文件内的普通函数);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。 Override(覆盖):是指派生类函数覆盖基类函数,只能是虚函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同
(3)返回值参数相同
(4)基类函数必须有virtual 关键字。 Overwrite(重写):是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 【规则1】
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

以下的例子转自 何海涛的博客

一个失去多态性的例子:在构造函数中调用虚函数

 class A
{
public:
A() //自定义默认构造函数
{
  Print();
}
virtual void Print()
{
  cout << "A is constructed." << endl;
}
}; class B: public A
{
public:
B()
{
  Print();
} virtual void Print() //“覆盖”,但实际可能不是这样
{
  cout << "B is constructed." << endl;
}
}; int main(int argc, char *argv[])
{
A *pa = new B; //加括号或者括号有参数的,调用相应的自定义的构造函数,没有括号,则调用默认构造函数或者唯一的构造函数
delete pa; return ;
}

以上代码输出

 A is constructed.
B is constructed.

用B的构造函数时,会先调用B的基类即A的构造函数。

A的构造函数里调用了Print,由于此时对象的类型B的部分还没有构造好,本质上它只是A的一个对象,其虚表指针指向的是类型A的虚函数表。

接着调用类型B的构造函数,并调用Print。此时已经开始构造B,因此此时调用的Print是B::Print。

因此虚函数在构造函数中调用时,已经失去了虚函数的动态绑定特性。

在普通成员函数中调用虚函数,保持多态性:

class A
{
public:
void print()
{
doPrint(); //调用虚函数
}
virtual void doPrint()
{
cout << "A::doPrint" << endl;
}
}; class B: public A
{
public:
virtual void doPrint() //虚函数覆盖
{
cout << "B::doPrint" << endl;
}
}; int main(int argc, char *argv[])
{
A a;
a.print(); B b;
b.print(); return ;
}

以上代码输出

 A::doPrint
B::doPrint

在print中调用doPrint时,doPrint()的写法和this->doPrint()是等价的,因此将根据实际的类型调用对应的doPrint。

虚函数的缺省参数

 class A
{
public:
virtual void fun(char c = 'A') //缺省参数
{
cout << "A::fun " << c << endl;
}
}; class B: public A
{
public:
virtual void fun(char c = 'B') //缺省参数,虚函数覆盖
{
cout << "B::fun " << c << endl;
}
}; int main(int argc, char *argv[])
{
B b;
A &a = b; a.fun(); //基类引用派生类对象,动态绑定 return ;
}

以上代码输出

 B::fun  A

由于基类的a是一个指向B对象的引用,因此在运行的时候会调用B::Fun。动态绑定的过程。

缺省参数是在编译期决定的。

编译时,编译器只知道a是一个类型A的引用,具体指向什么类型在编译期是不能确定的,因此会按照A::fun的声明把缺省参数c设为'a'。

关于C++虚函数的一些东西的更多相关文章

  1. c++中的虚函数是什么东西?

    #include <iostream> #include<string> #include<vector> using namespace std; class A ...

  2. 对C++虚函数的理解

    关于类不断被继承的过程,从整体上看,是一个从抽象到逐渐具体化的过程,基类可以是非常非常抽象的东西,而最终实例化的派生类就非常具体了. 虚函数的意义,就在于定义了一个从最早的基类到最终的派生类都可能会用 ...

  3. C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现

    tfref 前言 C++对象的内存布局 只有数据成员的对象 没有虚函数的对象 拥有仅一个虚函数的对象 拥有多个虚函数的对象 单继承且本身不存在虚函数的继承类的内存布局 本身不存在虚函数(不严谨)但存在 ...

  4. C++中虚函数的作用浅析

    虚函数联系到多态,多态联系到继承.所以本文中都是在继承层次上做文章.没了继承,什么都没得谈. 下面是对C++的虚函数这玩意儿的理解. 一, 什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你 ...

  5. C++-不要在构造和析构函数中调用虚函数

    在实习的单位搞CxImage库时不知为什么在Debug时没有问题,但是Release版里竟然跳出个Pure virtual function call error! 啥东西呀,竟然遇上了,就探个究竟吧 ...

  6. 浅谈C++虚函数

    很长时间都没写过博客了,主要是还没有养成思考总结的习惯,今天来一发. 我是重度拖延症患者,本来这篇总结应该是早就应该写下来的. 一.虚函数表 C++虚函数的机制想必大家都清楚了.不清楚的同学请参看各种 ...

  7. C++虚函数的缺陷

    MFC中的消息机制没有采用C++中的虚函数机制,原因是消息太多,虚函数内存开销太大.在Qt中也没有采用C++中的虚函数机制,原因与此相同,其实这里还有更深层次上的原因,大体说来,多态的底层实现机制只有 ...

  8. 以boost::function和boost:bind取代虚函数

    转自:http://blog.csdn.net/Solstice/archive/2008/10/13/3066268.aspx 这是一篇比较情绪化的blog,中心思想是“继承就像一条贼船,上去就下不 ...

  9. C++如何处理内联虚函数

    http://blog.csdn.net/hedylin/article/details/1775556 当一个函数是内联和虚函数时,会发生代码替换或使用虚表调用吗? 为了弄清楚内联和虚函数,让我们将 ...

随机推荐

  1. USACO Section 3.1: Score Inflation

    完全背包问题 /* ID: yingzho1 LANG: C++ TASK: inflate */ #include <iostream> #include <fstream> ...

  2. HeadFirst Jsp 07 (使用 jsp)

    Jsp 变成 Servlet, 容器会查看你的JSP, 把它转换成java源代码, 再编译成完整的Java servlet类. Jsp 不需要你的编译, 容器会自动替换成servlet. 在 jsp中 ...

  3. 详解javascript中的call, apply

    一些学js的同学一看到call, apply, 就蒙了, 感觉不好懂, 看的头大. 今天我们就一起来研究一下这2个东东.彻底弄清楚它们的用法. 定义: call, apply是函数的方法, 只有函数才 ...

  4. Photoshop图层混合模式计算公式大全(转)

    混合模式可以将两个图层的色彩值紧密结合在一起,从而创造出大量的效果.在这些效果的背后实际是一些简单的数学公式在起作用.下面我将介绍photoshop cs2中所有混合模式的数学计算公式.另外还介绍了不 ...

  5. 【转载】两军问题与Paxos算法 & 动画讲解Paxos算法

    http://harry.me/blog/2014/12/27/neat-algorithms-paxos/ 这篇文章里面有用JS写的Paxos过程,有助理解.但是没怎么仔细看,没时间. 这篇文章用两 ...

  6. ajaxFileUpload插件上传文件 返回 syntaxError :unexpected token

    Html 代码<table id="deploy_application" class="bordered-table"> <tr> & ...

  7. android 单词

    inflate: 胀, 膨, 通货膨胀, 膨胀系数

  8. HDU 3496 (二维费用的01背包) Watch The Movie

    多多想看N个动画片,她对这些动画片有不同喜欢程度,而且播放时长也不同 她的舅舅只能给她买其中M个(不多不少恰好M个),问在限定时间内观看动画片,她能得到的最大价值是多少 如果她不能在限定时间内看完买回 ...

  9. 漫游Kafka入门篇之简单介绍

    介绍 Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢?   首先让我们看几个基本的消息系统术语: Kafka将消息以 ...

  10. PHP面向对象(PHP对象在内存中的分配)

    对 像在PHP 里面和整型.浮点型一样,也是一种数据类,都是存储不同类型数据用的, 在运行的时候都要加载到内存中去用,那么对象在内存里面是怎么体现的呢?内存从逻 辑上 说大体上是分为4 段,栈空间段. ...