c++primer(七) 多态性于与虚函数

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



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

 函数重载要求编译器能够唯一地确定调用一个函数时应执行哪个函数代码,既采用哪个函数实现。确定函数实现时,要求从函数参数的个数和类型上来区分。这就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型不同。否则,将无法实现函数重载。

在C++语言中,编译程序选择相应的重载函数版本时函数返回值类型是不起作用的。不能仅靠函数的返回值来区别重载函数,必须从形式参数上区别开来。例如:
        void print(int a);
        void print(int a,int b);
        int print(float a[]);
这三个函数是重载函数,因为C++编译程序可以从形式参数上将它们区别开来。

但:    int f(int a);
            double f(int a);
这两个函数就不是重载函数,编译程序认为这是对一个函数的重复说明,因为两个函数的形式参数个数与相应位置的类型完全相同。
由typedef定义的类型别名并没有真正创建一个新的类型,所以以下程序段:
        typedef double money;
        double calculate(double income);
        money calculate(money income);
    也是错误的函数重载。
同样道理,不同参数传递方式也无法区别重载函数,如:
            void func(int value);
            void func(int &value);
    也不能作为重载函数。
在程序中不可滥用函数重载,不适当的重载会降低程序的可读性。C++语言并没有提供任何约束限制重载函数之间必须有关联,程序员可能用相同的名字定义两个互不相关的函数。实际上函数重载暗示了一种关联,不应该重载那些本质上有区别的函数,只有当函数实现的语义非常相近时才应使用函数重载。
函数重载的二义性(ambiguity)是指C++语言的编译程序无法在多个重载函数中选择正确的函数进行调用。函数重载的二义性主要源于C++语言的隐式类型转换与默认参数。
在函数调用时,编译程序将按以下规则选择重载函数:如果函数调用的实际参数类型与一个重载函数形式参数类型完全匹配,则选择调用该重载函数;如果找不到与实际参数类型完全匹配的函数原型,但如果将一个类型转换为更高级类型后能找到完全匹配的函数原型,编译程序将选择调用该重载函数。所谓更高级类型是指能处理的值域较大,如int转换为unsigned int,unsigned int转换为long,long转换为unsigned float等。

虚函数
C++还提供了一种更为灵活的多态性机制:虚函数。虚函数允许函数调用与函数体的联系在运行时才进行。当一般的类型对应于不同的类型变种时,这个能力显得尤其重要。  

虚函数是在基类中冠以关键字 virtual 的成员函数。它提供了一种接口界面。虚函数可以在一个或多个派生类中被重定义。
在C++语言中,是通过将一个函数定义成虚函数来实现运行时的多态的。如果一个函数被定义为虚函数,那么,即使是使用指向基类对象的指针来调用该成员函数,C++也能保证所调用的是正确的特定于实际对象的成员函数。
如果类c1,c2…由基类base派生而来,base有一个用virtual修饰的公有或保护函数成员f(),而在c1,c2…中的一些类中重新定义了成员函数f(),而对f()的调用都是通过级基类的对象或指针进行的,在程序执行时才决定是调用c1还是c2或其他派生类中定义的f(),这样的函数f()称为虚函数。

一旦一个函数在基类中第一次声明时使用了virtual了关键字,那么,当派生类重载该成员函数时,无论时否使用了virtual 关键字,该成员函数都将被看作一个虚函数,也就是说,虚函数的重载函数仍是虚函数。
注意:在派生类重定义虚函数时必须有相同的函数原型,包括返回类型,函数名、参数个数、参数类型的顺序必须相同。虚函数必须是类的成员函数。不能为全局函数,也不能为静态函数。不能将友员说明为虚函数,但虚函数可以是另一个类的友员。析构函数可以是虚函数,但构造函数不能为虚函数。

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

重载函数要求函数有相同的返回值类型和函数名称,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同;
重载函数可以是成员函数或友员函数,而虚函数只能是成员函数;
重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;
虚函数在运行时表现出多态功能,这是C++的精髓;而重载函数则在编译时表现出多态性


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

如果一个类中至少有一个纯虚函数,那么这个类被成为抽象类(abstract class)。抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。
抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。抽象类不能直接创建对象的原因是其中有一个或多个函数没有定义,但仍可使用指向抽象类的指针支持运行时多态性。


抽象类只能用作其他类的基类,抽象类不能建立对象。抽象类不能用作函数参数类型、函数返回值类型或显式转换的类型。可以声明抽象类的指针和引用。而且,如果在抽象类的构造函数中调用了纯虚函数,那么,其结果是不确定的。还有,由于抽象类的析构函数可以被声明为纯虚函数,这时,应该至少提供该析构函数的一个实现。一个很好的实现方式是的抽象类中提供一个默认的析构函数,该析构函数保证至少有析构函数的一个实现存在。如下面的例子所示:
于派生类的析构函数不可能和抽象类的析构函数同名,因此,提供一个默认的析构函数的实现是完全必要的。这也是纯虚析构函数和其他纯虚成员函数的一个最大的不同之处。一般情况下,抽象类的析构函数是有派生类的实现对象释放时由派生类的析构函数隐含的所调用的。
抽象类的主要作用是取若干类的共同行为,形成更清晰的概念层次。使用抽象类符合程序设计中的单选原则(single choice principle)。
从基类继承来的纯虚函数,在派生类中仍是虚函数。



二. 抽象类

1. 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。

抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。

2. 抽象类特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。

一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。

3. 在effective c++上中提到,纯虚函数可以被实现(定义)(既然是纯虚函数,为什么还可以被实现呢?这样做有什么好处呢?下文中“巧用纯虚析构函数实现接口类”中将说明这一功能的目的。),但是,不能创建对象实例,这也体现了抽象类的概念。

抽象类只能用于其他类的基类,抽象类不能建立对象。抽象类不能用过做函数类型,函数返回值类型或者显示转换的类型。可以声明抽象类的指针和引用。而且,如果在抽象类的构造函数中调用了纯虚函数,那么其结果是不确定 。由于抽象类中提供一个默认的析构函数,由于抽象类析构函数可以被声明为纯虚函数,这是,应该至少提供该虚函数的一个实现。一个很好的实现方式是抽象类中提供一个默认的析构函数,该析构函数至少保证至少有析构函数的一个实现存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值