美文网首页
C++ 虚函数

C++ 虚函数

作者: 水中的蓝天 | 来源:发表于2020-05-30 16:56 被阅读0次

    本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢。

    前言:

    多态性是面向对象程序设计的重要特征之一,所谓多态性是指当不同的对象收到函数名相同,参数不同时,产生不同的动作。C++的多态性具体体现在运行和编译两个阶段;在程序运行时的多态性通过继承虚函数来体现,而在程序编译时多态性体现在函数运算符重载上。

    重载

    在C++语言中,只有在声明函数原型时形式参数个数或者对应位置的类型不同,两个或多个的函数共用一个名字。这种在同一个作用域中允许多个函数使用同一函数名的措施被称为重载(overloading)。函数重载是C++程序获得多态性的途径之一。

    函数重载要求编译器能够唯一的确定调用一个函数时应执行哪个函数的代码,即采用哪个函数实现。确定函数实现时,要求从函数参数的个数类型上来区分。这就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型不同,否则,将无法实现函数重载。 注意:返回值不同不在考虑范围

    
    int square(int x){
    return x*x;
    }
    
    double square(double y){
    return y*y;
    }
    
    
    以上两个同名函数即为函数重载
    
    ----------------------------------------
    
    用重载函数实现求园和矩形的周长:
    
    const double PI = 3.1415;
    
    double length(float r){
    return 2*PI*r;
    }
    
    double length(float x, float y){
    return 2*(x+y);
    }
    
    

    在程序中不可滥用函数重载,不适当的重载会降低程序的可读性。C++语言并没有提供任何约束来限制重载函数之间必须有关联,开发者可能用相同的名字定义两个不相关的函数;实际上函数重载暗示了一种关联,不应该重载那些本质上有区别的函数,只有当函数实现的语义非常接近时才应使用函数重载 !

    函数重载的二义性(ambiguity)

    • 是指C++语言的编译程序无法在多个重载函数中选择正确的函数进行调用。说白了就是编译器蒙圈了,不知道应该怎么办咯
    • 函数重载的二义性主要源于C++语言的隐式类型转换默认参数
    • 在函数调用时,编译程序将按以下规则选择重载函数:
    1. 如果函数调用的实际参数类型与一个重载函数的形式参数类型完全匹配,则选择调用该重载函数;
    2. 如果找不到与实际参数类型完全匹配的函数原型,但将一个类型转换为更高级类型后能找到完全匹配的函数原型,编译程序将选择调用该重载函数。所谓更高级类型是指能处理的值域较大,比如:int 转换为 unsigned int; unsigned int 转换为 long;
    3. 隐式类型转换是由C++编译程序自动完成的,这种类型转换是引起函数重载二义性的主要原因。在重载函数中使用默认参数也可能造成二义性

    C++还提供了一种更为灵活的多态机制:虚函数

    虚函数允许函数调用函数体的联系在运行时才进行,当一般的类型对应与不同的类型变种时,这个能力显得尤其重要!

    • 指向基类和派生类的指针是相关的。

    例如:

    
    类型A,类型B;  B类型是A类型的派生类
    
    A  *p; //指向类型A的对象的指针
    A objA; //类型A的对象 objA
    B objB; //类型B的对象 objB
    p = & objA;  //p可以指向A类型的对象
    p = & objB;  //p可以指向B类型的对象
    
    利用指针p, 可以通过objB访问所有从objA继承的元素,但不能用p访问objB自身特定的元素(除非用了显示类型转换)
    
    
    • 虚函数是在基类中冠以关键字virtual的成员函数;它提供了一种接口界面。虚函数可以在一个或多个派生类中被重定义。

    • 在C++语言中,是通过将一个函数定义成虚函数来实现运行时的多态的;如果一个函数被定义为虚函数,那么即时是使用指向基类对象的指针类调用该成员函数,C++也能够保证调用的是正确的特定于实际对象的成员函数。

    • 虚函数不会出现函数调用二义性

    • virtual:用来修饰公有或保护成员函数;虽然也可以修饰私有函数,但这样做毫无意义

    • 虚函数的重载函数仍然是虚函数;比如在基类中声明了虚函数,那么在派生类重载成员函数时,无论是否使用virtual关键字,该成员函数都会被编译器编译为虚函数

    • 注意:

    1. 在派生类重定义虚函数时必须有相同的函数原型,包括返回值类型、函数名、参数个数、参数类型的顺序都必须相同
    2. 虚函数必须是类的成员函数,不能为全局函数,也不能为静态函数,不能将友员说明为虚函数,但虚函数可以是另一个类的友员

    虚函数使用注意

    • 在类体系中访问一个虚函数时,应使用指向基类类型的指针或对基类类型的引用,以满足运行时多态性的要求。当然也可以像普通成员函数那样利用对象名来调用一个函数
    • 在派生类中重新定义一个虚函数时,必须保证该函数的值和参数与基类中的说明完全一致,否则就属于重载(参数不同)或是一个错误(返回值不同)
    • 若在派生类照片那个没有重新定义虚函数,则该类的对象将使用其基类中的虚函数代码
    • 析构函数可以是虚函数,但构造函数不能为虚函数;一般来说,若某类中定义有虚函数,则其析构函数也应当说明为虚函数,特别是在析构函数需要完成一些有意义的操作---比如释放内存时,尤其应当如此。
    • 一个类的虚函数仅对派生类中重定义的函数起作用,对其他函数没有影响。
      在基类中使用虚函数,保证了通过指向基类对象的指针调用基类的一个虚函数时;C++系统会对该调用进行动态绑定;而使用普通函数则是静态绑定

    是不是对 构造函数和析构函数 很疑惑 ?

    • 析构函数(destructor)是成员函数的一种,它的名字与类名相同,但前面要加 ~ ,没有参数和返回值。

    • 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

    构造函数 示例

    block的构造函数 
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc; 
      __Block_byref_age_0 *age; // by ref    // 如果不使用__block 修饰,这里的代码会是 int age; 
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,__Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    

    析构函数示例

    class  CDemo {
    public:
         ~CDemo() { //析构函数
         cout << "Destructor called"<<endl;
           }
     };
    

    虚函数和重载函数的比较

    • 虚函数要求函数名、返回值类型、形参序列完全相同

    • 重构函数要求函数有相同的返回值类型和函数名,并有不同的形参序列

    • 虚函数只能是成员函数

    • 重载函数可以是成员函数或者友员函数

    • 虚函数是根据对象的不同去调用不同类型的虚函数

    • 重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据

    • 虚函数是在运行时表现出多态性,这是C++的精髓

    • 重载函数是在编译阶段变现出多态性

    扩展---纯虚函数

    • 在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。定义纯虚函数的一般形式为:
    class 类名{
       virtual 返回值类型 函数名(参数序列) = 0;
    }
    
    比如:
    
    class Creature{
       public:
       virtual char *kindOf() = 0;
    }
    
    
    • 纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义(实现),要求任何派生类都定义自己的版本。纯虚函数为各派生类提供一个公共界面。由于纯虚构函数所在的类中没有它的定义,在该类的构造函数析构函数中不允许调用纯虚函数,否则会导致程序运行错误。但其他成员函数可以调用纯虚函数

    抽象类

    • 如果一个类中至少有一个纯虚函数,那么这个类就是抽象类(abstract class); 抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的

    • 抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建实例对象;抽象类不能直接创建对象的原因是其中一个或多个函数没有定义,但仍可使用指向抽象类的指针,支持运行时多态性

    • 一个抽象类不可以用来创建对象,这只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看做一个抽象类。如果直接调用抽象类中定义的纯虚函数,必须使用完全限定名,如这样的格式:

    
    cout<<指向派生类的指针->kindOf()<<endl;
    
    上面的代码同时还给出了一种绕过虚函数机制的方法,即使用带有作用域限定符的完全限定函数名
    
    
    • 由于派生类的析构函数不可能和抽象类的析构函数同名,因此提供一个默认的析构函数的实现是完全不必要的。这也是纯虚析构函数和其他纯虚函数的一个最大的不同之处

    • 抽象类的主要作用是取若干类的共同行为,形成更清晰的概念层次

    • 从基类继承来的纯虚函数,在派生类中仍然是虚函数

    相关文章

      网友评论

          本文标题:C++ 虚函数

          本文链接:https://www.haomeiwen.com/subject/suiczhtx.html