C++虚函数与纯虚函数区别和重载的概念

来源:互联网 发布:动易cms 6.8 模板 编辑:程序博客网 时间:2024/05/22 16:40

1. 虚函数

1.1 定义

虚函数是在类中添加函数的修饰关键字“Virtual”,且对它进行定义,这时函数变被指定为虚函数。格式如下:
class CInterFace{public:    virtual void Func_Virtual(){}};

1.2 作用

虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

1.3 引入的原因

为了方便使用多态特性,常常需要在基类中定义虚函数。下面顺便列出C++实现多态的三个条件:
(1)在基类中将这些成员函数声明为虚函数,并实现(必须要实现)。
(2)在派生类中也声明这些成员函数并实现(必须实现),基类和派生类的这些函数必须同名,而且其形参个数和类型,返回值类型必须与基类中的这些函数完全相同。
(3)将派生类对象赋给基类的指针变量或者引用。至此,多态实现,可用基类指针或引用调用派生类的方法(符合多态条件的方法,而不是普通方法)。

相应的不是多态的情况:
(1)基类中声明为虚函数,派生类中也声明为虚函数,并且也同名。但是派生类中该函数的形参类型或者形参个数和基类中的不相同。此时,多态不满足,派生类和基类的这两个虚函数仅仅是隐藏关系,没有虚特性。
(2)基类中声明为虚函数,派生类中也声明了一个同名函数,但没有使用virtual,并且形参类型或者形参个数和基类不相同。这时候基类中的虚函数也丢失虚特性,派生类的该函数不会被虚化,当然也就够不成多态,这两个函数也仅仅是隐藏关系。
(3)基类中的函数不是虚函数,派生类中声明为虚函数,它们同名,这时也够不成多态,派生类的虚函数没有虚特性,它们也是隐藏关系。
(4)基类和派生类的两个函数同名,都是虚函数,形参的个数和类型也都相同,但是返回值类型不同,这时编译会报错,因为两个虚函数在隐藏时,返回值类型发生了冲突,因此隐藏发生错误。注意,如果这两个函数不是虚函数,这不会报错,隐藏会成功;同时,如果派生类中是虚函数,基类中不是虚函数,也不过报错,隐藏也是成功的。这也说明,虚化并隐藏时,返回值类型一定要保持相同。

2. 纯虚函数

2.1 定义

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
class CInterFace{public:    virtual void Func_PureVirtual()=0;};

2.2 引入作用

(1)为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
(2)在很多情况下,基类本身生成对象是不合情理的。例如,用一个虚拟的东西去实例化一个具体的对象,那从逻辑上是肯定不对的。
所以,为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()=0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

2.3 目的

定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

3. 虚函数和纯虚函数总结

(1)C++虚函数与纯虚函数用法与区别,虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
(2)虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义
(3)虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。核心理念就是通过基类访问派生类定义的函数
(4)虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口
(5)虚函数的定义形式:virtual function1(){method body}
  纯虚函数的定义形式:virtual function2() = 0;
在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
(6)如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生(也就是不能定义对象,只能派生),因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
(7)在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。目的是必须去释放资源。安全性的角度
(8)友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
(9)析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。


4. 虚函数和重载的关系

4.1 Class成员函数重载的定义

在一个类内,如果存在若干个同名函数,而且这些函数之间可以用形参个数或者形参类型区分开来的时候(注意不能靠函数返回类型区分),这几个函数就互为重载函数。这时,当你通过类对象调用这几个函数时,编译器就可以通过你传递的实参个数或者类型,去匹配相应的函数,而不会发生歧义。这也就是重载函数的作用所在(让你可以使用若干个同名函数)。

4.2 Class成员函数重载的注意事项

(1)重载绝对不会发生在基类和派生类之间,如上例所示。当基类和派生类中存在同名函数时,无论同名函数的形参个数或者类型是否相同,派生类中的同名函数都会将基类中的同名函数隐藏掉,因此它们是隐藏关系,而不是重载关系。关于隐藏,后边会提到。
(2)在同一个类中,重载函数之间必须依靠形参个数或者形参类型来进行区分,不能依靠返回类型。也就是说,如果同一个类中的两个同名函数形参个数和类型完全相同,但是返回值类型不同,这时候编译就会报错,因为当你通过类对象调用该同名函数时,编译器会出现二义性,不知道该选择哪个函数。记着,重载必须靠形参来区分。
(3)在同一个类中,虚函数和虚函数,虚函数和普通函数之间也可以重载,规则完全同上。

4.3 隐藏和覆盖的概念

4.3.1 隐藏

隐藏只能出现在基类和派生类之间,而不能发生在同一个类内(比如上述2中,只会引起编译器出现二义性)。当基类和派生类中存在同名函数时,无论同名函数的形参个数或者类型是否相同,派生类中的同名函数都会将基类中的同名函数隐藏掉,而不会是重载关系。这时,当你通过派生类对象调用该同名函数时,只能访问派生类的该函数,如果硬要访问基类的该函数,则需要在函数名前加上类作用域。

4.3.2 覆盖

覆盖也只能出现在基类和派生类之间,当派生类和基类中的存在同名函数,且参数个数和参数类型完全相同,并且基类中的该函数有virtual修饰(派生类中的该函数可有可无),则派生类的该函数覆盖掉基类的该函数。该性质用来实现多态。