C++ 缺省函数参数.docx

上传人:b****5 文档编号:7367938 上传时间:2023-01-23 格式:DOCX 页数:12 大小:74.12KB
下载 相关 举报
C++ 缺省函数参数.docx_第1页
第1页 / 共12页
C++ 缺省函数参数.docx_第2页
第2页 / 共12页
C++ 缺省函数参数.docx_第3页
第3页 / 共12页
C++ 缺省函数参数.docx_第4页
第4页 / 共12页
C++ 缺省函数参数.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

C++ 缺省函数参数.docx

《C++ 缺省函数参数.docx》由会员分享,可在线阅读,更多相关《C++ 缺省函数参数.docx(12页珍藏版)》请在冰豆网上搜索。

C++ 缺省函数参数.docx

C++缺省函数参数

C++缺省函数参数

缺省参数同函数重载一样,给程序员提供了很多方便,它们都使我们可以在不同的场合使用同一名字。

不同之处是,当我们不想亲手提供这些值时,由编译器提供一个缺省参数。

有时可用缺省参数代替函数重载。

用函数重载我们得把一个几乎同样含义、同样操作的函数写两遍甚至更多。

当然,如果函数之间的行为差异较大,用缺省参数就不合适了。

  在使用缺省参数时需注意以下几点。

1.只有参数列表的后部参数才可是缺省的,也就是说,我们不可以在一个缺省参数后面又跟一个非缺省的参数。

2.一旦我们开始使用缺省参数,那么这个参数后面的所有参数都必须是缺省的。

