C++ 虚函数的缺省参数问题

前些日子,有个同学问我一个关于虚函数的缺省参数问题。他是从某个论坛上看到的,但是自己没想通,便来找我。现在分享一下这个问题。先看一小段代码:

#include <iostream>

using namespace std;

class A
{
public:
    virtual void Fun(int number = 10)
    {
        cout << "A::Fun with number " << number;
    }
};  

class B: public A
{
public:
    virtual void Fun(int number = 20)
    {
        cout << "B::Fun with number " << number << endl;
    }
};  

int main()
{
    B b;
    A &a = b;
    a.Fun(); 

    return 0;
}  

问题是,这段代码输出什么?正确答案是:B::Fun with number 10

这个问题并不难,关键要看你对C++了解多少。我了解得不多,但是这个小问题恰好能答上来。很明显,这段代码的输出结果依赖于C++的多态。什么是多态?在C++中,多态表现为指向父类对象的指针(或引用)指向子类对象,然后利用父类指针(或引用)调用它实际指向的子类的成员函数。这些成员函数由virtual关键字定义,也就是所谓的虚函数。

如果你知道C++的多态是怎么回事,那么这道题目你至少能答对前半部分。也就是说,输出的前半部分应该是这样的:B::Fun with number。疑点在于,究竟number等于10还是20?

这就涉及到C++的静态绑定和动态绑定问题。说到静态绑定和动态绑定,就不能不谈“静态类型”和“动态类型”。何为静态类型呢?C++标准(2003)是这么说的:

1.3.11 static type                                                                                                                 [defns.static.type]

the type of an expression (3.9), which type results from analysis of the program without considering execu-

tion semantics. The static type of an expression depends only on the form of the program in which the

expression appears, and does not change while the program is executing.

什么又是动态类型呢?

1.3.3 dynamic type                                                                                                         [defns.dynamic.type]

the type of the most derived object (1.8) to which the lvalue denoted by an lvalue expression refers. [Exam-

ple: if a pointer (8.3.1) p whose static type is “pointer to class B” is pointing to an object of class D, derived

from B (clause 10), the dynamic type of the expression *p is “D.” References (8.3.2) are treated similarly. ]

The dynamic type of an rvalue expression is its static type.

如果你感觉标准写得点深奥,不容易懂,那就来看看C++ Primer(第4版,15.2.4)怎么说的吧:“基类类型引用和指针的关键点在于静态类型(static type,在编译时可知的引用类型或指针类型)和动态类型(dynamic type,指针或引用所绑定的对象的类型,这是仅在运行时可知的)可能不同”。在该书第15章的最后,对静态类型和动态类型做了一个总结:静态类型是指“编译时类型。对象的静态类型与动态类型相同。引用或指针所引用的对象的动态类型可以不同于引用或指针的静态类型”;动态类型是指“运行时类型。基类类型的指针和引用可以绑定到派生类型的对象,在这种情况下,静态类型是基类引用(或指针),但动态类型是派生类引用(或指针)”。

这下就明白多了,静态类型是编译期就能确定的类型,简单地说,当你声明一个变量时,为该变量指定的类型就是它的静态类型。动态类型是在程序运行时才能确定的类型,典型例子就是父类对象指针指向子类对象,这时,父类指针的动态类型就变成了子类指针。正如上述C++标准中所举的例子,假设p原本是一个B类型的指针,如果现在让p指向D对象,而D恰好是B的派生类,那么p的动态类型就是D类型的指针。听上去有点绕,为了方便说明,我还是拿出C++标准上的一个例子来分析:

struct A {
    virtual void f(int a = 7);
};
struct B : public A {
    void f(int a);
};
void m()
{
    B* pb = new B;
    A* pa = pb;
    pa->f();  // OK, calls pa->B::f(7)
    pb->f();  // error: wrong number of arguments for B::f()
}

这段代码中,pb的静态类型是B类型指针,它的动态类型也是B类型指针。pa的静态类型是A类型指针,而它的的动态类型却是B类型指针。

一旦明白了静态类型和动态类型的概念,静态绑定和动态绑定也就好理解了。按照C++ Primer的说法,动态绑定是指“延迟到运行时才选择运行哪个函数。在C++中,动态绑定指的是在运行时基于引用或指针绑定的对象的基础类型而选择运行哪个virtual函数”。显然,动态绑定与虚函数是息息相关的。与此对应,静态绑定就简单多了:如果一个类型的成员函数不是虚函数,那也就没什么好选择的了,通过指针或引用调用成员函数时,直接绑定到指针或引用的基础类型即可。比如,在上面的代码中,pa->f(),这里调用的实际上是B的成员函数f(),也就是说,被调用的是与pa的动态类型相对应的函数,这就是所谓的“动态绑定”。

说了这么多,来解释本文一开始给出的问题。在C++中,虽然虚函数的调用是通过动态绑定来确定的,但是虚函数的缺省参数却是通过静态绑定确定的。(就这么规定的,据说是为了提高效率)显然,a的静态类型是A的引用,而动态类型是B的引用,因此,当a调用虚函数Fun()时,根据动态绑定规则,它调用的是B的成员函数Fun();而对于虚函数的缺省参数,根据静态绑定规则,它将number确定为A中给出的缺省值10。

