c++ 虚函数与纯虚函数的区别(深入分析)

在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现 了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说, 它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。
那么,什么是虚函数呢,我们先来看看微软的解释:
虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。                                                          这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子:


代码如下:

#include "stdio.h"
#include "conio.h"
class Parent
{    
public:
    char data[20];    
    void Function1();
    virtual void Function2(); // 这里声明Function2是虚函数    
}parent;
void Parent::Function1()
{
    printf("This is parent,function1\n");
}
void Parent::Function2()
{
    printf("This is parent,function2\n");
}
class Child: public Parent
{
    void Function1();
    void Function2();

} child;
void Child::Function1()
{
    printf("This is child,function1\n");
}
void Child::Function2()
{
    printf("This is child,function2\n");
}
int main(int argc, char* argv[])
{    
    Parent *p; // 定义一个基类指针

if ( _getch()=='c' ) // 如果输入一个小写字母c
        p=&child; // 指向继承类对象
    else
        p=&parent; // 否则指向基类对象

p->Function1(); // 这里在编译时会直接给出Parent::Function1()的 入口地址。   
    p->Function2(); // 注意这里,执行的是哪一个Function2?

return 0;
}

用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:
This is parent,function1
This is child,function2
为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器 无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了 第一行的结果。
  
那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是 说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入 一个非c的字符,结果如下:
This is parent,function1
This is parent,function2
  
请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2 还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应 用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚 函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。
-----------------------------------------------------------
再看下面的
派生类的大小问题C++中虚函数和纯虚函数的概念,差别和分别存在的原因
首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数,定义它为虚函数是为了允许用基类的指针来调用子类的这个函数
定义一个函数为纯虚函数,才代表函数没有被实现,定义他是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
对继承的影响:
普通的类(没有虚函数,纯虚函数)就可以被继承,而且工作的相当好
关于这个问题有以下疑问:
纯虚函数难道就是为了实现接口?接口存在的意义?
我实在弄不懂,我干嘛要预先定义好?未来的事情本难料,就等有一天我的类中需要使用某个函数,在添加一个函数不就可以?
关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:


代码如下:

class CA
{
public:
    virtual void fun() = 0; // 说明fun函数为纯虚函数
    virtual void fun1();
};
class CB
{
public:
    virtual void fun();
    virtual void fun1();
};
// CA,CB类的实现
...
void main()
{
    CA a; // 不允许,因为类CA中有纯虚函数
    CB b; // 可以,因为类CB中没有纯虚函数

...
}

---------------------------------------------------------------
虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。
dog mydogwangwang;
mydogwangwang.born();
一定是返回“dog”
那么
horse myhorsepipi;
myhorsepipi.born();
一定是返回“horse”
也是多态呀?
/////////////////////////////////////////////////
有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:


代码如下:

void animal::fun1(animal *maybedog_maybehorse)
{
     maybedog_maybehorse->born();
}

参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。
也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。


代码如下:

//用虚函数
#include <iostream.h>
class animal
{
public:
    animal();
    ~animal();
    void fun1(animal *maybedog_maybehorse);
    virtual void born();
};
void animal::fun1(animal *maybedog_maybehorse)
{
    maybedog_maybehorse->born();
}
animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
    cout<< "animal";
}
class dog: public animal
{
public:
    dog();
    ~dog();
    virtual void born();
};
dog::dog()
{
}
dog::~dog()
{
}
void dog::born()
{
    cout<<"dog";
}
class horse:public animal
{
public:
    horse();
    ~horse();
    virtual void born();
};
horse::horse()
{
}
horse::~horse()
{
}
void horse::born()
{
    cout<<"horse";
}
void main()
{
    animal a;
    dog b;
    horse c;    
    a.fun1(&c);
}
//output: horse
//不用虚函数
#include <iostream.h>
class animal
{
public:
    animal();
    ~animal();
    void fun1(animal *maybedog_maybehorse);
    void born();    
};
void animal::fun1(animal *maybedog_maybehorse)
{
    maybedog_maybehorse->born();
}
animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
    cout<< "animal";
}
class dog: public animal
{
public:
    dog();
    ~dog();
    void born();
};
dog::dog()
{
}
dog::~dog()
{
}
void dog::born()
{
    cout<<"dog";
}
class horse:public animal
{
public:
    horse();
    ~horse();
    void born();
};
horse::horse()
{
}
horse::~horse()
{
}
void horse::born()
{
    cout<<"horse";
}
void main()
{
    animal a;
    dog b;
    horse c;    
    a.fun1(&c);
}
//output: animal