(从左至右,第一个为缺省,则所有均为缺省参数。

3.缺省参数只能放在函数声明中,通常在一个头文件中。

编译器必须在使用该函数之前知道缺省值。

4.小小补充:

函数定义与原型(声明)中的参数名称可以不同,编译器只检查参数类型是否相同;相同,编译通过;反之,不通过;

 要说明的是,上述3是很多课本和视频教程里谈到的,关于这个说法应该怎样去理解呢?

?

   我想可以这样理解:

函数的实现(定义)本来就与参数是否有缺省值无关,所以没有必要让缺省值出现在函数的定义体中。

其次参数的缺省值可能会改动,显然修改函数的声明比修改函数的定义要方便。

但是如果我们不遵循这样的一个规则,编译器仍然是能通过的。

例如:

上面如果把实现文件的函数加上缺省参数值,无论是否inty=0还是其他值都会发生编译错误:

errorC2572:

“father:

:

print”:

重定义默认参数:

参数

C++中虚函数中的默认参数问题

当通过指针调用一个对象的方法时,如果该方法是虚函数,则实际调用的是该实例的方法。

 

当缺省参数和虚函数一起出现的时候到底用哪个默认值呢?

虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。

也就是指针是哪种类型,就调用该类型对应的类中,该函数定义时的缺省值。

由上可知pa->out()和pa->out(3)调用都是函数A:

:

out(inti),

由上可知pb->out()和pb->out(4)调用都是函数B:

:

out(inti),

缺省参数是静态绑定的,pb->out()时,pb的静态类型是A*,它的缺省参数是1;但是调用的是B:

:

out(inti)

编写代码验证了一下,正确。

对于这个特性,估计没有人会喜欢。

所以,永远记住:

“绝不重新定义继承而来的缺省参数(Neverredefinefunction’sinheriteddefaultparametersvalue.)”

上面得输出如下

 

关于C++虚函数默认参数的问题。

EffectiveC++条款38:

决不要重新定义继承而来的缺省参数值

分类:

 C/C++2011-05-2310:

49 1511人阅读 评论(8) 收藏 举报

c++编译器class图形c

目录(?

)[+]

昨晚在chgaowei的博客上关于讨论C++虚函数的默认参数问题,刚翻书找了一下,在EffectiveC++中的38条有说明。

直接上原文吧,最后加几句细点的理解

条款38:

 决不要重新定义继承而来的缺省参数值

让我们从一开始就把问题简化。

缺省参数只能作为函数的一部分而存在;另外,只有两种函数可以继承:

虚函数和非虚函数。

因此,重定义缺省参数值的唯一方法是重定义一个继承而来的函数。

然而,重定义继承而来的非虚函数是一种错误(参见条款37),所以,我们完全可以把讨论的范围缩小为 "继承一个有缺省参数值的虚函数" 的情况。

既然如此,本条款的理由就变得非常明显:

虚函数是动态绑定而缺省参数值是静态绑定的。

什么意思?

你可能会说你不懂这些最新的面向对象术语;或者,过度劳累的你一时想不起静态和动态绑定的区别。

那么,让我们来复习一下。

对象的静态类型是指你声明的存在于程序代码文本中的类型。

看下面这个类层次结构:

[cpp] viewplaincopyprint?

1.enum ShapeColor { RED, GREEN, BLUE };  

2.  

3.// 一个表示几何形状的类  

4.  

5.class Shape {  

6.  

7.public:

  

8.  

9.  // 所有的形状都要提供一个函数绘制它们本身  

10.  

11.  virtual void draw(ShapeColor color = RED) const = 0;  

12.  

13.  ...  

14.  

15.};  

16.  

17.class Rectangle:

 public Shape {  

18.  

19.public:

  

20.  

21.  // 注意:

定义了不同的缺省参数值 ---- 不好!

  

22.  

23.  virtual void draw(ShapeColor color = GREEN) const;  

24.  

25.  ...  

26.  

27.};  

28.  

29.class Circle:

 public Shape {  

30.  

31.public:

  

32.  

33.  virtual void draw(ShapeColor color) const;  

34.  

35.  ...  

36.  

37.};  

用图形来表示是下面这样:

                Shape

                    //

                   /  /

                  /    /

   Rectangle    Circle

现在看看这些指针:

Shape*ps;                      // 静态类型 =Shape*

Shape*pc=newCircle;         // 静态类型 =Shape*

Shape*pr=newRectangle;      // 静态类型 =Shape*

这个例子中, ps, pc,和pr都被声明为Shape指针类型,所以它们都以此作为自己的静态类型。

注意,这和它们真的所指向的对象的类型绝对没有关系 ---- 它们的静态类型总是Shape*。

对象的动态类型是由它当前所指的对象的类型决定的。

即,对象的动态类型表示它将执行何种行为。

上面的例子中,pc的动态类型是Circle*,pr的动态类型是Rectangle*。

至于ps,实际上没有动态类型,因为它(还)没有指向任何对象。

动态类型,顾名思义,可以在程序运行时改变,典型的方法是通过赋值:

ps=pc;                        //ps的动态类型

                                // 现在是Circle*

ps=pr;                        //ps的动态类型

                                // 现在是Rectangle*

虚函数是动态绑定的,意思是说,虚函数通过哪个对象被调用,具体被调用的函数就由那个对象的动态类型决定:

pc->draw(RED);                  // 调用Circle:

:

draw(RED)

pr->draw(RED);                  // 调用Rectangle:

:

draw(RED)

我知道这些都是老掉牙的知识了,你当然也了解虚函数。

(如果想知道它们是怎么实现的,参见条款M24)但是,将虚函数和缺省参数值结合起来分析就会产生问题,因为,如上所述,虚函数是动态绑定的,但缺省参数是静态绑定的。

这意味着你最终可能调用的是一个定义在派生类,但使用了基类中的缺省参数值的虚函数:

pr->draw();                     // 调用Rectangle:

:

draw(RED)!

这种情况下,pr的动态类型是Rectangle*,所以Rectangle的虚函数被调用 ---- 正如我们所期望的那样。

Rectangle:

:

draw中,缺省参数值是GREEN。

但是,由于pr的静态类型是Shape*,这个函数调用的参数值是从Shape类中取得的,而不是Rectangle类!

所以结果将十分奇怪并且出人意料,因为这个调用包含了Shape和Rectangle类中Draw的声明的组合。

你当然不希望自己的软件以这种方式运行啦;至少,用户不希望这样,相信我。

不用说,ps, pc,和pr都是指针的事实和产生问题的原因无关。

如果它们是引用,问题也会继续存在。

问题仅仅出在,draw是一个虚函数,并且它的一个缺省参数在子类中被重新定义了。

为什么C++坚持这种有违常规的做法呢?

答案和运行效率有关。

如果缺省参数值被动态绑定,编译器就必须想办法为虚函数在运行时确定合适的缺省值,这将比现在采用的在编译阶段确定缺省值的机制更慢更复杂。

做出这种选择是想求得速度上的提高和实现上的简便,所以大家现在才能感受得到程序运行的高效;当然,如果忽视了本条款的建议,就会带来混乱。

 

先说一下现在的C++的参数传递机制,非虚函数和虚函数的传递机制都是一样的。

比如如下函数调用:

func(10);

会被编译器翻译成:

push10

puch返回地址

callfunc

大概就是这样:

先把参数压栈,然后压返回地址,在调用函数。

对于虚函数来说,如果调用时没有指定参数值,那么编译器会帮我们加上去。

对,加上去,这里就有问题来了,如果是用基类指针调用的虚函数,我们知道,因为动态绑定,编译器暂时无法知道实际调用的是哪个函数,所以他得用虚函数的机制进行2此指针操作在实际的类地址中找到虚函数表,再根据偏移找到实际的函数跳转地址,而此时,编译器必须提前把参数压栈准备好,call之后就直接用参数了。

那么,既然编译器还不知道实际调用的是哪个函数,那么当然就更不知道实际传递的默认参数应该是子类还是父类的了。

关键就在这里。

参数都是静态绑定的,如果要动态,上文说了,效率会跟虚函数调用一样稍微有点低,所以C++折中了。

话又说回来,如果要动态的实现,怎么办呢?

下面说点个人的思路

对,虚函数动态绑定是用vptbl实现的(这个《深度探索c++对象模型》中有),那么默认参数的实现是否也能参考呢?

应该可以。

编译器可以把一条压栈(压默认参数值的指令)放在函数代码的前面几条指令中,

然后在跳转的时候,实际的call指令可以延后几句,这样:

用effective中的例子:

pr->draw();  

编译后可能是:

//因为没有指定参数,所以在我们的尝试实现中不压参数

call(*pr->_vtbl[2]-4) //这个计算虚函数地址的我简单写到一行了,实际上时2个取指操作。

-4的原因待会说。

当然,函数代码也得相应的改变一下:

pushGREEN

Rectangle:

:

draw:

//这才是真正的draw代码,上述-4的原因就是为了执行上面的pushGREEN指令,这,同理在基类中代码也会变成这样:

pushRED

Shape:

:

draw:

 //Shape的draw代码

总结一下,就是说把参数压栈的指令稍微改一下,如果正常传递了参数当然就不用了,如果没传递参数,那么在函数的代码之前加上一句压参数指令,然后在函数跳转的时候,往回多跳一条指令,让实际掉用的代码去压这个参数。

说的可能有的乱,有些错的地方还忘见谅。

呵呵···

《认清C++语言》---继承而来的非虚函数和缺省参数

======================继承而来的非虚函数======================

假设类Derive公有继承自类Base,且类Base定义了一个公有非虚成员函数func:

class Base

{

public:

         void Func();

         ...

};

 

class Derive:

 public Base

{

         ...

}

Derivede;       //定义一个派生类对象

Base*pb=&de;    //得到一个指向de的Base指针

pb->Func();   //通过指针调用Func()

 

Derive*pd=&de;         //得到一个指向de的Derive指针

pd->Func();   //通过指针调用Func()

 

这种情况下,如果Derive没有重新定义Func函数时,两个指针调用Func的行为是相同的;但是,如果Func是非虚函数且Derive又重新定义了自己的Func版本,那么两者的行为就不会相同了:

class Derive:

 public Base

{

public:

         void Func();   //隐藏了Base:

:

Func;

         ...

};

 

pb->Func();   //调用Base:

:

Func()

pd->Func();   //调用Derive:

:

Func()

这种行为的两面性原因在于:

Base:

:

Func()和Derive:

:

Func()这样的非虚函数是静态绑定的。

因此,即使pb指向的是从Base派生的类的对象,但由于pb被声明为执行Base的指针类型,所以通过pb调用非虚函数时就总是调用那些定义在类Base中的函数。

与之相反,虚函数是动态绑定的,不会存在上述问题。

通过pb或pd调用Func()时都将导致调用Derive:

:

Func(),因为pb和pd实际上都是指向类型Derive的对象。

 

结论:

如果写派生类Derive时重新定义从基类Base继承而来的非虚函数Func(),Derive的对象就可能表现出精神分裂的症状。

引用也会和指针一样表现出这种异常行为。

因此,任何情况下都要禁止重新定义继承而来的非虚函数。

 

======================继承而来的缺省参数值=====================

重定义缺省参数值的唯一方法是重定义一个继承而来的函数,而由上面分析可知,重定义继承而来的非虚函数是错误的,因此,我们这里主要讨论“继承一个有缺省参数值的虚函数”。

 

虚函数是动态绑定的而缺省参数值是静态绑定的。

 

对象的静态类型是指我们声明的存在于程序代码文本中的类型:

enum ASCEShapeColor{RED,GREEN,BLUE};

class ASCEShape     //基类

{

public:

         //所有形状都要提供这样一个函数绘制自身

         virtual void draw(ASCEShapeColorcolor=RED) const =0;

         ...

};

 

class ASCERectangle:

 public ASCEShape

{

public:

         //定义了不同的缺省参数值错误!

         virtual void draw(ASCEShapeColorcolor=GREEN) const;

         ...

};

 

class ASCECircle:

 public ASCEShape

{

public:

         virtual void draw(ASCEShapeColorcolor) const;

         ...

};

 

ASCEShape*ps;      //静态类型是ASCEShape*

ASCEShape*pr= new ASCEShapeRectangle;    // 静态类型是ASCEShape*

ASCEShape*pc= new ASCEShapeCircle;  //静态类型是ASCEShape*

上面三个指针的静态类型和它们实际所指向的对象的类型是没有关系的。

 

而对象的动态类型是由它当前所指的对象的类型决定的,即对象的动态类型表示它将执行何种行为。

如上面的代码中,pr的动态类型是ASCERectangle*,pc的动态类型是ASCECircle*,而ps实际上没有动态类型,因为它还没有指向任何对象。

动态类型可以在程序运行时改变,典型的方法是通过赋值:

ps=pr;   //ps的动态类型现在是ASCERectangle*

ps=pc;  //ps的动态类型现在是ASCECircle*

 

虚函数就是动态绑定的,即虚函数通过哪个对象被调用,具体被调用的函数就由那个对象的动态类型决定:

pr->draw(REG);    //调用ASCERectangle:

:

draw(REG)

pc->draw(REG);   //调用ASCECircle:

:

draw(REG)

虚函数机制的正确性是毋庸置疑的,但当将虚函数和缺省参数值结合起来分析就会产生问题:

虚函数是动态绑定的,但缺省参数是静态绑定的,这意味着我们最终可能调用的是一个定义在派生类,但使用了基类中的缺省参数值的虚函数:

pr->draw();   //调用的是ASCERectangle:

:

draw(REG)!

!

,而不是ASCERectangle:

:

draw(GREEN)

 

由此,结论就是:

禁止重新定义继承而来的缺省参数值!

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 公共行政管理

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1