C++重载覆盖和隐藏,虚函数与纯虚函数

这几个概念都有一个共同点:函数名称相同,所以不免让人混淆,大致的区别如下:

重载(overload):
必须在一个域中,函数名称相同但是函数参数不同,重载的作用就是同一个函数有不同的行为,因此不是在一个域中的函数是无法构成重载的,这个是重载的重要特征

覆盖(override):
覆盖指的是派生类的虚拟函数覆盖了基类的同名且参数相同的函数,既然是和虚拟函数挂钩,说明了这个是一个多态支持的特性,所谓的覆盖指的是用基类对象的指针或者引用时访问虚拟函数的时候会根据实际的类型决定所调用的函数,因此此时派生类的成员函数可以"覆盖"掉基类的成员函数.
注意唯有同名且参数相同还有带有virtual关键字并且分别在派生类和基类的函数才能构成虚拟函数,这个也是派生类的重要特征.
而且,由于是和多态挂钩的,所以只有在使用类对象指针或者引用的时候才能使用上.
总之一句话:覆盖函数都是虚函数,反之不然~~

隐藏(hide):
指的是派生类的成员函数隐藏了基类函数的成员函数.隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义....
回到隐藏的定义中,前面已经说了有virtual关键字并且分别位于派生类和基类的同名,同参数函数构成覆盖的关系,因此隐藏的关系只有如下的可能:
1)必须分别位于派生类和基类中
2)必须同名
3)参数不同的时候本身已经不构成覆盖关系了,所以此时是否是virtual函数已经不重要了
当参数相同的时候就要看时候有virtual关键字了,有的话就是覆盖关系,没有的时候就是隐藏关系了

上面的解说大体把三者的区别给说清楚了,但是还有一些疑惑的地方,以下以代码例子说明.