---------------------------------------------------------------
有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
---------------------------------------------------------------
定义纯虚函数就是为了让基类不可实例化化,
因为实例化这样的抽象数据结构本身并没有意义.
或者给出实现也没有意义
实际上我个人认为纯虚函数的引入,是出于两个目的:
1.为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果. 提醒子类去做应做的实现.
2.为了效率,不是程序执行的效率,而是为了编码的效率.

(0)

相关推荐

  • 虚函数与纯虚函数(C++与Java虚函数的区别)的深入分析

    c++虚函数1.定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数 [1]2.语法:virtual 函数返回类型 函数名(参数表) { 函数体 }3.用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数,也就是允许子类override父类同名方法.虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型(也

  • 深入探讨C++父类子类中虚函数的应用

    构造函数不能是虚函数,因为在调用构造函数创建对象时,构造函数必须是确定的,所以构造函数不能是虚函数.析构函数可以是虚函数. 1.父类Father.h: 复制代码 代码如下: #pragma onceclass Father{public: Father(void); virtual ~Father(void); virtual int getCount();public: int count;}; Father.cpp 复制代码 代码如下: #include "StdAfx.h"#in

  • 深入解析C++中的虚函数与多态

    1.C++中的虚函数C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Tab

  • C++ COM编程之接口背后的虚函数表

    前言 学习C++的人,肯定都知道多态机制:多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.对于多态机制是如何实现的,你有没有想过呢?而COM中的接口就将这一机制运用到了极致,所以,不知道多态机制的人,是永运无法明白COM的.所以,在总结COM时,是非常有必要专门总结一下C++的多态机制是如何实现的. 多态 什么是多态?上面也说了,多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.现在通过代码,让大家切身的体会一下多态: 复制代

  • c++中虚函数和纯虚函数的作用与区别

    虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数. class Cman { public: virtual void Eat(){--}; void Move(); private: }; class CChild : public CMan { public: virtual void

  • c++中虚函数的实现详解

    前言 c++ 分为编译时多态和运行时多态.运行时多态依赖于虚函数,大部分人或许听说过虚函数是由虚函数表+虚函数指针实现的,但,真的是这样吗?虽然 c++ 规范有着复杂的语言细节,但底层实现机制却任由编译器厂商想象.(没准某种特殊的处理器电路结构原生支持虚函数,没准这个处理器压根不是冯纽曼型,或者将来厂商发明了比虚函数表更有效率的数据结构.) 虚函数表 封装把实例的数据和操作结合在了一起,但实例本身只有数据,没有函数,同一个类的函数是共享的.我们通过一个例子来间接证明这一点 class Base1

  • C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点

    普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的"字面值"类对象,调用自己的普通函数:虚函数为了重载和多态的需要,在基类中定义的,即便定义为空:纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须定义自己的实现方法. 假设我们有三个类Person.Teacher.Student它们之间的关系如下: 类的关系图 普通成员函数 [Demo1] 根据这个类图,我们有下面的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__

  • C++中虚函数与纯虚函数的用法

    本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

  • C++虚函数的实现机制分析

    本文针对C++的虚函数的实现机制进行较为深入的分析,具体如下: 1.简单地说,虚函数是通过虚函数表实现的.那么,什么是虚函数表呢? 事实上,如果一个类中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚函数表(vtbl),表中每一项指向一个虚函数的地址,实现上就是一个函数指针的数组. 例如下面这个例子: class Parent { public: virtual void foo1() { } virtual void foo1() { } void foo1(); }; class Ch

  • 浅析C++中的虚函数

    一.定义定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数. 语法:virtual 函数返回类型函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public. 定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数. 二.作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函

随机推荐