虚函数与默认实参

本文探讨了C++中虚函数使用默认实参时可能遇到的问题。特别关注通过基类引用或指针调用虚函数时,不同版本的默认实参会引起的混淆。举例说明了基类和派生类中相同虚函数的不同默认实参导致的行为差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近开始重新阅读C++ Primer,不愧是经典书籍,每次翻阅都有新的收获。

今日看书的过程中发现一个过去没有注意的细节,以一篇博文记录,也希望阅读本文的朋友能注意。

第十五章

15.2.4

5.虚函数与默认实参

像其他任何函数一样,虚函数也可以有默认实参。通常,如果有用在给定调用中的默认实参值,该值将在编译时确定。如果一个调用省略了默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数生命中定义的值,如果通过派生类的指针或者引用调用虚函数,则默认实参是在派生类的版本中声明的值。

在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。如果通过基类的引用或指针调用虚函数,但实际执行的是派生类中定义的版本,这时就可能会出现问题。在这种情况下,为虚函数的基类版本定义的默认实参将传给派生类定义的版本,而派生类版本是不同的默认实参定义的。

 

如果基类virtual函数中的默认实参和派生类中的默认实参不同,则一定会引起错误。原因在于这个值是在编译时确定,而且只与调用函数的类型有关,而和动态类型无关。也就是说,当动态绑定发生的时候,想要使用派生类中的默认实参,是使用的确实基类的!

以一个例子说明这个问题。

[cpp]  view plain copy
  1. //MyClass.h  
  2.    
  3. #pragma once  
  4. class MyClass  
  5. {  
  6. public:  
  7.        
  8.     MyClass(void);  
  9.     ~MyClass(void);  
  10.     virtual void f(int i=2);  
  11. };  
  12.    
  13.    
  14. //MyClass.cpp  
  15. #include "MyClass.h"  
  16. #include<iostream>  
  17. using namespace std;  
  18.    
  19. MyClass::MyClass(void)  
  20. {  
  21.    
  22. }  
  23.    
  24.    
  25. MyClass::~MyClass(void)  
  26. {  
  27. }  
  28. void MyClass::f(int i)  
  29. {  
  30.     cout<<"In MyClass "<<i<<endl;  
  31. }  
  32. //Derived.h  
  33. #pragma once  
  34. #include "myclass.h"  
  35. class Derived :  
  36.     public MyClass  
  37. {  
  38. public:  
  39.     Derived(void);  
  40.     ~Derived(void);  
  41.     void f(int i=3);  
  42. };  
  43.    
  44.    
  45. //Derived.cpp  
  46. #include "Derived.h"  
  47. #include<iostream>  
  48. using namespace std;  
  49.    
  50. Derived::Derived(void)  
  51. {  
  52. }  
  53.    
  54.    
  55. Derived::~Derived(void)  
  56. {  
  57. }  
  58. void Derived::f(int i)  
  59. {  
  60.     cout<<"In Derived "<<i<<endl;  
  61. }  
  62. //main.cpp  
  63. #include<iostream>  
  64. #include"MyClass.h"  
  65. #include"Derived.h"  
  66. using namespace std;  
  67.    
  68. int main()  
  69. {  
  70.     MyClass my;  
  71.     Derived de;  
  72.        
  73.     my.f();  
  74.     de.f();  
  75.    
  76.     MyClass *p=&my;  
  77.    
  78.     p->f();  
  79.     p=&de;  
  80.     p->f();  
  81. }  
运行结果为:
In MyClass 2
In Derived 3
In MyClass 2
In Derived 2

从运行结果可以清晰地看书,尽管实现了动态绑定,但是在p指向de以后,调用p->f()使用的参数确是基类中的默认实参。

还要注意的就是,默认实参可以在源文件或者头文件中进行指定,但是只能指定一次。通常应该在函数声明中指定默认实参,并且将该声明放在合适的头文件中。如果未放在头文件,而是放在函数定义(源文件)中,那么,要想使用这个默认实参,必须包含此cpp源文件。


见http://blog.csdn.net/sjyzhxw/article/details/7585857

### C++ 中虚函数的继承行为 在 C++ 中,当一个类定义了虚函数后,在其派生类中这些虚函数会被自动继承并保持 `virtual` 特性[^1]。这意味着即使派生类没有显式地重新声明某个函数为虚拟,只要基类中有相应标记为 `virtual` 的同名成员函数,那么这个特性也会传递下去。 对于派生类而言,可以选择是否提供一个新的实现来替代基类的行为: - 如果派生类提供了相同签名的方法(即方法名称、返回类型以及参数列表完全一致),则此操作称为 **重写** (override),此时调用该方法将会执行派生类内的版本; - 若派生类未提供这样的实现,则默认情况下仍然可以访问来自基类的那个原始实现; 需要注意的是,为了确保正确地覆盖而非隐藏基类中的虚函数,应当严格遵循原函数原型,包括但不限于函数的名字拼写准确性、参数的数量和类型匹配度以及常量修饰符的一致性等问题[^2]。 此外,每当创建含有虚函数的对象实例时,编译器会在内部为其分配额外的空间用于存储指向虚函数表(vtable)的指针,从而支持动态绑定机制的工作原理。而当存在多重层次结构下的继承关系时,子类不仅会继承父类所有的虚函数条目,还会将自己的新加入的虚函数追加至原有表格之后[^4]。 ```cpp #include <iostream> using namespace std; class Base { public: virtual void show() { cout << "Base Class"; } }; class Derived : public Base { public: void show() override { cout << "Derived Class"; } // 正确重写了show() }; ``` 上述代码展示了如何在一个简单的单继承体系下利用 `virtual` 和 `override` 来管理不同级别的显示逻辑。 #### 缺省参数处理 关于缺省参数的情况,值得注意的是,默认参数值是在编译期解析的,并且取决于静态类型而不是运行时期的实际对象类型。因此,即便派生类改变了缺省参数的具体数值,最终的效果仍依赖于指针或引用来决定哪个类别的默认值得以应用[^5]。 ```cpp // 基类设置了一个带有缺省参数的虚函数 void fun(int num = 1); // 派生类可能改变这一缺省参数 void fun(int num = 2); ``` 在这种情形下,如果通过基类类型的指针去调用 `fun()` 方法而不指定实参的话,将采用基类所提供的那个缺省值;反之亦然。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值