高质量C++C编程指南(下).pdf
《高质量C++C编程指南(下).pdf》由会员分享,可在线阅读,更多相关《高质量C++C编程指南(下).pdf(46页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第8章C+函数的高级特性对 比 于 C语言的函数,C+增加了重载(o v e r l o a d e d),内 联(i n l i n e)-.c o n s t 和 v i r t u a l四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数,c o n s t 与v i r t u a l 机制仅用于类的成员函数。重载和内联肯定有其好处才会被C+语言采纳,但是不可以当成免费的午餐而滥用。本章将探究重载和内联的优点与局限性,说明什么情况下应该采用、不该采用以及要警惕错用。8.1 函数重载的概念8.1.1 重载的起源自然语言中,一个词可以有许多不同的含义,即该词被重载了。人们可
2、以通过上下文来判断该词到底是哪种含义。“词的重载”可以使语言更加简练。例 如“吃饭”的含义十分广泛,人们没有必要每次非得说清楚具体吃什么不可。别迂腐得象孔已己,说茴香豆的茴字有四种写法。在 C+程序中,可以将语义、功能相似的几个函数用同一个名字表示,即函数重载。这样便于记忆,提高了函数的易用性,这 是 C+语言采用重载机制的一个理由。例如示例 8-1-1 中的函数E a t B e e f,E a t F i s h,E a t C h i c k e n 可以用同一个函数名E a t 表示,用不同类型的参数加以区别。示 例8-1-1重载函数Eatv o i d E a t B e e f (
3、);/可以改为v o i d E a t (B e e f );v o i d E a t F i s h (,);/可以改为v o i d E a t (F i s h ,);v o i d E a t C h i c k e n ();/可以改为v o i d E a t (C h i c k e n );C+语言采用重载机制的另一个理由是:类的构造函数需要重载机制。因 为 C+规定构造函数与类同名(请参见第9章),构造函数只能有一个名字。如果想用几种不同的方法创建对象该怎么办?别无选择,只能用重载机制来实现。所以类可以有多个同名的构造函数。8.1.2重载是如何实现的?儿个同名的重我函数仍
4、然是不同的函数,它们是如何区分的呢?我们自然想到函数接口的两个要素:参数与返回值。如果同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。局质量C+/C编程指南,v 1.0如果同名函数仅仅是返回值类型不同,有时可以区分,有时却不能。例如:voi d F u n c ti on(voi d);i n t F u n c ti on (voi d);上述两个函数,第一个没有返回值,第二个的返回值是i n t类型。如果这样调用函数:i n t x=F u n c ti on ();则可以判断出F u n c ti on是第二个函数。问题是在C+/C程序中,我们可以忽略函数的返回
5、值。在这种情况下,编译器和程序员都不知道哪个F u n c ti on函数被调用。所以只能靠参数而不能靠返回值类型的不同来区分重我函数。编译器根据参数为每个重载函数产生不同的内部标识符。例如编译器为示例8 7-1中的三个E a t函数产生象e a t b e e f、e a t f i sh、e a t c h i c ke n之类的内部标识符(不同的编译器可能产生不同风格的内部标识符)。如 果C+程序要调用已经被编译后的C函数,该怎么办?假设某个C函数的声明如下:voi d f oo(i n t x,i n t y);该 函 数 被C编译器编译后在库中的名字为一f oo,而C+编译器则会产生
6、像一f oo_ i n t_ i n t之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C+程序不能直 接 调 用C函数。C+提供了一个C连接交换指定符号e xte rn “C”来解决这个问题。例如:e xte rn C(voi d f oo(i n t x,i n t y);/其它函数)或者写成e xte rn C(ti i n c lu d e my h e a d e r.h-/其 它C头文件这 就 告 诉C+编译译器,函 数f oo是 个C连接,应该到库中找名字一f。而不是找_ f oo_ i n t_ i n to C+编译器开发商已经对C标准库的头文件作了 e xt
7、e rn “C”处理,所以我们可以用#i n c lu d e直接引用这些头文件。注意并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算重载,因为函数的作用域不同。例如:voi d P ri n t();/全局函数c la ss Avoi d P ri n t();成员函数2001Page 2 of 46局质量C+/C编程指南,v 1.0)不 论 两 个 P r i n t 函数的参数是否不同,如果类的某个成员函数要调用全局函数P ri n t,为了与成员函数P ri n t区别,全局函数被调用时应加:标志。如:P ri n t();/表 示 P ri n t是全局函数而非成
8、员函数8.1.3当心隐式类型转换导致重载函数产生二义性示 例 8-1-3 中,第 一 个 ou tpu t函数的参数是i n t类 型,第 二 个 ou tpu t函数的参数是 f loa t类型。由于数字本身没有类型,将数字当作参数时将自动进行类型转换(称为隐式类型转换)。语 句。u tpu t(0.5)将产生编译错误,因为编译器不知道该将0.5转换成i n t还 是 f loa t类型的参数。隐式类型转换在很多地方可以简化程序的书写,但是也可能留下隐患。#i n cl u d e v o i d o u t p u t (i n t x);/函数声明v o i d o u t p u t
9、(f l o a t x);/函数声明v o i d o u t p u t(i n t x)co u t o u t p u t i n t x e n d l ;)v o i d o u t p u t(f l o a t x)(co u t o u t p u t f l o a t x cl a s s B a s e(p u bl i c:v o i d f(i n t x)co u t B a s e:f(i n t)x e n d l;v o i d f(f l o a t x)co u t B a s e:f(f l o a t)x e n d l;v i r t u a l
10、v o i d g(v o i d)co u t B a s e:g(v o i d)“e n d l;);cl a s s D e r i v e d :p u bl i c B a s ep u bl i c:v i r t u a l v o i d g(v o i d)co u t Z/D e r i v e d:g(v o i d)f (42);/B a s e:f (i n t)422001Page 4 of 46局质量C+/C编程指南,v 1.0示 例 8-2-1成员函数的重载和覆盖p b-f(3.1 4f);/B a s e:f(f l o a t)3.1 4p b-g();)
11、/D e r i v e d:g (v o i d)8.2.2 令人迷惑的隐藏规则本来仅仅区别重载与覆盖并不算困难,但 是 C+的隐藏规则使问题复杂性陡然增加。这 里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不 论 有 无 v i r t u a l关键字,基类的函数将被隐藏(注意别与重载混淆)。(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有v i r t u a l关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。示例程序8-2-2(a)中:(1)函数 D e r i v e d:f (
12、f l o a t)覆盖了 B a s e:f (f l o a t)。(2)函数 D e r i v e d:g(i n t)隐藏了 B a s e:g(f l o a t),而不是重载。(3)函数 D e r i v e d:h(f l o a t)隐藏了 B a s e:h(f l o a t),而不是覆盖。#i n cl u d e cl a s s B a s ep u bl i c:v i r t u a l v o i d f(f l o a t x)co u t B a s e:f(f l o a t)x e n d l;)v o i d g(f l o a t x)co u
13、t B a s e:g(f l o a t)x e n d l;)v o i d h (f l o a t x)co u t B a s e:h(f l o a t)x e n d l;);cl a s s D e r i v e d :p u bl i c B a s e(p u bl i c:v i r t u a l v o i d f(f l o a t x)co u t *D e r i v e d:f(f l o a t)x e n d l;v o i d g(i n t x)co u t D e r i v e d:g(i n t)x e n d l;v o i d h(f l
14、o a t x)co u t D e r i v e d:h(f l o a t)x f(3.1 4f);/D e r i v e d:f(f l o a t)3.1 4p d-f(3.1 4f);/D e r i v e d:f(f l o a t)3.1 4/B a d :be h a v i o r d e p e n d s o n t y p e o f t h e p o i n t e rp b-g (3.1 4f);/B a s e:g(f l o a t)3.1 4p d-g(3.1 4f);/D e r i v e d:g(i n t)3(s u r p r i s e!)
15、/B a d :be h a v i o r d e p e n d s o n t y p e o f t h e p o i n t e rp b-h(3.1 4f);/B a s e:h(f l o a t)3.1 4(s u r p r i s e!)p d-h(3.1 4f);/D e r i v e d:h(f l o a t)3.1 4示例8-2-2(b)重载、覆盖和隐藏的比较8.2.3 摆脱隐藏隐藏规则引起了不少麻烦。示 例8-2-3程序中,语 句p d-f(1 0)的本意是想调用函数 B a s e:f (i n t),但是 B a s e:f (i n t)不幸被 D e
16、r i v e d:f (ch a r *)隐藏了。由于数字 1 0不能被隐式地转化为字符串,所以在编译时出错。c l a s s B a s e(p u b l i c:v o i d f (i n t x);;c l a s s D e r i v e d :p u b l i c B a s ep u b l i c:v o i d f(c h a r *s t r);;v o i d T e s t(v o i d)(D e r i v e d *p d =n e w D e r i v e d;p d-f(1 0);/e r r o r示 例8-2-3由于隐藏而导致错误2001Pag
17、e 6 of 46局质量C+/C编程指南,v 1.0从示例8-2-3看来,隐藏规则似乎很愚蠢。但是隐藏规则至少有两个存在的理由:写 语 句p d-f(1 0)的人可能真的想调用D e r i v e d:f(c h a r *)函数,只是他误将参数写错了。有了隐藏规则,编译器就可以明确指出错误,这未必不是好事。否则,编译器会静悄悄地将错就错,程序员将很难发现这个错误,流卜祸根。假 如 类D e r i v e d有多个基类(多重继承),有时搞不清楚哪些基类定义了函数f。如果没有隐藏规则,那 么p d-f (1 0)可能会调用一个出乎意料的基类函数f。尽管隐藏规则看起来不怎么有道理,但它的确能消
18、灭这些意外。示 例8-2-3中,如 果 语 句p d-f (1 0)一 定 要 调 用 函 数B a s e:f (i n t),那么将类D e r i v e d修改为如下即可。c l a s s D e r i v e d :p u b l i c B a s e(p u b l i c:v o i d f(c h a r *s t r);v o i d f(i n t x)B a s e:f(x););8.3参数的缺省值有一些参数的值在每次函数调用忖都相同,书写这样的语句会使人厌烦。C+语言采用参数的缺省值使书写变得简洁(在编译时,缺省值由编译器自动插入)。参数缺省值的使用规则:【规 则
19、8-3-1 参数缺省值只能出现在函数的声明中,而不能出现在定义体中。例如:v o i d F o o(i n t x=0,i n t y=0);/正确,缺省值出现在函数的声明中v o i d F o o(i n t x=0,i n t y=0)/错误,缺省值出现在函数的定义体中)为什么会这样?我想是有两个原因:一是函数的实现(定义)本来就与参数是否有缺省值无关,所以没有必要让缺省值出现在函数的定义体中。二是参数的缺省值可能会改动,显然修改函数的声明比修改函数的定义要方便。【规 则8-3-2 如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样。正确的示例如卜:v o
20、i d F o o (i n t x,i n t y=0,i n t z=0);2001Page 7 of 46局质量C+/C编程指南,v 1.0错误的示例如下:v o i d F o o(i n t x=0,i n t y,i n t z=0);要注意,使用参数的缺省值并没有赋予函数新的功能,仅仅是使书写变得简洁一些。它可能会提高函数的易用性,但是也可能会降低函数的可理解性。所以我们只能适当地使用参数的缺省值,要防止使用不当产生负面效果。示 例 8-3-2 中,不合理地使用参数的缺省值将导致重载函数o u t p u t 产生二义性。#i n c l u d e v o i d o u t
21、p u t(i n t x);v o i d o u t p u t(i n t x,f l o a t y=0.0);v o i d o u t p u t(i n t x)(c o u t o u t p u t i n t X x e n d lv o i d o u t p u t(i n t x,f l o a t y)(c o u t o u t p u t i n t 7 7 x a n d f l o a t y 只能重载为成员函数+=-=/=*=&=1=%=(b)?(a):(b)语句r e s u l t =M A X(i,j)+2 ;将被预处理器解释为r e s u l t
22、 =(i)(j)?(i):(j)+2 ;由 于 运 算 符 +比 运 算 符 :的优先级高,所以上述语句并不等价于期望的r e s u l t =(i)(j)?(i):(j)+2 ;如果把宏代码改写为#d e f i n e M A X(a,b)(a)(b)?(a):(b)则可以解决由优先级引起的错误。但是即使使用修改后的宏代码也不是万无一失的,例如语句r e s u l t =M A X(i+,j);将被预处理器解释为r e s u l t =(i+)(j)?(i+):(j);对 于C+而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员。让我们看看C+的“函数内联”是如何工作的。对于任
23、何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对 象 的 地 址(t h i s)会被放在合适的地方,这也是预处理器办不到的。C+语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作
24、类的数据成员。所 以 在C+程序中,应该用内联函数取代所有宏代码,“断 言as s er t”恐怕是唯一的例外。as s er t是 仅 在D eb u g版本起作用的宏,它 用 于 检 查“不应该”发生的情况。为了不在程序的D eb u g版 本 和R el eas e版本引起差别,as s er t不应该产生任何副作用。如 果as s er t是函数,由于函数调用会引起内存、代码的变动,那么将导致D eb u g版 本 与R el eas e版本存在差异。所 以as s er t不是函数,而是宏。(参 见6.5节“使用断言”)8.5.2内联函数的编程风格关 键 字i nl i ne必须与函
25、数定义体放在一起才能使函数成为内联,仅 将i nl i ne放在函数声明前面不起任何作用。如下风格的函数F oo不能成为内联函数:i nl i ne v oi d F oo(i nt x,i nt y);/i ni i ne 仅与函数声明放在起2001Page 10 of 46局质量C+/C编程指南,v 1.0v oi d F oo(i nt x,i nt y)而如卜风格的函数F oo则成为内联函数:v oi d F oo(i nt x,i nt y);i nl i ne v oi d F oo(i nt x,i nt y)/i nl i ne 与函数定义体放在一起()所以说,i nl i n
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 质量 编程 指南
限制150内