C++ 中函数重载、覆盖与隐藏详解

 更新时间:2021年10月01日 23:00:25   投稿:lqh  
这篇文章主要介绍了C++ 中函数重载、覆盖与隐藏详解的相关资料,需要的朋友可以参考下

C++ 中函数重载、覆盖与隐藏详解

在C++语言中,函数扮演着很重要的角色,不管面向过程设计,还是基于对象设计;不管是面向对象编程,还是基于泛型编程,函数都可以随处而见。在谈论C++中的函数重载、覆盖和隐藏之前,先回顾下函数的基础知识。

成员函数的重载、覆盖与隐藏

成员函数的重载、覆盖(override)与隐藏很容易混淆,C++程序员必须要搞清楚
概念,否则错误将防不胜防。

8.2.1 重载与覆盖

成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

覆盖是指派生类函数覆盖基类函数,特征是:

(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

示例8-2-1 中,函数Base::f(int)与Base::f(float)相互重载,而Base::g(void)

被Derived::g(void)覆盖。

#include <iostream>
using namespace std;
class Base
{
public:
void f(int x){ cout << "Base::f(int) " << x << endl; }
void f(float x){ cout << "Base::f(float) " << x << endl; }
virtual void g(void){ cout << "Base::g(void)" << endl;}
};
class Derived : public Base
{
public:
virtual void g(void){ cout << "Derived::g(void)" << endl;}
};
void main(void)
{
Derived d;
Base *pb = &d;
pb->f(42); // Base::f(int) 42

pb->f(3.14f); // Base::f(float) 3.14
pb->g(); // Derived::g(void)
}

示例8-2-1 成员函数的重载和覆盖

8.2.2 令人迷惑的隐藏规则

本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。

这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

示例程序8-2-2(a)中:

(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

#include <iostream>
using namespace std;
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};

示例8-2-2(a)成员函数的重载、覆盖和隐藏
据作者考察,很多C++程序员没有意识到有“隐藏”这回事。由于认识不够深刻,
“隐藏”的发生可谓神出鬼没,常常产生令人迷惑的结果。
示例8-2-2(b)中,bp 和dp 指向同一地址,按理说运行结果应该是相同的,可事
实并非这样。

void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}

示例8-2-2(b) 重载、覆盖和隐藏的比较

8.2.3 摆脱隐藏

隐藏规则引起了不少麻烦。示例8-2-3 程序中,语句pd->f(10)的本意是想调用函
数Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隐藏了。由于数字10
不能被隐式地转化为字符串,所以在编译时出错。

class Base
{
public:
void f(int x);
};
class Derived : public Base
{
public:
void f(char *str);
};
void Test(void)
{
Derived *pd = new Derived;
pd->f(10); // error
}

下面是补充大家可以参考一下

函数的声明包括函数的返回值类型,函数名称,参数列表(参数的类型、参数的个数、参数的顺序)。例如,声明一个两个整数之和的函数,int iAdd(int iNum1,int iNum2);而函数的定义可以理解为对函数功能的详尽而准确的解说,通俗点,就是实现函数“how to do?”的效能。两个整数之和函数的定义如下:

int iAdd(int iNum1,int iNum2)
{
   return (iNum1+iNum2);
}

       仔细观察函数的声明和定义,我们不难发现,函数的定义就是除掉函数声明后面的分号,换之成大括号,在大括号里面实现函数的功能。虽然在某些情况下,可以容许不对函数进行声明,只需要对函数定义,就能调用函数了。但是,强烈建议养成先声明函数,然后再定义函数,最后在调用函数的良好习惯。关于函数的基础知识,暂时论述到这。

       现在,进入本文的主题。函数重载(function overload),它是在同一可访问区域内部声明具有几个不同参数列(参数的类型、参数的个数,参数的顺序)的相同函数名称的一种机制,函数的调用是根据不同的参数类型和最佳匹配原则确定最终使用那个函数。函数覆盖(function override)是在派生类中完全一致性地声明了父类中的函数,区别在于函数定义中的大括号之间的内容可以不同,并且该函数在父类中有关键字virtual标识;函数隐藏(function hide)是指在派生类中函数与父类函数完全一致,但是在父类中该函数没有关键字virtual标识,或者是指在派生类中函数与父类的函数名相同,参数列表不一样,父类中的该函数可有也可无关键字virtual标识。

