我对以下代码有一种奇怪的情况。请帮我澄清一下。
class B
{
public:
B();
virtual void print(int data=10)
{
cout << endl << "B--data=" << data;
}
};
class D:public B
{
public:
D();
void print(int data=20)
{
cout << endl << "D--data=" << data;
}
};
int main()
{
B *bp = new D();
bp->print();
return 0;
}
关于我预期的输出
[ D--data=20 ]
但实际上它是
[ D--data=10 ]
请帮忙。这对您来说似乎很明显,但我不了解内部机制。
答案 0 :(得分:32)
标准说(8.3.6.10):
虚函数调用(10.3)使用 中的默认参数 声明虚函数 由静态类型决定 表示的指针或引用 宾语。一个重要的功能 派生类不获取默认值 来自函数的参数 覆盖。
这意味着,由于您通过print
类型的指针调用B
,因此它使用默认参数B::print
。
答案 1 :(得分:25)
默认参数完全是编译时功能。即在编译时执行默认参数替换缺少参数的替换。因此,显然,成员函数的默认参数选择无法依赖于对象的动态(即运行时)类型。它总是依赖于对象的 static (即编译时)类型。
您在代码示例中编写的调用会立即由编译器解释为bp->print(10)
,而不管其他任何内容。
答案 2 :(得分:3)
通常,使用在特定范围内可见的默认参数。你可以做(但不应该)时髦的事情:
#include <iostream>
void frob (int x) {
std::cout << "frob(" << x << ")\n";
}
void frob (int = 0);
int main () {
frob(); // using 0
{
void frob (int x=5) ;
frob(); // using 5
}
{
void frob (int x=-5) ;
frob(); // using -5
}
}
在您的情况下,基类签名是可见的。为了使用派生的默认参数,您必须通过指向派生类的指针显式调用该函数,方法是通过这种方式声明,或者通过正确地转换它。
答案 3 :(得分:1)
默认参数值代表调用者传递。从调用者的角度来看,它适用于B类(而非D),因此它通过10(与B类一样)
答案 4 :(得分:0)
您的变量是B类型,因此将调用B的函数。要调用D,您必须将变量声明为D,或者转换为D。
答案 5 :(得分:0)
动态绑定使用的是vpointer和vtable。但是,动态绑定仅适用于函数指针。动态绑定参数没有机制。
因此,默认参数在编译器时间静态确定。在这种情况下,它由bp类型静态确定,bp类型是指向Base类的指针。因此,data = 10作为函数参数传递,而函数指针指向Derived类成员函数:D :: print。基本上,它调用D :: print(10)。
以下代码片段和结果输出清楚地说明了这一点:即使它调用Derived调用成员函数Derived :: resize(int),它也会传递Base类的默认参数:size = 0。
virtual void Derived :: resize(int)size 0
#include <iostream>
#include <stdio.h>
using namespace std;
#define pr_dbgc(fmt,args...) \
printf("%d %s " fmt "\n",__LINE__,__PRETTY_FUNCTION__, ##args);
class Base {
public:
virtual void resize(int size=0){
pr_dbgc("size %d",size);
}
};
class Derived : public Base {
public:
void resize(int size=3){
pr_dbgc("size %d",size);
}
};
int main()
{
Base * base_p = new Base;
Derived * derived_p = new Derived;
base_p->resize(); /* calling base member function
resize with default
argument value --- size 0 */
derived_p->resize(); /* calling derived member
function resize with default
argument default --- size 3 */
base_p = derived_p; /* dynamic binding using vpointer
and vtable */
/* however, this dynamic binding only
applied to function pointer.
There is no mechanism to dynamic
binding argument. */
/* So, the default argument is determined
statically by base_p type,
which is pointer to base class. Thus
size = 0 is passed as function
argument */
base_p->resize(); /* polymorphism: calling derived class
member function
however with base member function
default value 0 --- size 0 */
return 0;
}
#if 0
The following shows the outputs:
17 virtual void Base::resize(int) size 0
24 virtual void Derived::resize(int) size 3
24 virtual void Derived::resize(int) size 0
#endif
答案 6 :(得分:0)
基本上,当您使用这样的默认参数声明函数时会发生什么,您(隐式)声明和定义了一个内联重载,其中少了一个参数,该参数仅调用具有该参数值的完整函数。问题是,这个额外的重载函数不是虚拟的,即使该函数是。所以你在 B 中定义的函数等价于:
virtual void print(int data)
{
cout << endl << "B--data=" << data;
}
void print() { print(10); }
这意味着当您调用 print()
(不带参数)时,您得到的函数是基于静态类型的(如果您感到困惑,则为 B)。然后调用 print(int)
是虚拟的,因此使用动态类型。
如果您希望这个默认参数是虚拟的,您需要显式定义重载函数(作为虚拟)以使其工作。