`

学习虚函数和重载

    博客分类:
  • C++
 
阅读更多
数据封装和继承性共同构成了面向对象程序设计的三个重要机制。
    1.静态联编与动态联编

    由于函数重载的存在,当程序中出现调用同名函数时,编译器会根据函数的参数类型、个数决定调用执行哪一个同名函数的代码,这种把一个函数的调用与适当的函数实现代码联系在一起的过程,叫做联编。根据联编的实现阶段的不同,可将其分为静态联编和动态联编两种。

    静态联编是在程序编译阶段确定一个函数调用与函数实现代码间的对应关系,这种对应关系确定下来后,在程序运行过程中就根据这个对应关系去调用执行相应的函数代码,并且这种对应关系在程序运行过程中始终保持不变。

    而动态联编是在编译阶段不能决定执行哪个同名的被调函数,只在程序运行过程中根据需要处理的对象类型来决定执行哪个类的成员函数。

    2.多态性

    所谓多态性就是指同样的消息被类的不同对象接收时导致的完全不同的行为的一种现象。这里所说的消息即对类的成员函数的调用。

    函数的重载可以实现多态性,但这里要讲的多态性是通过虚函数来实现的,而虚函数又必须存在于继承的环境下。

    C++语言支持两种类型的多态:一种是编译时的多态(静态多态),另一种是运行时的多态(动态多态)。在编译时的多态是通过静态联编实现的,而在运行时的多态则是通过动态联编实现的。

    3.虚函数

    声明虚函数的方法是在基类中的成员函数原型前加上关键字virtual.格式如下:

    class 类名{

    ……

    virtual 类型 函数名(参数表);

    ……

    };

    当一个类的成员函数声明为虚函数后,这就意味着该成员函数在派生类中可能有不同的实现,也就是说,该函数在派生类中可能需要定义与其基类虚函数原型相同的函数。

    虚函数是动态联编的基础,当用基类类型的指针或引用的方法指向不同派生类对象时,系统会在程序运行中根据所指向对象的不同自动选择适当的函数,从而实现了运行时的多态性。

    当通过基类指针或引用标识对象并调用成员函数时,由于基类指针可以指向该基类的不同派生类对象,因此存在需要动态联编的可能性,但具体是否使用动态联编,还要看所调用的是否是虚函数。

    虚函数可以在一个或多个派生类中被重新定义,但它要求在派生类中重新定义时必须与基类中的函数原型完全相同,包括函数名、返回值类型、参数个数和参数类型的顺序。

    只有类的成员函数才能声明为虚函数,但类的构造函数以及全局函数和静态成员函数不能声明为虚函数。

    4.用基类指针指向公有派生类对象

    指向基类的指针自然可以指向其公有派生类的对象。但是,由于基类指针本身的类型并没有改变,因此,基类指针仅能访问派生类中的基类部分。

    5.纯虚函数与抽象类

    在定义一个表达抽象概念的基类时,有时可能会无法给出某些成员函数的具体实现。这时,就可以将这些函数声明为纯虚函数。

    纯需函数的声明格式如下:

    virtual 类型 函数名(参数表)=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 <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修饰,不单单对基类和派生类的普通成员函数有必要,而且对于基类和派生类的析构函数同样重要。

分享到:
评论

相关推荐

    微软官方MFC教程之虚函数重载画图

    本教程聚焦于MFC中的虚函数重载,特别在画图功能上的应用。 在MFC中,画图通常通过CDC(Device Context)类来实现,这是一个用于图形设备接口(GDI)的类。CDC提供了各种方法来绘制线条、曲线、文本、图形等元素。...

    MFC写的画图的有助于理解虚函数

    通过这样的实践,MFC初学者不仅能掌握基本的GUI编程技巧,还能深入理解虚函数和操作符重载的原理和用法。虚函数保证了多态性,使得程序更加灵活;操作符重载则提高了代码的表达力,让代码更贴近自然语言。结合MFC库...

    C++面向对象编程:操作符重载、虚函数与抽象类及封装

    内容概要:本文深入探讨了C++中面向对象编程(OOP)的关键特性,主要讨论了操作符重载、虚函数与抽象类以及类的访问控制与封装。通过详细的代码示例,解释了如何通过操作符重载实现类对象的常见运算,如何利用虚函数...

    C++学习笔记(13)——利用对象、引用、指针调用虚函数.pdf

    1. 重载函数要求函数名相同,但参数列表不同,而虚函数重定义时参数列表和返回类型必须相同。 2. 虚函数必须是类的成员,而重载函数可以是成员函数也可以是非成员函数。 3. 构造函数可以重载但不能是虚函数,而析构...

    实验7 多态性和运算符重载.doc

    在实验中,我们将学习如何实现运算符重载,包括成员函数和友元函数两种形式,并且学习如何使用虚函数实现多态性。通过实验,我们将更好地理解多态性和运算符重载的概念,并掌握如何在实际编程中应用这些技术。

    vc++_mfc函数重载与操作符重载

    在这个主题中,我们主要关注的是函数重载和操作符重载,以及它们在MFC中的应用。此外,我们还将讨论虚函数、纯虚函数和抽象类的概念,这些都是面向对象编程中的核心概念。 函数重载是C++的一个关键特性,允许我们在...

    C++ 编程思想 象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器类、多重继承、异常处理和运行时类型识别

    内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器...

    C++学习笔记(13)——利用对象、引用、指针调用虚函数.docx

    5. 虚函数与重载函数的主要区别在于,重载函数必须有不同的参数列表,而虚函数可以有相同的参数列表,但实现可能不同。 6. 构造函数不能是虚函数,但析构函数可以而且常常声明为虚函数,以便在派生类对象销毁时调用...

    C++全套学习课件函数的覆盖、隐藏和重载.pdf

    虽然多态和动态绑定提供了很大的灵活性,但它们也带来了一些性能损失,因为调用虚函数需要通过虚函数表(vtable)查找实际的函数地址。在性能关键的代码段中,应谨慎使用虚函数。 总之,理解并正确使用C++中的函数...

    第11次(虚函数).zip

    通过深入学习和实践这些概念,你可以更好地理解和利用C++中的虚函数,从而编写出更加灵活和可维护的代码。在实际编程中,虚函数是实现复杂系统设计的重要工具,如设计模式中的工厂模式、策略模式等。

    C程序设计电子多态性和虚函数PPT学习教案.pptx

    在C++中,多态性主要通过函数重载(Overloading)和虚函数(Virtual Functions)来实现。 1. **函数重载**: - **普通成员函数重载**:同一个类或不同类中可以有多个同名函数,只要它们的参数列表不同(包括数量、...

    C程序设计电子多态性和虚函数学习教案.pptx

    本教程主要探讨了多态性以及其在C++中的实现方式,特别是通过普通成员函数重载、构造函数重载和派生类指针的应用。 首先,我们来看普通成员函数的重载。函数重载允许在同一个作用域内定义多个同名函数,但它们的...

    c++课件第十章虚函数和多态性共19页.pdf.zip

    静态多态主要通过函数重载和运算符重载来实现,而动态多态则是通过虚函数和纯虚函数(pure virtual function)来实现的。 纯虚函数是一个只有声明没有定义的虚函数,它使得包含纯虚函数的类成为抽象类(Abstract ...

    多态性与虚函数PPT学习教案.pptx

    静态多态性,也称为编译时多态性,是通过函数重载和运算符重载实现的。函数重载允许我们在同一个作用域内使用相同的名字但参数列表不同的函数。例如,我们可以为不同类型的数据提供重载的加法运算符`+`,编译器会在...

    第六章多态性与虚函数PPT学习教案.pptx

    静态多态性通过函数重载和运算符重载在编译时实现,而动态多态性则通过虚函数在运行时动态绑定,确保调用正确的函数版本。理解并熟练掌握这两种多态性对于编写高效、灵活的面向对象程序至关重要。

    C程序设计电子多态性和虚函数PPT课件.pptx

    在C++编程语言中,多态...总结来说,C++中的多态性主要通过成员函数重载和虚函数实现,它提高了代码的可读性和可复用性,使得编写更通用的代码成为可能。在编写面向对象的C++程序时,理解并合理运用这些概念至关重要。

    c++程序设计讲义 C++的初步认识 类和对象 运算符重载 继承与派生 多态性与虚函数 输入输出流

    5. 多态性与虚函数:多态性是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在C++中,虚函数是实现多态性的主要手段。虚函数使得基类指针或引用可以调用派生类的重写版本,实现动态绑定。 6....

    C++程序设计讲义 多态性与虚函数

    ### C++程序设计讲义:多态性与虚函数 #### 12.1 多态性的概念 ...通过以上内容的学习,我们可以看到多态性和虚函数在C++中的重要性。它们不仅提高了代码的复用性和可扩展性,还使得设计更为灵活和高效。

    多态性和虚函数PPT课件.pptx

    静态多态性(Static Polymorphism)发生在编译时,主要通过成员函数重载(Overloading)和运算符重载(Operator Overloading)来实现。成员函数重载是指在同一类中可以有多个同名函数,但它们的参数列表不同(参数...

    c++多态性与虚函数习题答案.pdf

    编译时多态性主要通过函数重载和模板来实现,编译器在编译阶段就能确定调用哪个函数。而运行时多态性则依赖于虚函数,它在程序运行时根据对象的实际类型来决定调用哪个函数,实现了动态绑定。 1. **虚函数** 虚...

Global site tag (gtag.js) - Google Analytics