分享
 
 
 

转贴:C++语言常见问题解:#16 ~ #32

王朝c/c++·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

这是我从台湾的http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-Chinese/发现的《C++ Frequently Asked Questions》的繁体翻译,作者是:叶秉哲,也是《C++ Programming Language》3/e繁体版的译者,该文章是非常的好,出于学习用途而将它转贴,本人未取得作者的授权,原文章的版权仍然归属原作者.

C++语言常见问题解

Q16:行内函数是做什么的?

行内函数(inline function)是个程序代码会塞入呼叫者所在之处的函数。就像宏

一样,行内函数免除了函数呼叫的额外负担,以增进效率,并且(尤其是!)还能让

编译器对它施以最佳化(程序融合 "procedural integration")。不过和宏不同

的是:它只会对所有自变量求一次的值(在语意上,该“函数呼叫”和正常函数一样,

只是比较快速罢了),以避免某些不易察觉的宏错误。此外,它还会检测自变量的型

态,做必要的型别转换(宏对你有害;除非绝对必要,否则别再用它了)。

注意:过度使用行内函数会让程序代码肥胖,于分页(paging)环境下反而有负面的性

能影响。

宣告法:在函数定义处使用 "inline" 关键词:

inline void f(int i, char c) { /*...*/ }

或者是在类别内将定义包括进去:

class Fred {

public:

void f(int i, char c) { /*...*/ }

};

或是在类别外头,以 "inline" 来定义该成员函数:

class Fred {

public:

void f(int i, char c);

};

inline void Fred::f(int i, char c) { /*...*/ }

=============================

■□ 第5节:建构子和解构子

=============================

Q17:建构子(constructor)是做什么的?

建构子乃用来从零开始建立对象。

建构子就像个「初始化函数」;它把一堆散乱的字节成一个活生生的对象。最低限

度它会初始化内部用到的字段元,也可能会配置所须的资源(内存、档案、semaphore

、socket 等等)。

"ctor" 是建构子 constructor 最常见的缩写。

========================================

Q18:怎样才能让建构子呼叫另一个同处一室的建构子?

没有办法。

原因是:如果你呼叫另一个建构子,编译器会初始化一个暂时的区域性对象;但并没

有初始化“这个”你想要的对象。你可以用预设参数(default parameter),将两

个建构子合并起来,或是在私有的 "init()" 成员函数中共享它们的程序代码。

========================================

Q19:解构子(destructor)是做什么的?

解构子乃对象之葬礼。

解构子是用来释放该对象所配置到的资源,譬如:Lock 类别可能会锁住一个

semaphore,解构子则用来释放它。最常见的例子是:当建构子用了 "new" 以后,解

构子用 "delete"。

解构子是个「去死吧」的运作行为(method),通常缩写为 "dtor"。

=========================

■□ 第6节:运操作数多载

=========================

Q20:运操作数多载(operator overloading)是做什么的?

它可让使用类别的人以直觉来操作之。

运操作数多载让 C/C++ 的运操作数,能对自订的型态(对象类别)赋予自订的意义。它

们形同是函数呼叫的语法糖衣 (syntactic sugar):

class Fred {

public:

//...

};

#if 0

Fred add(Fred, Fred); //没有运操作数多载

Fred mul(Fred, Fred);

#else

Fred operator+(Fred, Fred); //有运操作数多载

Fred operator*(Fred, Fred);

#endif

Fred f(Fred a, Fred b, Fred c)

{

#if 0

return add(add(mul(a,b), mul(b,c)), mul(c,a)); //没有...

#else

return a*b + b*c + c*a; //有...

#endif

}

========================================

Q21:哪些运操作数可以/不能被多载?

大部份都可以被多载。

不能的 C 运操作数有 "." 和 "?:"(和以技术上来说,可算是运操作数的 "sizeof")。

C++ 增加了些自己的运操作数,其中除了 "::" 和 ".*". 之外都可以被多载。

底下是个足标(subscript)运操作数的例子(它会传回一个参考)。最前面是“不用

”多载的:

class Array {

public:

#if 0

int& elem(unsigned i) { if (i>99) error(); return data[i]; }

#else

int& operator[] (unsigned i) { if (i>99) error(); return data[i]; }

#endif

private:

int data[100];

};

main()

{

Array a;

#if 0

a.elem(10) = 42;

a.elem(12) += a.elem(13);

#else

a[10] = 42;

a[12] += a[13];

#endif

}

========================================

Q22:怎样做一个 "**"「次方」运操作数?

无解。