很多人分辨不清隐藏和覆盖的区别,因为他们都是发生在基类和派生类之中的.但是它们之间最为重要的区别就是:
覆盖的函数是多态的,是存在于vtbl之中的函数才能构成"覆盖"的关系,而隐藏的函数都是一般的函数,不支持多态,在编译阶段就已经确定下来了.
 1  class  Base
 2  {
 3  public  :
 4  virtual   void  f(  float  x) {cout  <<   "  Base::f(folat)  "   <<  x  <<  endl;} 
 5  void  g(  float  x) {cout  <<   "  Base::g(float)  "   <<  x  <<  endl;} 
 6  } ;
 7 
 8  class  Derived:  public  Base
 9  {
10  public  :
11  virtual   void  f(  float  x) {cout  <<   "  Derived::f(float)  "   <<  x  <<  endl;} 
12  void  g(  int  x) {cout  <<   "  Deriver::g(int)  "   <<  x  <<  endl;} 
13  } ;
14 
15  int  main()
16  {
17  Derived d;
18  Base  *  pb  =&  d;
19  Derived  *  pd  =&  d;
20  pb  ->  f(  3.14f  );
21  pd  ->  f(  3.14f  );
22  pb  ->  g(  3.14f  );  //  输出结果:Base::g(float)3.14 
23  pd  ->  g(  3.14f  );  //  输出结果:Dervied::g(int)3 
24  return   0  ;
25 
26 
在调用f函数的时候,派生类Derived的f函数覆盖了基类Base的f函数,而派生类Derived的g函数隐藏了基类Base的g函数.
为什么?理由很简单,f函数是virtual函数,但是g函数不是.我们可以把Base类和Derived类看成这样的一个struct:
 1  struct  Base
 2  {
 3  void  ( * g)( float );  //  Base类型的函数指针,不可变
 4  struct  VTABLE  * __vptr;  //  虚拟函数指针数组,可变
 5  };
 6 
 7  void  __Baseg( float )
 8  {
 9  cout << " Base::g(folat) " <<
10  }
11 
12  struct  Derived
13  {
14  void  ( * g)( float );  //  Derived类型的函数指针,不可变
15  struct  VTABLE  * __vptr;  //  虚拟函数指针数组,可变
16  };
17 
18  void  __Derivedg( float )
19  {
20  cout << " Deriver::g(int) " <<
21  }
22 
23  struct  VTABLE
24  {
25  void  ( * f)( float );  //  函数指针
26  };
27 
28  void  __Basef( float )
29  {
30  cout << " Base::f(folat) " <<
31  }
32 
33  void  __Derivedf( float )
34  {
35  cout << " Deriver::f(int) " <<
36  }
37 

在程序编译的时候,函数指针f就已经是确定的了,但是__vptr根据不同的而有分别,而这个变化是运行期动态决定的.
也就是说:f的地址不可变,__vptr可变.
回到上面的例子中,Base *pb=&d;的时候只是用Derived类对象d的__vptr修改了Base类pb的__vptr指针,但是当Base类成员建立的
时候f函数指针就是不能改变的.

当函数被声明为virtual的时候,就激活了多态机制,程序在运行的时候会根据类型的实际类型到VTABLE中查找函数指针,因此对函数g的调用就是这样子的:
pb->__vptr->g();
而对f的调用就是一般的类成员函数指针的调用了:pb->f(),因为这个类型在程序编译的时候已经确认了,所以在程序运行的时候是不能发生改变的.

综上,可以把
Derived d;
Base *pb=&d;
的过程分解为:
d.g = __Derivedg;
d.__vptr->f = __Derivedf;
pb->g = __Baseg; // 这里根据指针的真正类型确定函数指针
pb->__vptr = d.__vptr; // 这里只是简单的指针赋值,因此访问到的就是Derived的函数了
最后在调用:
pb->f(3.14f);
pb->g(3.14f);
实际上是:
pb->__vptr->__Derivedf(3.14f);
__Baseg(3.14f);
这么写就明白最后在调用的时候为什么会用那样的结果了,可以看出多了一个__vptr这个间接层实现了所谓的"动态绑定".

最后,需要说明的一点是:实际上在c++中,非static和非virtual的函数指针并不会在一个class中保存它的函数指针,上面把函数g的指针写在struct里面只是为了方便说明这样的问题:在编译阶段这个函数就已经是确定的不可改变的了.特此说明一下.








虚函数与纯虚函数

虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。

定义一个函数为虚函数,不代表函数为不被实现的函数,定义它为虚函数是为了允许用基类的指针来调用子类的这个函数 
定义一个函数为纯虚函数,才代表函数没有被实现,定义他是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。 

有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。

1.为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果. 提醒子类去做应做的实现. 
2.为了效率,不是程序执行的效率,而是为了编码的效率.

### C++虚函数纯虚函数的概念及用法 #### 虚函数 虚函数C++ 中用于实现动态绑定或多态的一种机制。当一个基类的成员函数被声明为 `virtual` 时,它允许派生类对该函数进行重写(覆盖)。如果通过基类类型的指针或引用调用此虚函数,则实际执行的是派生类中对应的版本[^1]。 以下是虚函数的一个简单例子: ```cpp #include <iostream> class Base { public: virtual void display() { std::cout << "Base Class" << std::endl; } }; class Derived : public Base { public: void display() override { std::cout << "Derived Class" << std::endl; } }; void callDisplay(Base* obj) { obj->display(); } int main() { Base b; Derived d; callDisplay(&b); // 输出: Base Class callDisplay(&d); // 输出: Derived Class (多态行为) return 0; } ``` 在这个例子中,尽管 `callDisplay` 函数接受的是 `Base*` 类型的参数,但由于 `display()` 是虚函数,在传递 `Derived` 对象时会调用派生类中的实现[^2]。 --- #### 纯虚函数 纯虚函数是一种特殊的虚函数,它的定义形式是在函数原型后面加 `= 0`。含有纯虚函数的类被称为抽象类,无法直接实例化对象。纯虚函数的主要目的是强制要求派生类必须提供自己的具体实现[^3]。 下面是一个带有纯虚函数的例子: ```cpp #include <iostream> #include <string> class Animal { public: virtual ~Animal() {} // 建议给抽象类添加虚拟析构函数 virtual void speak() = 0; // 纯虚函数 }; class Dog : public Animal { public: void speak() override { std::cout << "Woof!" << std::endl; } }; class Cat : public Animal { public: void speak() override { std::cout << "Meow!" << std::endl; } }; void makeSound(Animal* animal) { animal->speak(); } int main() { Dog dog; Cat cat; makeSound(&dog); // 输出: Woof! makeSound(&cat); // 输出: Meow! return 0; } ``` 在此示例中,`Animal` 是一个抽象类,因为它包含了一个未实现的纯虚函数 `speak()`。任何继承自 `Animal` 的子类都必须实现该方法才能成为具体的可实例化的类[^4]。 --- #### 虚函数纯虚函数的区别 | **特性** | **虚函数** | **纯虚函数** | |-----------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| | 定义方式 | 使用 `virtual` 关键字 | 使用 `virtual` 加上`= 0` | | 是否有默认实现 | 可以有默认实现 | 不提供默认实现 | | 抽象类的关系 | 所属类不一定是抽象类 | 所属类必定是抽象类 | | 子类的要求 | 子类可以选择性地重载 | 子类必须实现 | --- #### 使用场景分析 - **虚函数适用场景**: 当希望某些功能可以在基类中给出通用实现,而其他特定的功能留给派生类自行决定是否重新定义时,适合使用普通的虚函数。 - **纯虚函数适用场景**: 如果某个接口对于所有派生类来说都是必需的,并且不存在统一的行为模式或者根本不需要在基类里实现逻辑的情况下,应该考虑采用纯虚函数来设计这种关系结构。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值