函数重载的特征:相同的范围内(在同一个类中),函数的名称相同,参数列表不同,virtual关键字可有可无;函数覆盖的特征:在不同的范围内(父类与派生类),函数的名字相同,参数列表相同,父类函数必须有关键字virtual;函数隐藏的特征:在不同范围内(父类与派生类),函数的名字相同,参数列表相同,但是父类函数没有关键字virtual或者,参数列表不相同,父类函数中virtual关键字可有可无。

为了直观地理解,请看下面的代码。

#include<iostream>
using namespace std;
class A
{
public:
   void print(int iNum)
   {
     cout<<"在类A中,参数类型是整型"<<endl;
   }
   void print(float fNum)
   {
     cout<<"在类A中,参数类型是单精度浮点型"<<endl;
   }
 virtual void print(void)
   {
     cout<<"在类A中,参数类型是空类型"<<endl;
   }
};
class B:public A
{
public:
   void print( void)
   {
     cout<<"在类B中,参数类型是空类型"<<endl;
   }
 
   void print(int iNum)
   {
     cout<<"在类B中,参数类型是整型"<<endl;
   }
 };
int main()
{
   A a;
   B b;
  //函数的重载
   a.print();
   a.print(1);
   a.print(1.0f);
  //函数的覆盖
   b.print();
 //函数的隐藏
   b.print(1);
  return 0;
}

      运行结果是:

   在类A中,参数类型是空类型
   在类A中,参数类型是整型
   在类A中,参数类型是单精度浮点型
   在类B中,参数类型是空类型
   在类B中,参数类型是整型

     通过上述代码和运行的结果,简明地知道了函数重载,覆盖和隐藏。恰当里利用这些特性,可以编写出更加有效、清晰和精简的代码。                           

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

相关文章

  • C语言链接属性的实践应用

    C语言链接属性的实践应用

    C语言中链接属性决定如何处理在不同文件中出现的标示符,下面这篇文章主要给大家介绍了关于C语言链接属性的实践应用,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • C语言结构体内存对齐详解

    C语言结构体内存对齐详解

    大家好,本篇文章主要讲的是C语言结构体内存对齐详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • 带你粗略了解C++回文链表

    带你粗略了解C++回文链表

    这篇文章主要介绍了Python实现的判断回文链表算法,结合实例形式分析了Python针对链表是否为回文链表进行判断的相关算法实现技巧,需要的朋友可以参考下
    2021-08-08
  • 详解C语言中的错误报告errno与其相关应用方法

    详解C语言中的错误报告errno与其相关应用方法

    这篇文章主要介绍了C语言中的错误报告errno与其相关应用方法,包括errno和strerror以及perror的介绍,需要的朋友可以参考下
    2015-08-08
  • C++内存管理之简易内存池的实现

    C++内存管理之简易内存池的实现

    大家好,本篇文章主要讲的是C++内存管理之简易内存池的实现,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2021-12-12
  • C++树之遍历二叉树实例详解

    C++树之遍历二叉树实例详解

    这篇文章主要给大家介绍了关于C++树之遍历二叉树的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • C++音乐播放按钮的封装过程详解

    C++音乐播放按钮的封装过程详解

    此篇文章用于记录学习C++封装音乐播放按钮,封装将对象的属性和行为作为一个整体,表现生活中的事物、将属性和行为加以权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • C++17实现flyweight_factory模板类及使用示例详解

    C++17实现flyweight_factory模板类及使用示例详解

    这篇文章主要为大家介绍了C++17实现flyweight_factory模板类及使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 用标准c++实现string与各种类型之间的转换

    用标准c++实现string与各种类型之间的转换

    这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
    2013-09-09
  • C++中getline()、gets()等函数的用法详解

    C++中getline()、gets()等函数的用法详解

    这篇文章主要介绍了C++中getline()、gets()等函数的用法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02

最新评论