《C++ 探秘:68 讲贯通 C++》(译作)上架

2010/12/25 | 17:40 | 分类:写作与翻译 | 标签: | 3,651次阅读

《C++ 探秘:68 讲贯通 C++》(译作)上架

C++ 探秘:68 讲贯通 C++
Exploring C++: The Programmer's Introduction to C++

  作  者:(美)Ray Lischner
  译  者:刘晓娜 林健 石小兵 李杰
  丛 书 名:图灵程序设计丛书
  出 版 社:人民邮电出版社
  ISBN:9787115242273
  出版日期:2011 年 1 月
  相关链接:China-pub 预订/购买 豆瓣读书


  虽说以前也翻译过不少东西,但翻译带 ISBN 的正式出版物还是第一次。作为第二译者,感谢晓娜同学提供机会,感谢小兵同学交流讨论,感谢刘江老师、图灵团队以及图灵讨论区里提供过帮助的朋友。所谓“诚惶诚恐、难免疏漏”都只是无力的套话,有任何意见或建议您就直接了当地提吧,善于从别人译作中挑刺的我这次做好了接受各种批评的准备。
  这本书比较适合 C++ 初学者,特别是自学者,聊天似的教学风格一步步引导读者正确使用 C++ 的核心特性写出有用的程序。对于 C++ 的高级特性,作者不求全求深,只求实用。即使你对 C++ 已略有小成,也不妨看看本书是如何通过实际的代码来阐述 C++ 中那些看似学院派花架子、实则用心良苦的设计的。这本书不适合应付企业面试题目中的语言细节问题,但体会了作者的循循善诱,你也许就能从中领悟分析 C++ 问题的思路。
  584 页,89 元,价格的确高了点。此时此刻,我想起了师姐龚老师的谆谆教诲:“签名可以,赠没有”。

《程序员的自我修养》是有学习价值的

2009/05/23 | 14:20 | 分类:计算机科学与编程 | 标签: | 4,422次阅读

  不久前收到了博文视点寄来的新书——《程序员的自我修养——链接、装载与库》。比较巧的是,我这段时间正在从事一个涉及多语言互操作的项目,那些天还在看一些有关C++语言设计和实现原理的文献,这本《程序员的自我修养》和我最近的工作还是多多少少有点联系的。
  有的人可能认为读博士的人不应该再去关注这些面向程序员、纠结于技术细节的书了,而要专心于自己的研究领域,做偏科学、偏理论的事。这是有道理的,博士教育和硕士教育的差异即在此,漂泊在茫无涯际的技术海洋很可能让本应该专注于一个方向的人不知所向。但是我们也要反思,自己的基础是否足够扎实?对计算机原理的理解是否超越了那些具体技术的层面?自己是否真的具备了从事科研的素质?我的导师在他的科普作品《电脑启示录》(中篇 硅谷的秘密)中曾提到作为计算机科研人员应该具备的基本素质,并例举了几个用来考察这些素质的问题,诸如:从计算机开机到操作系统等待用户输入,经历的一系列流程是什么?在浏览器里敲入一个网址到网页呈现给用户,期间又有哪些工作细节?这些过程看似稀疏平常,其中的大道理在本科计算机课程中也都或多或少地介绍过,然而要精确地表达每个步骤,更重要地是说明每个步骤为什么要这样设计、为什么会这样实现、其中的科学依据是什么,往往不是每个计算机专业毕业生都能说清楚的。这类问题常常能从侧面反映一个从业人员的理论功底及实践经验。《程序员的自我修养》所阐述的也正是同一类的问题:一个程序由硬盘上目标文件、可执行文件变成内存中的进程体、CPU中的指令流,整个过程的来龙去脉是什么,有什么原理、诀窍、讲究和因果联系。
  在计算机领域从事不同具体工作的科研、技术人员,静下心来分析一下这些平时被各种层面的接口掩盖了的机制是有好处的。初用VC++编程的时候,也许你会奇怪,监视窗口为什么要输出一串“烫”字;干Linux工程时,仅仅链接了几个标准库文件就把程序搞崩溃了,你会认为这是链接器或标准库的bug,还是自己没有弄明白它们的机理?如果你在计算机学科的其它方面有过一些细致的了解,你又会发现,像自举引导、延迟绑定等共性的方法,时间与空间转换、策略与机制分离等共性的原则在编译、链接、装载过程中也都有生动的体现。而即使你是做研究的,方向与程序原理相隔甚远,看看这些计算机领域内实现相对成熟、应用相对普遍的、原理相对通用的问题,对自己的工作也很有启发意义。
  尽管这本书有待市场的考验,但它研究的问题确属计算机学科中的经典。把理解链接、装载与库作为程序员的自我修养是否合适?我想,它算不上充分条件,不过确实是一个必要条件。不仅对程序员来说必要,对任何一个从事计算机研究与开发的人来说都是有价值的。