运操作数的名称、优先序、结合律以及元数(arity)都被语言所定死了。C++ 里没有

"**" 运操作数,所以你无法替类别订做一个它。

还怀疑的话,考虑看看 "x ** y" 和 "x * (*y)",这两者是完全一样的(换句话说

,编译器会假设 "y" 是个指针)。此外,运操作数多载只是函数呼叫的语法糖衣而已

,虽然甜甜的,但本质上并未增加什么东西。我建议你多载 "pow(base,exponent)"

这个函数(它的倍精确度版本在 <math.h> 中)。

附带一提:operator^ 可以用,但它的优先序及结合律不符「次方」所需。

===================

■□ 第7节:伙伴

===================

Q23:伙伴(friend)是什么?

让别的类别或函数能存取到你的类别内部的东西。

伙伴可以是函数或其它类别。类别会对它的伙伴开放存取权限。正常情况下,程序员

会下意识﹑技术性地控制该类别的伙伴与运作行为(否则当你想更动类别时,还得先

有其它部份的拥有者之同意才行)。

========================================

Q24:「伙伴」违反了封装性吗?

若善用之,反而会「强化」封装性。

我们经常得将一个类别切成两半,当这两半各有不同的案例个数及生命期时。在此情

形之下,它们通常需要直接存取对方的内部(这两半“本来”是在同一个类别里面,

所以你并未“增加”存取数据结构的运作行为个数;你只是在“搬动”这些运作行为

所在之处而已)。最安全的实作方式,就是让这两半互为彼此的「伙伴」。

若你如上述般的使用伙伴,你依然是将私有的东西保持在私有的状态。遇到上述的情

况,如果还呆呆的想避免使用伙伴关系,许多人不是采用公共资料(糟透了!),就

是弄个公共的 get/set 存取运作行为来存取彼此的资料,事实上这些都破坏了封装

性。只有在类别的外面该私有资料「仍有其意义」(以使用者的角度来看)时,开放

出私有资料的存取运作行为才称得上是恰当的做法。多数情况下,「存取运作行为」

就和「公共资料」一样糟糕:它们对私有资料成员只隐其“名”而已,却未隐藏其“

存在”。

同样的,如果将「伙伴函数」做为另一种类别公共存取函数的语法,那就和违反封装

性的成员函数一样破坏了封装。换句话说,对象类别的伙伴及成员都是「封装的界线

」,如同「类别定义」本身一样。

========================================

Q25:伙伴函数的优缺点?

它提供了某种接口设计上的自由。

成员函数和伙伴函数都有同等的存取特权(100% 的权利),主要的差别在于:伙伴

函数用起来像是 "f(x)",而成员函数则是 "x.f()"。因此,伙伴函数可让对象类别

设计者挑选他看得最顺眼的语法,以降低维护成本。

伙伴函数主要的缺点在于:当你想做动态系结(dynamic binding)时,它需要额外

的程序代码。想做出「虚拟伙伴」的效果,该伙伴函数应该呼叫个隐藏的(通常是放在

"protected:" 里)虚拟成员函数;像这个样子:"void f(Base& b) { b.do_f(); }"

。衍生类别会覆盖(override)掉那个隐藏的成员函数("void Derived::do_f()")

,而不是该伙伴函数。

========================================

Q26:「伙伴关系无继承及递移性」是什么意思?

伙伴关系的特权性无法被继承下来:伙伴的衍生类别不必然还是伙伴(我把你当朋友

,但这不代表我也一定会信任你的孩子)。如果 "Base" 类别宣告了 "f()" 为它的

伙伴,"f()" 并不会自动对由 "Base" 衍生出来的 "Derived" 类别所多出来的部份

拥有特殊的存取权力。

伙伴关系的特权无递移性:伙伴类别的伙伴不必然还是原类别的伙伴(朋友的朋友不

一定也是朋友)。譬如,如果 "Fred" 类别宣告了 "Wilma" 类别为它的伙伴,而且

"Wilma" 类别宣告了 "f()" 为它的伙伴,则 "f()" 不见得对 "Fred" 有特殊的存取

权力。

========================================

Q27:应该替类别宣告个成员函数,还是伙伴函数?

可能的话,用成员函数;必要时,就用伙伴。

有时在语法上来看,伙伴比较好(譬如:在 "Fred" 类别中,伙伴函数可把 "Fred"

弄成是第二个参数,但在成员函数中则一定得放在第一个)。另一个好例子是:二元

