下面有关派生类与基类中存在同名函数 fn:

  1. class A
  2. {
  3. public:
  4. void fn()
  5. {}
  6.  
  7. void fn(int a)
  8. {}
  9. };
  10.  
  11. class B : public A
  12. {
  13. public:
  14. void fn()
  15. {}
  16. };
  17.  
  18. int main()
  19. {
  20. B b;
  21. b.fn();
  22. return ;
  23. }   

1、以上代码编译为什么不能通过? (问题在第21行,编译器会报怨说,B中,并不存在fn(int)的函数)。

2、编译器这样做(即不允许通过这样的代码)的好处是什么?

  相信这是一个非常之普遍的问题了,在众多经典的C++书籍中,都会将之列为一个重要C++问题,详细地深入地讲解。我这里仅能简单回答,可能对有同样疑问的同学,有个快速了解的作用。由于出差在外,非常不方便,回答问题时既不能详细调试,也不能做必要的查经求典的动作(手头没书),犯错的地方,请大家指正,我会及时修订。

回答如下:

  你涉及到一个C++中的重要的知识点。即:同名函数的重载动作,只发生在自由函数(即非成员),及同一个class/struct内部的函数之间。而不能跨越基类和派生类。当派生类写一个和基类同名(无论参数列表相同或不相同)的函数时,此时发生的动作叫“覆盖”。覆盖的意思,就是基类的同名函数,在派生类内,将变得无法直接调用(但可以间接调用)。

  首先,我们还是针对问题的本质,简化一下代码,抛弃无直接相关的枝节。

  1. struct A
  2. {
  3. void foo(int d)
  4. {
  5. cout << "A::foo - int" << endl;
  6. cout << d << endl;
  7. }
  8. };
  9.  
  10. struct B : public A
  11. {
  12. void foo(double d) //覆盖了A::foo(int d);
  13. {
  14. cout << "B::foo - double" << endl;
  15. cout << d << endl;
  16. }
  17. };
  18.  
  19. int main()
  20. {
  21. A a;
  22. a.foo();
  23.  
  24. B b;
  25. b.foo(10.2);
  26. b.foo(); //调用的仍然是B::foo,虽然2明显是个整数
  27.  
  28. return ;
  29. }

以上代码,运行之后输出结果大致如下:(注释为后加内容)

A::foo - int
10
B::foo - double
10.2
B::foo - double //调用的仍然是B::foo,虽然2明显是个整数
2

那么,要如何才能调用基类的foo(int )呢?

方法有两种,其一为“临时法”:

  1. B b;
  2. b.A::foo(); //显式调用A范围内的foo

  其二就该叫“终身法”,哈哈这名字又是我瞎起的,更好的叫法,应叫“引狼入室法”,别掰了。回忆一下 namespace 的三种用法,其中一种称为“using declaration/使用声明”, 这里可以用上类似的代码(很多情况下,class/struct域,和一个namespace有相同的功能)。请看代码:

  1. struct B : public A
  2. {
  3. using A::foo; //通过“使用声明”,引入了A::foo……
  4.  
  5. void foo(double d)
  6. {
  7. cout << "B::foo - double" << endl;
  8. cout << d << endl;
  9. }
  10. };

  现在要调用时:

  1. int main(void)
  2. {
  3. B b;
  4.  
  5. b.A::foo(); //调用的……当然是A::foo(int)
  6. b.foo(); //调用的……也是A::foo(int)
  7.  
  8. b.foo(10.234); //调用的……B::foo(double)
  9.  
  10. return ;
  11. }

  接下回答“编译器这样做的好处是什么?”

  这是为了避免“非恶意性的错误”。这也是C++语言设计中的一个重要原则:语法规则,会尽量让程序员避免“无意的错误”,但并不去管"有意,恶意,不怀好意的错误"。

  以A,B代码为例,想像一下,如果我是A的作者,你是B的作者。

  由于foo并不是“virtual/虚”函数,所以二者之间可能是各写各的。如果一开始我没有在A内写那个foo(int)函数,而你因为需要,在B中写了一个foo(double)函数,并且用起来很爽----因为此时基类中没有同名函数,所以你无论写 foo(2)也好,还是写foo(2.0)也好,由于int 到 double的转换是安全的,所以,两次都非常爽地调用了B自己的foo(double) 。通常这也是我们所要的。

  接着有一天,作为开发小组成员,我在修改A时,我觉得需要一个 void foo(int);于是我在A中加了这个函数。并且由于它不是virtual,最主要是: 由于我是基类的作者,我哪管得了天下到底有几个人派生了我的A类呢? 所以我才懒得告诉你A类中多了一个很普通的函数。但现在情况如何呢?如果按派生类和基类的普通同名函数也可以构成重载关系,完了完了,当你拿到A的新版,重新编译项目,一切正常,编译器不报任何错,可是你前面所说的那段代码却突然改为调用基类的那个,我刚刚写的同名函数,这还了得!

  通常,基类的作者,都是比较牛逼的人,为什么?因为他肩负着更多的责任。当一个类,以“基类”的形式提供出去以后,通常,它就不应该——有同学抢话说:“它就不应该再修改”——那倒不是(我们又不是在写COM组件),基类也要发展,也有版本升级,否则类库如何取得进步? 正确要求是:通常,它所做的改动,都应该是向前兼容的。即基类的修改,可以为派生类提供更多的新功能,但不应该影响了派生类原来已有的功能。在此要求下,如果C++的在本例中的规则是:写基类的人只是根据自己需要,写了一个普普通通的成员函数,结果就造成了派生类的原有行为被偷偷地修改,那这也太为难基类作者,他最终会每写一个函数,都使劲猜测会不会存在一个亚洲的,美洲的,南极大陆上的某个派生类的作者,在过去,或现在,或将来的时间写的某个函数正好同名,这太累了!基类的作者再牛,但你也不该把他逼到这份上啊!

  C++之父是英明的!OH Yeah~~~

  原文地址:基类和派生类之间的同名函数,存在重载吗?

  and more:c++(重载、覆盖、隐藏)