复习C++,看了点书

2009/05/01 | 19:48 | 分类:学习随感 | 标签: | 4,444次阅读

  冠仔和晓玮最近找我讨论了不少C++问题,这促使我重新拿起书把C++复习了一阵。大二的时候学习C++,看的是《Thinking in C++》,对那些概念、规则记得比较熟,但往往说不清每种特性在实际中到底有什么用,它们为什么要这样设计。前一段时间挑了《The C++ Programming Language》《C++ Primer》中的部分章节阅读,加之有了几年的工程实践,很多问题也便豁然开朗了。对复习过程最受用的还要算《The Design and Evolution of C++》这本在国内不太流行的书,要知道它的作者同样是C++之父Stroustrup。这本书从技术史的角度阐述了C++的每个特性的设计目的和实现机理,使人对C++一些看似蹩脚的设计不再感到奇怪。它与微软那本《The Old New Thing: Practical Development Throughout the Evolution of Windows》给我们一个共同的启示:在不了解一个事物的历史源由的情况下,不要轻言其设计得愚蠢。像C++和Windows这类相对成熟的产品,每个细节的存在都是有一定道理的。
  为检验自己的复习效果,我又借了本这两年买得很火的《程序员面试宝典》(第二版)来。冠仔一看这书,马上提出了他的反对意见,说这书内容很功利,编写很浮躁,还有一些错误。这也倒好,我可以批判性地阅读,看看自己有没有能力把书中的错误找出来,或者给回答得浮躁的问题一个更好的答案。花了两个半天读完,觉得这本书的瑕疵还真是不少,仅以我重点阅读的“C/C++程序设计”部分,就发现了若干错误或不妥的地方:
  ● 5.2节例题1、2,main()函数的定义不符合标准,要知道“int main()”和“return 0;”是往往考查过程中一个重要的细节;
  ● 5.6节例题1,源代码中出现了中文字面值,而且没有用wchar_t存储也没有用转义,这是一种依赖于编辑器和编译器的不好的编程习惯;
  ● 6.2节扩展知识,C++中标准不支持“不写类型默认为int的定义”,“const bufsize;”的首要错误应该是这一条;
  ● 6.3节例题1,混淆了“位”与“字节”;
  ● 6.3节扩展知识,尽管看上去VC调整后的3个变量只占了连续的9个字节,但运行时栈里还是要遵循对齐的,char所在的字还是有3个字节未用,这和gcc(Dev-C++)没有本质区别,并非减少浪费。要想说明编译器“聪明”,可以多写几个char和int穿插,但这种调整布局的优化VC/gcc都会做,无所谓谁更“聪明”;
  ● 7.2节例题2,“全局区域的值是不能进行修改的”表述有误,应该强调字符串常量保存在只读的数据段,而不是像全局变量那样保存在普通数据段(静态存储区);
  ● 10.2节例题2,对“Test b();”的解释含糊不清,其实它就是一个参数为void、返回为Test对象的函数的声明,把这一点讲出来,答案自然就清楚了;
  其它还有一些没有回答到点子上的问题,就不一一例举了。尽管如此,我还是从中学到了一些在经典教材上没有注意到的问题,比如C和C++下const变量默认连接规则的不同(6.2节)、volatile变量使用时的注意事项(11.2节)等。总体来说,《程序员面试宝典》为面试突击看看还行,但不要把里面的字字句句当圣经——它很多地方经不起推敲。

C和C++处理register关键字的一处差异