中序式算数运操作数(譬如:"aComplex + aComplex" 可能应该定义成伙伴而非成员函

数,因为你想让 "aFloat + aComplex" 这种写法也能成立;回想一下:成员函数无

法提升它左侧的参数,因为那样会把引发该成员函数的对象所属之类别给改变掉)。

在其它情况下,请选成员函数而不要用伙伴函数。

====================================================

■□ 第8节:输入/输出:<iostream.h> 和 <stdio.h>

====================================================

Q28:该怎样替 "class Fred" 提供输出功能?

用伙伴函数 operator<<:

class Fred {

public:

friend ostream& operator<< (ostream& o, const Fred& fred)

{ return o << fred.i; }

//...

private:

int i; //只为了说明起见而设的

};

我们用伙伴而不用成员函数,因为 "Fred" 是第二个参数而非第一个。输入的功能亦

类似,只是要改写成:

istream& operator>> (istream& i, Fred& fred);

// ^^^^^------- 不是 "const Fred& fred"!

========================================

Q29:为什么我该用 <iostream.h> 而不是以前的 <stdio.h>?

增加型别安全、减少错误、增进效率、有延展性、提供衍生能力。

Printf 还好,而 scanf 除了容易写错之外也还算可以,然而和 C++ 的 I/O 系统相

比,它们都有其限制。C++ 的 I/O(用 "<<" 及 ">>" ),和 C( "printf()" 和

"scanf()" )相比:

* 型别安全--要做 I/O 的对象,编译器会静态地事先得知其型别,而不是动态地

由 "%" 一栏查知。

* 不易出错--冗余的信息会增加错误的机会。C++ 的 I/O 就不需要多余的 "%"。

* 更快速--printf 是个小型语言的「解译器」,该语言主要是由 "%" 这种东西

构成的;在执行期它用这些字段来选择正确的格式化方式。C++ 的 I/O 系统则是

静态的依各自变量真正的型别来挑选子程序,以增进执行效率。

* 延展性--C++ I/O 机制可在不改动原有程序代码的情况下,就加进使用者新设计

的型态(能想象如果大家同时把互不兼容的 "%" 字段塞入 printf 和 scanf,会

是怎样的混乱场面?!)。

* 可衍生(subclassable)--ostream 和 istream(C++ 的 FILE* 代替品)都是

真正的类别,因此可以被衍生下去。这意味着:你可以让其它自定的东西有着和

stream 雷同的外表与行为,但实际上做的却是你想做的特定事情。你自动就重用

了数以万计别人(你甚至不认识它们)写好的 I/O 程序代码,而他们也不需要知道

你所做的「延伸 stream」类别。

========================================

Q30:为什么我处理输入时,会超过档案的结尾?

因为 eof(档案结尾)的状态,是到「将要超过档案结尾的动作」才会被设定。也就

是说,读档案的最后一个字节并不会设定 eof 的状态。

【译注】这也是 C 常见的错误。

如果你的程序像这样:

int i = 0;

while (! cin.eof()) {

cin >> x;

++i;

// work with x

}

你的 i 变量就会多了一。

你真正该做的是这样:

int i;

while (cin >> x) {

++i;

// work with x

}

========================================

Q31:为什么我的程序执行完第一次循环后,会对输入的要求不加理睬?

因为读取数值的程序,把非数字的字符留在输入缓冲区 (input buffer) 里头了。

【译注】这也是 C,甚至 Pascal 常见的错误。

如果你的程序如下:

char name[1000];

int age;

for (;;) {

cout << "Name: ";

cin >> name;

cout << "Age: ";

cin >> age;

}

你应该这样写:

for (;;) {

cout << "Name: ";

cin >> name;

cout << "Age: ";

cin >> age;

cin.ignore(INT_MAX, '\n');

}

========================================

Q32:在 DOS 及 OS/2 的 binary 模式下,要怎样来 "reopen" cin 及 cout?

有这个问题,最典型的情况就是:有人想对 cin、cout 做 binary 的 I/O,但是作

业系统(像是 DOS 或 OS/2)却总是会做 CR-LF 的转换动作。

解决法:cin、cout、cerr 这些事先定义好的串流,都是 text 的串流,没有标准做

法能把它们弄成 binary 模式。把串流关掉再设法以 binary 模式 reopen 它们,可

能会导致不可预期的结果。

在这两种模式有不同行为的系统上,一定有办法让它们变成 binary 串流,但是你得

去查查该系统的文件。

--

Marshall Cline

--

Marshall P. Cline, Ph.D. / Paradigm Shift Inc / PO Box 5108 / Potsdam NY 13676

cline@sun.soe.clarkson.edu / 315-353-6100 / FAX: 315-353-6110

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有