| 热门文章 |
 |
|
| 编辑推荐 |
 |
|
|
|
|
|
作者:中译者:叶秉哲
来源:永远的UNIX 点击: 更新:2006-12-19
|
|
行吗? 有的;ANSI(美国的)和 ISO(国际的)组织正密切合作。ANSI-C++ 委员会称为 "X3J16" ,而 ISO C++ 标准团体称为 "WG21"。ANSI/ISO C++ 的标准过程中包含了 这些人: AT&T, IBM, DEC, HP, Sun, MS, Borland, Zortech, Apple, OSF 等等等等。每次开 会约有 70 人,他们来自美、英、日、德、瑞典、丹麦、法国……(他们都有「区域 性」的委员会,派遣正式代表并主导「区域性」的会议)。 ======================================== Q7:该到哪里索取最新的 ANSI-C++ 标准草案? ISO Committee Draft for C++ 以及 ANSI C++ Draft(将要供 public review 的文 件)可如此取得: http://www.cygnus.com/~mrs/wp-draft 你也可以拿到 Postscript 和 Adobe Acrobat 的版本: ftp://research.att.com/dist/stdc++/WP 也能拿到 HTML 和 ASCII 的版本: ftp://ftp.cygnus.com/pub/g++ 也能拿到书面版本: X3 Secretariat 1250 Eye Street NW Suite 200 Washington, DC 20005 202-626-5738 你也可以用 email: lbarra@itic.nw.dc.us (Lynn Barra) 注明要索取最新的 "Draft Proposed American National Standard for Information Systems -- Programming Language C++",文件编号 CD14882。它通常是用2日期的 FedEx(美国境内)来递送的,所以很快就能收到。 ======================================== Q8:C++ 对 ANSI-C 回溯相容吗? 几乎是。 C++ 尽可能地和 C 相容,但不能更相容了。事实上,主要的不同在於 C++ 要求函数 原型:"f()" 宣告的是无参数的函数(在 C 里,"f()" 和 "f(...)" 是一样的)。 还有些细微的差别,像在 C++ 里 sizeof('x') 等同於 sizeof(char),但在 C 里面 却是等同於 sizeof(int)。 而且,C++ 直接就把结构的标签(tag)当成是型别的名 字,但 C 就需要加个 "struct" 字("typedef struct Fred Fred" 这种技巧仍然能 用,但在 C++ 中是累赘的)。 ======================================== Q9:多久才能学会 C++? 像 Paradigm Shift 公司,成功地教授过标准的工业界「短期课程」,将大学一学期 的课压缩到一周 40 小时。然而真正的精通得由实际经验而来:没有东西能取代时间 。需动手做的指定专题是必要的,因为它们能将你的观念「凝固成形」。 大约要 6-12 个月才能流利使用 C++/OOP,如果身边有高手的话,费时会短些;反之 若没有个“好的”通用型 C++物件程式库,则会耗时更久。想成为顾问级的高手,则 约需 3 年。 有些人却根本办不到。除非你是可造之材,且有强烈的个人驱动力,否则你也做不到 。「孺子可教」最起码的要求是:你必须能「觉今是而昨非」。「驱动力」最起码的 要求是:你愿意多投入时间精力(改变思考的方式〔典□转移 paradigm shift〕要 远比学些新的东西来得困难)。 ========================= ■□ 第4节:C++ 的基础 ========================= Q10:什麽是类别(class)? 物件导向系统的基石。 类别是用来定义资料型态(data type)的,就像 C 的 struct 一样。 以资讯科学术语来说,一个型态包含了一组状态(state),以及在状态之间转移的 动作行为(operation)。因此 "int" 是个「型态」,因为它有一组状态,还有诸如 「加两个整数」、「整数相乘」等等的运作行为。同样的,「类别」提供一组(通常 是公共的)运算,及一组(通常是非公共的)资料栏位,以代表该型态的案例所拥有 的抽象值。以 C 的角度来看,类别就是其成员(members)皆预设为 "private" 的 struct。 把 "int" 想成是个类别,它拥有 "operator++" 等等的运作行为(method)。 ======================================== Q11:什麽是物件(object)? 一块赋有某种语意的储存空间。 在宣告 "int i;" 之後,我们称「i 是个 int 型态的物件」。在 C++/OOP 里,「物 件」通常意指「类别的案例(an instance of a class)」,因此类别定义了数个物 件(案例)的行为。 ======================================== Q12:什麽是参考(reference)? 一个物件的“别名”(alias,另一个名称)。 参考通常用於传址呼叫(pass-by-reference): void swap(int& i, int& j) { int tmp = i; i = j; j = tmp; } main() { int x, y; //... swap(x,y); } 在这里 "i" 和 "j" 分别是是 main 函数中 "x" 与 "y" 的别名,换句话说,"i" 就 是 "x"--不是个指向 "x" 的指标,也不是 "x" 该值的复制品,而它的的确确就是 "x" 本身。你对 "i" 做的任何动作,都会反映到 "x" 上;反之亦然。 从最底层来看,参考最常用指标来实作,它的效果有点像 C 里头的「传指标呼叫」 (pass-by-pointer),但 "&" 取址运算子由呼叫者换到被呼叫者之处了,你也要删 去所有的 "*" 运算子。 ======================================== Q13:如果设定某值给参考会怎麽样? 会更动到被参考者(referrent,该「参考」所参考到的物件)。 记住:「参考」就是「被参考者」,因此动了参考就会改动到被参考者(「参考」是 「被参考者」的左值 "Lvalue"〔出现在设定陈述的左边〕)。 更进一步,我们也允许参考被传回。这样子函数呼叫就可放在设定陈述的左边,这对 运算子多载的场合很有用。 ======================================== Q14:怎样才能将参考改设成别的物件? 没有办法。 和指标不同,一旦参考被系结到某个物件,它就不能再被改设到其他物件去。「参考 」本身不是一个物件(它自己没有位址;「取参考的位址」只会得到被参考者的位址 ;切记:「参考」就是「被参考者」)。 将「参考」与「被参考者」分离开来是不可能的。 ======================================== Q15:何时该用参考,何时又该用指标? 可以时,用参考;必要时,就用指标。 当你不需要“重设”它时(见前一个问题),参考会比指标好。这通常意味著:在物 件类别的公共介面中参考最有用。参考大多用於物件的表层,而指标则多用於里层。 但有一个例外:当函数参数或传回值需要一个「临界」(sentinel)的参考值时,最 好是用指标来做,以 NULL 指标做为一个特别值(「参考」应该是个实质物件的「别 名」,而不是个解参用的〔dereferenced〕NULL 指标)。 注意:老资格的 C 程式员不喜欢参考,因为在父程式的地方,「参考」的语意并不 是那麽明显。然而有了些 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)" 这个函数(它的倍精确度版本在 中)。 附带一提: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:「夥伴关系无继承及递移性」是什麽意思? 夥伴关系的特权性无法被继承下上一页 [1] [2] [3] [4] [5] 下一页
|
|
|