2009/04/19 | 23:08 | 分类:计算机科学与编程 | 标签: | 10,397次阅读

  C++并不是完全兼容C语言的,上次提到的sizeof('a')等于几的问题就是一例。今天我在编码时又无意中发现了一处不同:
  用register关键字修饰的变量,在C语言中是不可以用&操作符取地址的,这是我已有的经验。因为编译器如果接受了程序员的建议把变量存入寄存器,它是不存在虚拟地址的。但在C++中,用register修饰的变量可以用&操作符取地址,这是我在一段代码中发现的。如果程序中显式取了register变量的地址,编译器一定会将这个变量定义在内存中,而不会定义为寄存器变量。
  我在C99(ISO/IEC 9899:1999)和ISO C++(ISO/IEC 14882:2003)标准中得到了确认,C和C++标准对register遇到&的处理确实有不同的明确定义。但为什么要这样定义?我只能从标准的字里行间猜测。K&R C1中如何描述register我尚未查证,K&R C2(ANSI C)中说明了“register variables are to be placed in machine registers ... but compilers are free to ignore the advice ”。但在C99和ISO C++中,措辞分别变成:“suggests that access to the object be as fast as possible”、“a hint to the implementation that the object so declared will be heavily used”,不再特别提及“machine registers”。可见历史上register关键字在强调尽可能地把变量保存到寄存器,而现在的register关键字不再强调具体手段,只是建议编译器通过各种可行的方式优化该变量的访问(不过很多编译器会忽略这一关键字,而采用自身的优化策略)。C99可能是为了保持对K&R C的兼容而不允许取地址操作;而C++也许是因为没有历史包袱才放宽了这个限制吧。猜测而已,希望知道内幕的朋友告诉我更精确的答案。

编译器与标准——严格还是智能?