(转) C++中基类和派生类之间的同名函数的重载问题的更多相关文章

  1. c# 中基类变量指向派生类对象的实例化

    这一篇文章转载自:http://www.xuebuyuan.com/390279.html 我对这篇文章进行了一一的验证,确实是这样子的,也明白了很多东西,觉得很有用,转载过来希望能够帮助大家. 1. ...

  2. c++中基类与派生类中隐含的this指针的分析

    先不要看结果,看一下你是否真正了解了this指针? #include<iostream> using namespace std; class Parent{ public: int x; ...

  3. 详解C++中基类与派生类的转换以及虚基类

    很详细!转载链接 C++基类与派生类的转换在公用继承.私有继承和保护继承中,只有公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外的基类所有成员,基类的公用或保护成员的访问权限在派生类中 ...

  4. c++ 的类 和 类继承, 什么是c++中的基类和派生类?

    闲云潭影日悠悠,物换星移几度秋 你既然已经做出了选择, 又何必去问为什么选择.鬼谷绝学的要义, 从来都不是回答, 而是抉与择 普通类 #ifndef TABTENN0_H_ #define TABTE ...

  5. 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)

    [源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...

  6. C++中public,protected,private派生类继承问题和访问权限问题

    C++中public,protected,private派生类继承问题和访问权限问题 当一个子类从父类继承时,父类的所有成员成为子类的成员,此时对父类成员的访问状态由继承时使用的继承限定符决定. 1. ...

  7. C++学习21 基类和派生类的赋值

    在C/C++中,经常会发生数据类型转换,例如整型数据可以赋值给浮点型变量,在赋值之前,先把整型数据转换为浮点型:反过来,浮点型数据也可以赋值给整型变量. 数据类型转换的前提是,编译器知道如何对数据进行 ...

  8. 基类和派生类--this

    基类指针在程序运行的时候的确指向的是一个派生类的对象,但指针的类型仍然是基类指针.C++是一种强类型语言,因此不能用基类指针类型的指针直接调用派生类:而且,同一个类可能有多种不同的派生类,因此不知道实 ...

  9. C++:基类与派生类对象之间的赋值兼容关系

    4.5 基类与派生类对象之间的赋值兼容关系 在一定条件下,不同类型的数据之间可以进行类型转换,例如可以将整型数据赋给双精度型变量. 在赋值之前,先把整型数据转换为双精度型数据,然后再把它双精度型变量. ...

随机推荐

  1. JMeter学习-021-JMeter 定时器(Synchronizing Timer)之集合点应用

    性能测试中我们经常提到一个概念就是“并发”,其实在实际真实的性能测试中是不存在真正的并发的.为了更真实的模拟对一个请求的并发测试场景,我们通常设置一个集合点,JMeter中提供了这样的一个功能设置. ...

  2. centos升级支持到C++11, gcc4.8.2

    升级到4.8[这个应该是目前最新的啦,不过网上查的话已经到5.2啦,感觉落后一点比较稳,当然还有就是这个版本是新的里面使用最多的] wget http://people.centos.org/tru/ ...

  3. (转)MVC设计模式学习总结

    原文  : chenchun的博客 http://www.cnblogs.com/jobscn/archive/2011/11/08/2240725.html -------------------- ...

  4. 对于家政020 APP平台如何走出资本寒冬?

    成都亿合科技小编了解到,随着O2O烧钱大战过去,网络上流传的一份O2O项目死亡名单上显示,近年来,汽车.社区.旅游.教育等16个领域的多个O2O项目关门大吉,仅外卖餐饮O2O项目倒闭的就有十几个.只有 ...

  5. 笔记本自带 WiFi 功能

    在寝室,动网速基本崩溃.平时打电话什么的都得到阳台,有时候还听不清声音.对于学校的环境,我不说什么了. 笔记本可以上网,那就要满足手机等移动电子设备上网的上网需求. WiFi 热点就显得尤为重要了. ...

  6. java实现求数组中元素第二大的元素

    /** * 找出数组中数第二大的值 * @param array * @date 2016-9-25 * @author shaobn */ public static void getMethod_ ...

  7. ASP.NET MVC 3 CheckBoxList 的使用

    在以前的 ASP.NET MVC 中可以直接使用 CheckBoxList,但后来不知道什么原因在 MVC 中移除了 CheckBoxList,所以默认情况下 ASP.NET MVC 3 中是没有 C ...

  8. android 消息机制

    一.Android应用程序的主线程主要用于更新UI界面,并且主线程不能做耗时操作,否则会引起ANR:这种情况下需要开一个子线程来进行耗时操作,动作完成之后,子线程发消息给主线程通知其更新UI显示,常见 ...

  9. JavaScript中,格式化DateTime

    参考 http://www.cnblogs.com/best/p/3537030.html new Date(parseInt(list[i].StudyTime.replace(/\D/igm, & ...

  10. SUDTOJ 3323园艺问题 (线段树)

    园艺问题 Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描述 本巨养了一盆双色茉莉.这种花有一种特点:第i朵花在第Di天盛开,刚开时是紫色的 ...