C++手稿:虚函数与多态

C++类继承带来了诸多好处:基类代码复用、通用的方法和属性、更好的可维护性, 然而最大的好处莫过于提供统一的接口。接口是一种对类型的抽象,它统一了一系列类的行为, 不同类的对象之间交互更加容易。Java、objective C等面向对象语言都提供了接口的概念, 在C++中,可以通过抽象类来实现一个接口。

C++通过虚函数实现了多态:通过基类指针或引用调用虚函数时,会调用当前对象的实际类型中声明的函数。 为了这个特性,包含虚函数的C++对象中会存储一个虚函数表指针,来完成动态联编。

编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序运行时才能确定将要调用的函数, 为此要确切知道该调用的函数,要求联编工作要在程序运行时进行, 这种在程序运行时进行联编工作被称为动态联编。

虚函数

虚函数通过virtual关键字来声明。

class CPerson{
public:
    virtual void hello(){
        cout<<"I‘m a person"<<endl;
    }
};
class CMan: public CPerson{
public:
    // 子类中不必声明virtual
    void hello(){
        cout<<"I‘m a man"<<endl;
    }
};
CPerson *p = new CMan();
p->hello();
// I‘m a man

上述代码中,通过基类指针调用虚函数时,子类的同名函数得到了执行。多态在C++中有三种形态:

  1. 通过基类指针调用基类和子类的同名虚函数时,会调用对象的实际类型中的虚函数。
  2. 通过基类引用调用基类和子类的同名虚函数时,会调用对象的实际类型中的虚函数。
  3. 基类或子类的成员函数中调用基类和子类的同名虚函数,会调用对象的实际类型中的虚函数。

纯虚函数

虚函数的声明以=0结束,便可将它声明为纯虚函数。包含纯虚函数的类不允许实例化,称为抽象类。
事实上纯虚函数提供了面向对象中接口的功能。当然,这样的接口是以继承的方式实现的。

class CPerson{
public:
    virtual void hello() = 0;
};
CPerson p;  // compile error

注意空方法、纯虚函数、方法声明的区别。类声明中的空方法给出了方法声明+方法定义。 只声明但没有定义的方法将会产生链接错,无论是否被调用过。

class CPerson{
public:
    void empty(){};
    void declare();
};
CPerson::declare(){
    // ...
};

访问级别

虚函数的调用会在运行时动态匹配当前类型,然而成员函数的访问性检查是语法检查的一部分,在编译期完成。 如果虚函数在父类中是Private,即使在子类中是Public,也不可以通过父类指针调用它:

class CPerson{
    virtual void hello();
};
class CMan: public CPerson{
public:
    virtual void hello();
};

CPerson* p = new CMan;
p->hello(); // 编译错

虚析构函数

虚函数的机制使得我们可以通过更加通用的基类指针来操作对象。然而使用基类指针来delete对象则面临着问题:

CPerson *p = new CMan();
delete p;

上述代码只会回收CManCPerson部分所占用的内存,执行了CPerson的析构函数,却没有执行CMan的虚构函数。
解决办法很容易理解:将析构函数设为virtual

构造函数不允许是虚函数。

class CPerson{
public:
    virtual ~CPerson(){};
};
class CMan: public CPerson{
public:
    ~CMan(){};
};
CPerson *p = new CMan();
delete p;

这样,delete时会先调用~CMan()在调用~CPerson()

构造函数调用虚函数

当执行构造函数时,当前对象的类型为构造函数所属在的类。 所以在构造函数中调用虚函数和调用普通函数是一样的,不会动态联编, 被调用的函数来自自己或者基类。

class CPerson{
public:
    virtual void hello(){
        cout<<"I‘m a person"<<endl;
    }
    virtual void bye(){
        cout<<"Bye, person"<<endl;
    }
};
class CMan: public CPerson{
public:
    CMan(){
        hello();
        bye();
    }
    void hello(){
        cout<<"I‘m a man"<<endl;
    }
};
class CReek: public CMan{
public:
    void hello(){
        cout<<"I‘m a reek"<<endl;
    }
    void bye(){
        cout<<"Bye, reek"<<endl;
    }
};