2009/03/21 | 14:21 | 分类:计算机科学与编程 | 标签: | 4,052次阅读

  最近做的C/C++工程需要在多种平台下编译(下面所说的编译泛指编译、连接等过程)并运行通过,多种不同版本的编译器考验着代码正确性和严格性。经测试,我们的历史代码中存在着这样几类问题,使得它们在VS2008和低版本gcc下编译通过,但在高版本的gcc下编译就会或多或少地报错:
  1、使用了外部的函数,但没有在本文件中声明或#include对应的头文件。VS2008和gcc3.2在很多情况下可以智能地从一同编译的其它文件或标准库中找到对应的函数并连接,而gcc4.3要求必须声明。
  2、使用模板时,T::name表示T::name类型还是T类的name成员?即使不使用typename关键字,VS2008和gcc3.2在很多情况下可以智能推断T::name a;中的T::name表示表示一个类型。但在gcc4.1/4.3下必须使用typename T::name来表示T::name类型。
  3、隐式类型转换。对于函数Func(string&),在VS2008中可以采用Func(string("new"));来调用,但gcc不能隐式地转换构造函数的返回,需要使用string str("new"); Func(str);来调用。
  4、一些copy-paste粗心小问题,比如声明类的成员函数时不应该加“类名::”限定。如果加了,VS2008会忽略,而gcc4.1/4.3会报错。
  5、非标准的库函数,比如VS2008的itoa(_itoa)。如果说上述几条是“严格性”的问题,这个应该算是“正确性”的问题了———个编译器扩展的内容在另一个编译器看来自然是不知所云。当然,为方便起见,使用编译器提供的非标准扩展也是可以的(比如在不完全支持C99的gcc和VC版本下,分别使用atoq和_atoi64来代替C99的atoll),但这时候别忘了使用条件编译,针对不同的编译器提供不同的代码。
  等等。
  总体来看,我们用到的几个编译器的严格程度如下。注意仅仅是从一些小的侧面反映的结果,不完全准确,说明大致情况而已。

  ─────── 更严格 ──────→
   VS2008  gcc3.2  gcc4.1  gcc4.3
  ←────── 更智能 ───────

  编译器的严格性也许是一把双刃剑。但我个人认为严格一些还是利大于弊的。拿上面的问题1来说,我就遇到过gcc3.2“智能”连接错误的问题,而如果使用gcc4.3,这个bug就可以轻易地被定位,节约大量的调试时间。
  智能的底线是正确性。如果代码中出现了不规范的写法,编译器给出智能判断并忽略问题的同时,应该给程序员一定的警告信息,让他们核实代码并判断是否需要向标准的方向修改。不过可悲的是编译器的警告往往被程序员们一略而过了。也许正是因为这一点,gcc才变得越发严格,试图和程序员划清责任域,减少扯皮现象?
  当然,仅仅写出语法上严格的代码,解决了不同编译器下编译通过的问题,我们还不能保证程序运行时语义的正确性。例如程序中存在未初始化的变量,一种编译器的实现是以0填充,恰好与程序员的原意相符;而切换到另一编译器下,未初始化的变量成了随机值,程序运行就会出问题。所以,对C/C++这样高度自由的语言来说,严格的编译器也不是万能的,人的因素始终是第一位的。
  说了这么些严格和智能,评判它们的依据是什么?自然是语言的标准了(C99、ISO C++之类)。有关编译器对语言(包含库)标准的支持,我们大约可以按以下维度划分:

      标准要求实现的
         ↑
         │
  标准未定义的 │ 标准定义的
     ←───┼───→
         │
         │
         ↓
      编译器扩展的

  标准定义的语法在任何编译器下都应该有相同、无歧义的语义。尽管其内部实现可能不同,或是依赖于具体的机器架构,但在标准中总有明确的说明。而标准中也存在大量未定义的语义,这通常是为了让编译器作者放开限制,做更高效、更简单的实现。这类语法在不同的编译器或不同的运行环境下可能产生不同的结果,经典的例子就是n=m+++m++这种副作用语句的叠加,再如L'abcd'表示什么的问题。
  工程中通常应该严格禁用标准未定义的语法,因为机器架构、编译器种类和版本、优化选项、运行时环境等的变化都有可能对程序的运行结果产生不可预知的影响。但有些时候标准未定义的的语法也是有用的,比如为了学习理解编译器的原理和优化算法。另外在一些技巧性游戏,如Quine,以及IOCCCTime Limit Exceeded这样的hack竞赛中,使用未定义语法的捷径也是未尝不可的。
  而编译器扩展的语法和库则是一类相对有用的工具。不同于标准未定义的内容,编译器所做的扩展也是有严格定义的,不会产生不可预知的结果。在对代码的迁移性要求不高的场合直接使用并无大碍。如果对代码的迁移性有要求,那就如上文所述使用条件编译来限制其作用范围。毕竟编译器所做的扩展是为了弥补标准库的不足,方便开发者。如果对代码将来的使用范围有明显的预知,就需要权衡是否使用编译器的扩展特性。不过就连Linux内核的代码都在使用gcc的扩展语法
  在C/C++语言教学中,强调标准是十分必要的。然而市面上的C/C++教材鱼目混珠:那天在师兄的桌上看到的一本国内某知名高校出版的C++教材上,Hello World程序竟有3处明显的问题,不用试验就知道在VS2008或gcc下编译有两处是Error,一处至少是Warning(据说在VC6下可以编译通过)。一些高校计算机教学中普遍使用历史遗留的、非标准的编译器也成为滋生不良编程习惯、阻碍标准推广的源泉。究其原因,某些教师的惰性是一方面,而相关资格考试与现行标准、业界规范的严重脱节也成为应试教育体制下技术标准难以为师生重视的重要因素。正面的例子又如何呢?北理工计算机学院有一门程序设计实践与方法的本科课程,这门课的重心本是算法与编程技巧的训练,但由于它使用了基于gcc的Online Judge机制和像ACM-ICPC一样严格、众多的测试用例,使得学生对代码的标准与严格、算法的正确与高效、条件的完全覆盖必须做仔细的考量。我自己上过这门课,也做过这门课的助教,两种角色的体验让我深知师生各自的抱怨与苦衷。错误究竟在谁?我想不在于这门课的教师和学生,而在于这门课之外的计算机教育氛围。改变这个氛围并不可以一蹴而就,但至少有两点是可以尽己所能的:主观方面,建立标准意识,培养良好的编程习惯;客观方面,使用实现了符合现行标准的编译器。尽管这对计算机教学的整体环境影响甚微,但起码对未来从事计算机方面的工作来说是有益的——实际工程对标准的要求非同儿戏。

页面: 上页(较新) 1 2 下页(较旧)