C++中类的多态与虚函数的使用

C++的三大特性:封装、继承、多态。以前学的时候自己没去总结,记得在一本c++入门的书讲得还是比较清楚。今天上网找了一下多态,找到下面这篇文章写得比较清晰。

http://pcedu.pconline.com.cn/empolder/gj/c/0503/574706.html

类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visual BASIC 6.0 是典型的非面向对象的开发语言,但是它的确是支持类,支持类并不能说明就是支持面向对象,能够解决多态问题的语言,才是真正支持面向对象的开发的语言,所以务必提醒有过其它非面向对象语言基础的读者注意!

看如下代码:

//例程1
#include <iostream>
using namespace std;    

class Vehicle
{
public:
    Vehicle(float speed,int total)
    {
        Vehicle::speed=speed;
        Vehicle::total=total;
    }
    void ShowMember()
    {
        cout<<speed<<"|"<<total<<endl;
    }
protected:
    float speed;
    int total;
};
class Car:public Vehicle
{
public:
    Car(int aird,float speed,int total):Vehicle(speed,total)
    {
        Car::aird=aird;
    }
    void ShowMember()
    {
        cout<<speed<<"|"<<total<<"|"<<aird<<endl;
    }
protected:
    int aird;
};    

void main()
{
    Vehicle a(120,4);
    a.ShowMember();
    Car b(180,110,4);
    b.ShowMember();
    cin.get();
}

  

 在c++中是允许派生类重载基类成员函数的,对于类的重载来说,明确的,不同类的对象,调用其类的成员函数的时候,系统是知道如何找到其类的同名成员,上面代码中的a.ShowMember();,即调用的是Vehicle::ShowMember(),b.ShowMember();,即调用的是Car::ShowMemeber();。

 但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:

//例程2
#include <iostream>
using namespace std;    

class Vehicle
{
public:
    Vehicle(float speed,int total)
    {
        Vehicle::speed=speed;
        Vehicle::total=total;
    }
    void ShowMember()
    {
        cout<<speed<<"|"<<total<<endl;
    }
protected:
    float speed;
    int total;
};
class Car:public Vehicle
{
public:
    Car(int aird,float speed,int total):Vehicle(speed,total)
    {
        Car::aird=aird;
    }
    void ShowMember()
    {
        cout<<speed<<"|"<<total<<"|"<<aird<<endl;
    }
protected:
    int aird;
};    

void test(Vehicle &temp)
{
    temp.ShowMember();
}  

void main()
{
    Vehicle a(120,4);
    Car b(180,110,4);
    test(a);
    test(b);
    cin.get();
}

  

例子中,对象a与b分别是基类和派生类的对象,而函数test的形参却只是Vehicle类的引用,按照类继承的特点,系统把Car类对象看做是一个Vehicle类对象,因为Car类的覆盖范围包含Vehicle类,所以test函数的定义并没有错误,我们想利用test函数达到的目的是,传递不同类对象的引用,分别调用不同类的,重载了的,ShowMember成员函数,但是程序的运行结果却出乎人们的意料,系统分不清楚传递过来的基类对象还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。

为了要解决上述不能正确分辨对象类型的问题,c++提供了一种叫做多态性(polymorphism)的技术来解决问题,对于例程序1,这种能够在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(early binding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding),下面我们要看的例程3,就是滞后联编,滞后联编正是解决多态问题的方法。

代码如下:

//例程3
#include <iostream>
using namespace std;    

class Vehicle
{
public:
    Vehicle(float speed,int total)
    {
        Vehicle::speed = speed;
        Vehicle::total = total;
    }
    virtual void ShowMember()//虚函数
    {
        cout<<speed<<"|"<<total<<endl;
    }
protected:
    float speed;
    int total;
};
class Car:public Vehicle
{
public:
    Car(int aird,float speed,int total):Vehicle(speed,total)
    {
        Car::aird = aird;
    }
    virtual void ShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加
    {
        cout<<speed<<"|"<<total<<"|"<<aird<<endl;
    }
public:
    int aird;
};  

void test(Vehicle &temp)
{
    temp.ShowMember();
}  

int main()
{
    Vehicle a(120,4);
    Car b(180,110,4);
    test(a);
    test(b);
    cin.get();
}

  

多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上virtual关键字,那么该成员函数就变成了虚函数,从上例代码运行的结果看,系统成功的分辨出了对象的真实类型,成功的调用了各自的重载成员函数。

  多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。

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

  1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

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

  3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

  4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。

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

  6.析构函数可以是虚函数,而且通常声名为虚函数。

  说明一下,虽然我们说使用虚函数会降低效率,但是在处理器速度越来越快的今天,将一个类中的所有成员函数都定义成为virtual总是有好处的,它除了会增加一些额外的开销是没有其它坏处的,对于保证类的封装特性是有好处的。

  对于上面虚函数使用的重要规则6,我们有必要用实例说明一下,为什么具备多态特性的类的析构函数,有必要声明为virtual

代码如下:

#include "stdafx.h"

#include <iostream>    

using namespace std;   

class Vehicle 

{   

public:  

    Vehicle(float speed,int total) 

    { 

        Vehicle::speed=speed; 

        Vehicle::total=total; 

    } 

    virtual void ShowMember() 

    { 

        cout<<speed<<"|"<<total<<endl; 

    } 

    virtual ~Vehicle() 

    { 

        cout<<"载入Vehicle基类析构函数"<<endl; 

        cin.get(); 

    } 

protected:   

    float speed; 

    int total; 

};   

class Car:public Vehicle   

{   

public:   

    Car(int aird,float speed,int total):Vehicle(speed,total)   

    {   

        Car::aird=aird;   

    } 

    virtual void ShowMember() 