int main(){
    CReek r;
    return 0;
}

上述的调用结果是:

I‘m a man
Bye, person

hellobye都是虚函数,其中hello三个层级都有定义,但被执行的是当前类CMan中的定义; bye在上下两个层级有定义,被执行的是上一级类CPerson中的定义。
可见,构造函数执行时当前对象的类型是定义构造函数的类。



除非注明,本博客文章均为原创,转载请以链接形式标明本文地址: http://harttle.com/2015/06/28/cpp-polymorphism.html

版权声明:本文为博主原创文章,转载请附上原文链接。

时间: 2024-11-05 20:44:55

C++手稿:虚函数与多态的相关文章

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数.抽象类.虚析构函数.动态创建对象 一.纯虚函数 1.虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 2.如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 3.在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做 4.定义纯虚函数: class <类名> { virtual <类型> <函

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分

C++中虚函数和多态

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

你好,C++(37)上车的人请买票!6.3.3 用虚函数实现多态

6.3.3  用虚函数实现多态 在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像“学生”是“人”类中的一种一样.既然“学生”是“人”的一种,那么在使用“人”这个概念的时候,这个“人”可以指的是“学生”,而“学生”也可以应用在“人”的场合.比如可以问“教室里有多少人”,实际上问的是“教室里有多少学生”.这种用基类指代派生类的关系反映到C++中,就是基类指针可以指向派生类的对象,而派生类的对象也可以当成基类对象使用.这样的解释对大家来说是不是很抽象呢?没关系,可以

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr)、C++对象模型

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr).C++对象模型 一.虚函数表指针(vptr)及虚基类表指针(bptr) C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括: virtual function机制:用以支持一个有效率的"执行期绑定": virtual base class:用以实现多次在继承体系中的基类,有一个单一而被共享的实体. 1.虚函数表指针 C++中,有两种数据

c++ 内存分配 虚函数实现多态等

看到了很好的解释,先mark下,有空整理. 虚函数实现多态与多重继承乌索普的回答 很有收获,毕竟我觉得多态才是面向对象的灵魂. 这样的设计也让人得以理解为什么虚函数会像成员变量一样与对象绑定. C++成员函数在内存中的存储方式 原文地址:https://www.cnblogs.com/zsl96/p/8732662.html

&lt;C++&gt; 类(3):初始化列表 常函数和常量对象 虚函数与多态(包括纯虚函数)

一.初始化列表(初始化列表中必须有的两个内容) 1.类中const的成员变量: ①特点:不能修改 必须初始化 在构造函数后面加冒号 格式为:":变量名(值)" 也就是说 常量必须在初始化列表中初始化 ②执行顺序:构造函数先执行初始化列表 然后执行函数中的内容 1 #include<iostream> 2 using namespace std; 3 4 class CPerson 5 { 6 public: 7 const int a; 8 public: 9 CPerso

一道理解虚函数(多态)机制的题目

一道理解虚函数(多态)机制的题目(摘抄) 以下程序输出为 class Base { public: Base(int j): i(j) {} virtual~Base() {} void func1() { i *= 10; func2(); } int getValue() { return i; } protected: virtual void func2() { i++; } protected: int i; }; class Child: public Base { public:

虚函数和多态

虚函数和多态 - 虚函数 在类的定义中,前面有virtual类关键字的成员函数就是虚函数 class base{ virtual int get(); }; int base::get(){} virtual关键字只用在类定义里的函数声明,写函数体时不用 构造函数和静态成员函数不能是虚函数 虚函数可以参与多态,普通函数不能 - 多态的表现形式一 派生类的指针可以赋给基类指针 通过基类指针指向一个基类和派生类中的同名虚函数时 若该指针指向一个基类对象,那么被调用的是基类的虚函数 若该指针指向一个派