C++虚函数 - STEMHA's Blog

C++虚函数

虚函数与纯虚函数

定义虚函数的目的:为了允许用基类的指针来调用子类的这个函数。
定义纯虚函数的目的:为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

注意:

  • 定义一个函数为虚函数,不代表函数为不被实现的函数。
  • 定义一个函数为纯虚函数,才代表函数没有被实现。

C++ 中,基类必须指出希望派生类重写哪些函数,定义为 virtual 的函数(虚函数)是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

虚函数

虚函数定义:被virtual关键字修饰的成员函数。

使用规则:

  • 为了指明函数为虚函数,在其返回类型前面加上保留字 virtual。
  • 除了构造函数之外,任意非 static 成员函数都可以是虚函数。
  • virtual 保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
  • 虚函数声明基类和子类要一致:在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同.
    • 虚函数在派生类中重新定义时候,如果返回的是对基类型的引用或是指针,那么派生类的虚函数可以返回基类函数返回类型的派生类引用或指针,而不是一定要求一样的,primer上看到的。
  • 虚函数符号子类可以不加virtual:c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
    • 如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数;
    • 如果派生类没有覆盖基类中的某个成员函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。

作用:

  • 启用动态绑定,实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。 如果成员默认为非虚函数,对非虚函数的调用在编译时确定。
  • 虚函数是重载的一种表现形式,是一种动态的重载方式。
  • 虚函数是基类希望派生类继承函数的缺省实现和重载的成员函数。当基类的指针或者引用指向派生类的对象的时候,这时候,如果使用基类指针或引用调用一个虚函数,实际上调用的是继承类的版本;

注意:

  • 当我们使用指针或引用对虚函数的调用将被动态绑定,即根据所绑定的对象不同,所执行的函数不同,有可能是基类的,也有可能是派生类的。
  • 相对的,非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。

在 C++ 中,基类必须指出希望派生类重写哪些函数,定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。//也就是说虚函数部分,是不被作为基类部分在派生类中继承的,而是被重新定义的,画一画继承的图可能会更清晰地理解这一点。

纯虚函数

纯虚函数(pure virtual)没有实现,被初始化为0;规定:在函数形参表后面写上 = 0 以指定纯虚函数。

1
virtual void fun(void) = 0;

声明一个纯虚函数(pure virtual)的目的:

  • 是为了让派生类只继承函数接口,也就是上面说的接口继承。这个类(包含纯虚函数的类)的存在只是为了让其他类继承。
  • 为了令继承类继承函数接口以及做一份强制性实现。

声明非纯虚函数(impure virtual)的目的:

  • 是让继承类继承该函数的接口和缺省实现。

非虚函数

一般成员函数,无virtual关键字修饰。
非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定,而不是像

override关键字

C++11新标准允许派生类显式注明它使用某个成员函数覆盖了它继承的虚函数,具体做法是在形参列表或const限定符或引用限定符后面添加一个关键字override

规定:

  • 基类函数必须有 virtual 关键字,不能有 static 。
  • 不在同一个作用域(分别位于派生类与基类),函数名字相同,参数相同。

优点:

  • 如果我们使用了override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将会报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
pulic:
virtual void func() const;
};
class B: A
{
pulic:
virtual void func() const override final; //OK
};
class C: B
{
pulic:
virtual void func()const; //error, B::func is final
};

final关键字

禁用继承

在C++11中允许将类标记为final,方法时直接在类名称后面使用关键字final。如果继承该类会导致编译错误。

1
2
3
4
class A final
{
/*...*/
};

禁用重写

在C++中,如果把某个函数指定为final,使之后任何尝试覆盖该函数的操作都引发错误。final关键字至于方法参数列表后面。

1
2
3
4
5
6
class A
{
public:
A();
virtual void Method() final;
};

C++抽象类

抽象类:包含有(或继承)一个或多个纯虚函数的类是抽象类。在C#,Java中用abstract定义抽象类,而在C++中有抽象类的概念,但没有这个关键字。

规定:

  • 抽象类只能用作其他类的基类,不能建立抽象类对象。
  • 抽象类不能用作参数类型、函数返回类型或显式转换的类型。
  • 可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。

特点:

  • 由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象,也就是说抽象类无法实例化。

C++接口

C++接口满足以下条件:

  1. 类中没有定义任何的成员变量
  2. 所有的成员函数都是纯虚函数
  3. 所有的成员函数都是公有的
  4. 接口是一种特殊的抽象类

实现继承和接口继承

实现继承:派生类同时继承基类的接口和实现。
接口继承:派生类不继承基类的实现,只会继承基类的接口。

包含虚函数的类和包含纯虚函数的类有什么别称吗?

如果类中包含有纯虚函数,则这个类是抽象类,而且由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

如果有虚函数和正常函数就是普通的类。

总结

纯虚函数:要求继承类必须含有某个接口,并对接口函数实现。
虚函数:继承类必须含有某个接口,可以自己实现。也可以不实现,而采用基类定义的缺省实现
非虚函数:继承类必须含有某个接口,必须使用基类的实现。

参考资料

C++ primer 第四版
C++接口继承与实现继承
C++虚函数
C++开发者眼中的Java关键字abstract

多态

任何可以在基类对象上执行的操作也可以通过派生类对象使用。

可以使用基类类型的指针或引用来引用派生类型对象,所以,使用基类类型的引用或指针时,不知道指针或引用所绑定的对象的类型:基类类型的引用或指针可以引用基类类型对象,也可以引用派生类型对象。无论实际对象具有哪种类型,编译器都将它当作基类类型对象。将派生类对象当作基类对象是安全的,因为每个派生类对象都拥有基类子对象。而且,派生类继承基类的操作。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×