    { 

        cout<<speed<<"|"<<total<<"|"<<aird<<endl; 

    } 

    virtual ~Car() 

    { 

        cout<<"载入Car派生类析构函数"<<endl; 

        cin.get(); 

    } 

protected:   

    int aird; 

};   

void test(Vehicle &temp) 

{ 

    temp.ShowMember(); 

} 

void DelPN(Vehicle *temp) 

{ 

    delete temp; 

} 

void main() 

{   

    Car *a=new Car(100,1,1); 

    a->ShowMember(); 

    DelPN(a); 

    cin.get(); 

} 

  从上例代码的运行结果来看,当调用DelPN(a);后,在析构的时候,系统成功的确定了先调用Car类的析构函数,而如果将析构函数的virtual修饰去掉,再观察结果,会发现析构的时候,始终只调用了基类的析构函数,由此我们发现,多态的特性的virtual修饰,不单单对基类和派生类的普通成员函数有必要,而且对于基类和派生类的析构函数同样重要。

时间: 2024-10-11 12:55:45

C++中类的多态与虚函数的使用的相关文章

GeekBand-secondweek-c++的多态和虚函数

多态与虚函数 13章的简单继承只是实现了对已有对象的实现的重定义和直接调用,但是向上映射导致的对象切割仍然是个缺陷: 1.延续13章的向上映射 简单继承中,派生类重定义了基类的成员函数,此时,向上映射的结果是很明显的,它使用了基类实现的函数版本,这显然并不是我们想要的效果:为什么会有这样的结果发生,我们先探讨几个问题: 函数调用绑定:函数调用确定目标函数体称为捆绑,编译期绑定称为早绑定,上面的问题就是早绑定引起的,因为编译器只知道基类对象的类型,调用函数也会绑定基类实现的函数版本,要解决这一问题

C++中的多态与虚函数的内部实现

1.什么是多态 多态性可以简单概括为“一个接口,多种方法”. 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和运算符的重载来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.重

C++的多态与虚函数

多态性:对于同一消息,不同的对象由不同的响应方式 多态分为静态多态(编译时多态)和动态多态(运行时多态),动态多态通过虚函数来实现. 覆盖-->子类和父类中有同名同参数列表但是功能不同的函数叫做覆盖,在同一个类中有相同的是重复定义,不是覆盖. 虚函数的使用方法,如下: ①在基类中声明一个函数为虚函数,如: //基类vStudent class vStudent { public: vStudent(int,string); virtual void display();//虚函数,用来实现多态性

PKU C++程序设计实习 学习笔记3 多态与虚函数

第六章 多态与虚函数 6.1 多态和虚函数的基本概念 引言 多态是面向对象程序设计里面非常重要的这个机制.它能很有效的提高程序的可扩充性. 有些程序设计语言有被对象继承的概念,但是没有多态的概念,那这样的程序设计语言只能被称作基于对象的程序设计语言,而不能称为面向对象的语言, 比方说visual basic. 虚函数 在类的定义中,前面有 virtual 关键字的成员函数就是虚函数. class base { <span style="color:#ff0000;">vir

多态实现--虚函数与纯虚函数

多态实现--虚函数与纯虚函数 C++中实现多态是使用虚函数表的方法实现的. 那么具体怎么实现的呢? 举例说明 假设有这样一个多态场景: 有一个基类动物(animal类),动物里面又有两个派生类:猫(cat类)和狗(dog类).现在要求动物类有一个共同的方法:叫声(voice成员函数),但猫和狗叫声是不同的(即:它们的叫声实现方法不同). 那么代码怎么写呢? 多态的代码实现 #include <iostream> using namespace std; //1. 定义一个纯虚函数 class

C++ 多态与虚函数

1.多态的概念 由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应. 先看下面这个简单的例子: #include<iostream> using std::cout; using std::endl; class A { public: void print(){cout << "I am A's print" << endl;} }; class B : public A { public: void print()

多态与虚函数

无论是在编译还是在运行时,c++都支持多态性.编译时的多态是通过重载函数和运算符实现的,而编译时的多态则是通过使用继承和虚函数实现的. 虚函数:是一个成员函数,该函数在基类声明,在派生类中重新定义.再基类中将成员函数声明前加关键字virtual,当继承包含虚函数的类时,派生类将重新定义虚函数. 虚函数实现了“一个接口,多种方法”.

C++多态和虚函数学习笔记

1.从实现的角度看,多态可以划分为两种情况:编译期多态和运行时多态. 前者是在编译过程中,确定同名操作的具体操作对象,从而确定同名函数的具体实现: 后者是在程序运行过程中,动态确定具体的操作对象,从而确定同名函数的具体实现. 这种确定操作具体对象的过程成为联编或联合.联编就是将一个标识符和一个存储地址联系在一起的过程,是计算机程序自身彼此相关联的过程. 从联编进行的不同阶段,可以将联编分为静态联编和动态联编. (1)静态联编是指联编工作在程序编译和连接阶段完成的联编过程.静态联编是程序编译之前进

《C++编程思想》 第十四章 多态和虚函数 (原书代码+习题+讲解)

一.相关知识点 函数调用捆绑 把函数体与函数调用相联系称为捆绑(binding).当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑.我们可能没有听到过这个术语,因为在过程语言中是不会有的:C编译只有一种函数调用,就是早捆绑.上面程序中的问题是早捆绑引起的,因为编译器在只有 instrument地址时它不知道正确的调用函数.解决方法被称为晚捆绑,这意味着捆绑在运行时发生,基于对象的类型.晚捆绑又称为动态捆绑或运行时捆绑.当一个语言实现晚捆绑时,必须有一种机制在运行时确定对象的类型和合适的