再简单说一下本文给出的第二段代码。这是C++标准中给出的一个例子,而且也给了说明:“A virtual function call (10.3) uses the default arguments in the declaration of the virtual function determined by the static type of the pointer or reference denoting the object. An
overriding function in a derived class does not acquire default arguments from the function it overrides.” 我来翻译一下吧:“调用虚函数时使用的缺省参数在虚函数声明中给出,这些缺省参数由指示对象的指针或引用的静态类型确定。派生类中的重写函数无法获得它所重写的函数的缺省参数。”

C++ 虚函数的缺省参数问题

时间: 2024-10-05 13:08:41

C++ 虚函数的缺省参数问题的相关文章

【转】深入理解C++的动态绑定和静态绑定 &amp; 不要重定义虚函数中的默认参数

为了支持c++的多态性,才用了动态绑定和静态绑定.理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误.需要理解四个名词:1.对象的静态类型:对象在声明时采用的类型.是在编译期确定的.2.对象的动态类型:目前所指对象的类型.是在运行期决定的.对象的动态类型可以更改,但是静态类型无法更改.关于对象的静态类型和动态类型,看一个示例: class B { } class C : public B { } class D : public B { } D* pD = new D();//p

继承中虚函数的缺省参数值问题

如果类继承中重新定义了虚函数,那么虚函数中的缺省参数不要重新定义. 用一句话来解释原因就是:虚函数是动态绑定的(dynamically bound),但是缺省参数却是静态绑定的(statically bound). 静态类型和动态类型 首先需要了解什么是对象的静态类型和动态类型,对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型. 以下面的类为例: class Shape { public: enum ShapeColor{Red,Green,Blue}; virt

在虚函数的声明的参数列表后加上“=0”就将函数变成了纯虚函数

在虚函数的声明的参数列表后加上“=0”就将函数变成了纯虚函数class Base{ virtual void function()=0;}我们不需要为纯虚函数Base::function()提供任何定义,那些声明了纯虚函数的类就是抽象类.任何试图创建一个抽象类对象的操作都会导致编译器错误.如果一个派生Base并且重写了Base::function()函数,它就成为了具体的类.class Derived :public Basae { void function(); } 通常将抽象类用作接口声明

C++ 函数的缺省参数

一.普通函数的缺省参数 # include<iostream> using namespace std; void func(int m=0,int n=1) //m=0,叫做函数的默认参数,也叫做缺省参数.缺省参数可以有一个,也可以有多个. { cout<<"m:"<<m<<"\t"<<"n:"<<n<<endl; } int main() { func();

不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板

[源码下载] 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 函数重载 缺省参数 内联函数 函数模板 示例1.函数重载, 缺省参数CppFunction1.h #pragma once #include <string> using namespace std; namespace NativeDll { class CppFunction1 { public: string Demo(); }; } CppFunction1.cpp /* * 函数重载,缺省参数

函数的缺省参数和函数初始化示例以及布尔型参数的使用示例

代码示例 1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 void set(int = 30, int = 5);//声明函数时,初始化参数 7 void count(bool = false);//声明函数时,初始化参数 8 private: 9 int w; 10 int h; 11 }; 12 void A::set(int width, int height) 13 { 14 w = widt

C++ 静态绑定与动态绑定------绝不重新定义继承而来的缺省参数

在了解静态绑定和动态绑定之前,先了解什么是对象的静态类型,什么是对象的动态类型. 对象的静态类型:对象在声明时采用的类型.是在编译器决定的. 对象的动态类型:目前所指对象的类型.是在运行期决定的. 动态类型可以更改,而静态类型不可更改.看一个示例 class Base { public: void setData(int i=10) { cout <<" virtual int Base::setData()"<<endl; } virtual int getD

位运算+引用+const+new/delete+内联函数、函数重载、函数缺省参数

一.位运算 应用: 1.判断某一位是否为1 2.只改变其中某一位,而保持其它位都不变 位运算操作: 1.& 按位与(双目): 将某变量中的某些位清0(与0位与)且同时保留其它位不变(与1位与):获取某变量中某一位(与其位与后判断是否为该数) 2.|  按位或(双目): 将某变量中的某些位置1(与1位或)且保留其它位不变 3.^  按位异或(双目): 将某变量中的某些位取反(与1异或)且保留其它位不变 异或运算特点: 如果 a^b=c,那么就有 c^b = a以及c^a=b.(穷举法可证---用于

第一周 从C走进C++ 008 函数缺省参数

1. 函数的缺省参数? C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值. void func( int x1, int x2 = 2, int x3 = 3) { } func(10 ) ; //等效于func(10,2,3) func(10,8) ; //等效于func(10,8,3) func(10, , 8) ; //不行,只能最右边的连续若干个参数缺省 ? 函数参数可缺省的目的在于提高程序的可扩充性.? 即如果某个写好