西西河

主题:【原创】猛批烂书 程序员面试宝典 上 -- 晨池

共:💬64 🌺96
分页树展主题 · 全看首页 上页
/ 5
下页 末页
  • 家园 【原创】猛批烂书 程序员面试宝典 上

      最近看到一本烂书,本来忽略过去就是了,可惜这本烂书还挺流行,为了阻止它继续误人子弟,我不得不花点时间出来,猛批此书,请大家不要上当,买了此书的赶快销毁——不要扔掉,以免别人捡去上当,打算买的先来看看我的意见再做决定,把此书借给别人的赶快要回来销毁,从别人那里借了此书的赶快还回并且告诉人家我的意见。

      我自己在面试和被面试上都有一些经验,在本文最后,我会给出自己在这方面的建议,希望能对大家有所帮助,尤其是那些因为我的文章而放弃了这本烂书的同学,我希望我的建议能填补你这方面的需要。

    烂书的书名

      中文名:程序员面试宝典

      英文名:BEST PRACTICE FOR PROGRAMMER INTERVIEW

      (小沈阳:我的中文名字是 小沈阳,英文名字是 Xiao Shen Yang)

    烂的原因

      这本书的目的是赚钱,完全不是为了提高大家的面试笔试水平,所以出现下面的情况,也是理所当然的了:首先,这本书名字起的好,营销也做的好,否则不会这么流行;可惜内容不好,属于有能力没道德类型的坏人,能力越大,破坏越大。其次,这本书基本上是以一种应对考试的方式来应对找工作的笔试和面试,刚刚从学校出来的同学们正是考试的好手,如果还以这种心态来应对笔试面试后果是很不妙的。最后,这本书当中讲解的东西大部分基础简单的内容都是对的,比较高级的内容偏偏是错的,属于隐蔽的很深的坏人,倒霉了还不知道是谁干的,最坏。

      这本书我随便翻了翻,几乎气死,哪里有这样介绍东西的?自己都是一知半解,就来编制宝典,真个是以己之昏昏明人之昭昭啊。还忽悠那么多刚刚或者即将步出校门的同学从微薄的生活费里挤出三十九块钱买来上当。编书的赚到钱了,同学们找不到工作他们可不管——本来我也不用管,可是我的女朋友也在找工作,也买了这本烂书(可以吃多少个排骨啊!),我就不能不管了!

      下面,我会把这本书当中的错误、混乱、语无伦次、蒙混过关之处,只选择我随便翻翻出来的一小部分罗列出来,让大家看看这本书的真面目是如何的烂。这并不是说这本书就只有我列出来的这些问题,还有更多我看到了没有写(没功夫,也不想把他们教聪明了),更多更多的是我没发现的。另外,我个人只对C/C++/STL有深入的认识,所以,我也只对这方面揭批一下,请对其他方面精通的好同志在其他地方揭批其他方面,也请精通C/C++/STL的好同志对我的错误不吝赐教。

    烂的方面

      对第六章到第九章,我随手翻了翻,第十章我看的比较仔细,因为我对C++面向对象很感兴趣。后面的,由于第十章里面错误太多,我没有看以免气到自己(很多地方写的,态度非常成问题!完全是在敷衍,我真怀疑他们写这么一厚本书花的心思,有没有我写这篇短文的心思多?)

    6.4 内联函数和宏定义,在这一个小节当中的问题如下:

      首先,这个小节的写作态度不认真水平很马虎:这个小节大概是在网上搜了搜关于inline和宏就写完了,所以只是语无伦次的介绍了基本概念,但是没有涉及任何高级特性——他们绝对不是为了避免读者无法理解而不涉及高级特性的,而是他们自己不懂,否则的话,我不相信这帮人会不抓紧卖弄一下自己的“学识”。如果你想知道这个小节有多垃圾,就去看看msdn上关于inline的介绍,或者TPCL当中对inline的介绍就可以了。

      其次,罗嗦得语无伦次且不着重点:内联函数和宏定义的区别,大概只要几句话就可以说清楚,这本书用了几段话反而说糊涂了,而且最重要的一句话,没说(参见第五)。

      第三,把清楚的解释糊涂了:原文当中有这样一句“对于短小的代码来说,inline可以带来一定的效率提升,而且和C时代的宏函数相比,inline更安全可靠。可是这个是以增加空间消耗为代价的。”按照这两句话的意思,inline增加空间消耗换来的是比宏更加安全?知道的自然明白是书上漏了一句话,不知道这不就被蒙了?读烂书还是马虎点好,不是被蒙就是被气。实际情况是,inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。

      第四、例子像浆糊一样,注释像例子一样:唯一的一个例子,我不厌其烦的抄录如下、

    inline fac(float i) {return i * i};//没有写返回值

    printf("bb = %d", fact(8));//调用时就是执行printf("bb = %d", 8 * 8);

      这个例子和其中的注释,想说明什么问题?能说明什么问题?

      我估计,想说明的问题是inline和宏一样可以展开,但是那个自作聪明的注释,实在费解,干嘛不写返回值?在华为这样写函数连看门的大爷都会上来鄙视一下,这本书里还当例子拿出来。我估计写这个例子的作者,根本不知道如果一个函数不写返回值的话编译器是如何处理的(本科二年级C语言80分以上的都应该知道),他的意图是向读者展示inline函数确实仅仅是扩展开,即使没有返回值也没有关系,因为inline函数仅仅扩展开就可以了,不需要返回值——他的意图完全错误。而且,调用时候执行的根本不可能是注释当中写的那样,也根本不是例子里面展示的那样会展开,具体如何,请看第五条罪状。

      第五、inlinle函数最重要的一个特性没有说到:这个特性是什么?请你自己先写一个程序(或者复制我的):

    #include <stdio.h>

    inline int fact(int i) { return i * i;}

    int main()

    {

    printf("%d", fact(8));

    }

      把这个程序在VS2003当中编译一下,然后分别在Debug和Release模式下进行调试,断点放在printf那一行,程序暂停以后点击右键,选择“Go to disassemlby”,进入到汇编码,在Debug模式下,你会看到这样的情况:、

    printf("%d", fac(8));

    00411D3E push 8

    00411D40 call fact (41168Bh)

    00411D45 add esp,4

    00411D48 push eax

    00411D49 push offset string "%d" (42B01Ch)

    00411D4E call @ILT+1500(_printf) (4115E1h)

    00411D53 add esp,8

    在release下你会看到这样的情况:

    printf("%d", fac(8));

    00401000 push 40h

    00401002 push offset string "%d" (4060FCh)

    00401007 call printf (401012h)

    0040100C add esp,8

      如果你还没有体会出来,提示一下,在debug模式下有一个call fact,fact?怎么那个inline函数被调用了?不是说展开么?但是在release模式下,你看到8 * 8了没?只有一个40h,把这个十六进制的数字换算成十进制——同样,为什么也没有展开呢?总之,是没有按照那本书例子当中注释说的执行,为什么呢?

      原因在msdn里说的很清楚:The insertion (called inline expansion or inlining) occurs only if the compiler's cost/benefit analysis show it to be profitable. 编译器只在计算之后认为值得的时候才会扩展inline函数。

      这么小一个知识点就能犯这么多错误,也只有这本烂书能做到了。

    7.1节 面试例题3

      这只是一道例题,但是非常典型的展示了这本书是如何的避重就轻的解释问题和作者自己的C++真实水平。题目如下:

    Which of the following is NOT true about the "this" pointer of class X?

    A. It lets each object of class X to access its address. (让X类的每一个对象指向它的地址)

    B. It will be implicitly passed as argument of every non-static member function of class X. (可以隐性传递this指针)

    C. It can not be used explicitly in memeber function of class X. (不能在类的成员函数里明确的声明)

    D. Its type is const X* in const member function of class X. (它是常量函数中的一个常量指针)

      答案是C,原书的解释我就不抄了,仅仅解释了this的用法,还给出了一段完全不着调的例程,对于问题当中考察的四个要点,都没有介绍原因,仅仅解释了一下this的用法,就算解析完成了?这和告诉读者,this指针的拼写是t-h-i-s有什么区别?这还不是最大的问题。

      最大的问题在于添加的那些翻译,这些翻译是书的作者添加的,不是我加的。如果对着那些翻译,你能选出C来吗?ACD都可以选,B之所以不选是因为不知所云。我来给个翻译:

    A. this指针让X类的对象能够访问自己的地址(关键点:every和each的区别,access的词义)

    B. this指针会被隐性的当作一个参数传递给X类的每一个非静态函数(关键点:翻译以前要看懂原文)

    C. this指针不能在X类的成员函数里显式声明(关键点:恰当使用专业术语,汉语可以没有宾语但是不能没有主语)

    D. 在X类的经过const修饰的成员函数里,this指针的类型是const X*(关键点:翻译以前不可以误解原文)

      想到6.4节里那个莫名其妙的注释,这里出现歪曲原意的翻译也是很自然的了,这帮人莫非是用金山快译翻译的?还是盗版的?

    8.1节 面试例题1

      这个题目给出了一个计算阶乘的例子程序,是错的,首先编译无法通过,有语法错误;其次逻辑有错误,没人能预料到它的输出是什么;最后编码风格很烂,这样写代码的人,只能去此书编辑部上班,其他公司都不会要的——总之错的我很无语,这还是示例,我看这是典型错误示例还差不多!写出这样程序的人,需要回炉重造,从if-else开始重学四年编程再说;而把这样程序当例子做示范的人么,只能去编书,对,就是编一本英文名字是“BEST PRACTICE FOR PROGRAMMER INTERVIEW”的书。

      来吧,买书了的请把书翻到83页,看“答案”两个字下面的东西;没书的看这里,我抄上来了:

    int n,t;

    int find(int n);

    {

    if (n=1) t=1;

    else

    {

    return find (n-1) * n;

    }

    }

    main()

    {

    cin >> n;

    find(n);

    printf('N!=', t:1:0);

    }

      看了以后我就想问,这个是什么语言?看到main函数,应该是C/C++,可是最后一行的t:1:0是什么语义?看到用单引号表示字符串,应该是Pascal,但是输出时候用的printf是哪种Pascal里的函数?当n=2的时候,函数会返回什么?为什么要用一个t来保存结果?n只在main函数里面用了,为什么要定义成全局变量?判断条件里的n=1,这错误犯的,我想问问,这本书的作者,你刚开始学C语言吧?知道C++怎么拼吗?我告诉你:C-p-l-u-s-p-l-u-s。

      连个阶乘都写不好,还来教别人怎么笔试面试?二十行不到的程序错误一大堆,还来当例子程序?而且这是多么低级的错误啊,我好久没见到过了。还拿来当例子程序~是在编笑话书吗?把这本书的垃圾代码收集一下,就是一本用C/C++写的笑林广记了。

    9.1节 面试例题2

      这个题目是关于vector的,答案是基本正确的(因为抄了别处的),可惜解释错的颠三倒四(因为是作者自己写的),不看广告,看疗效,原题目如下:

    要求找出程序中的错误

    #include <vector>

    using namespace std;

    class CDemo{

    public:

    CDemo():str(NULL){};

    ~CDemo() {if (str) delete[] str; };

    char * str;

    };

    int main(int argc, char ** argv) {

    CDemo d1;

    d1.str = new char[32];

    strcpy(d1.str, "trend micro");

    vector<CDemo> *a1 = new vector<CDemo>();

    a1->push_back(d1);

    delete a1;

    return 0;

    }

      书上的解释是“vector对象指针能够自动析构,所以不需要调用delete a1,否则会造成两次析构对象”,这个解释,三句话两个错,说明作者第一不懂什么叫拷贝构造函数,第二不懂vector怎么用。vector对象的指针不过是一个指针,它怎么自动析构?它又不是智能指针。如果不调用delete a1,确实不会出现两次析构,但是会出现内存泄漏——一个更低级的错误!至于那个“不需要调用delete a1”,就更低级了,最需要调用的,就是delete a1!后面给出的答案是基本正确的,却在响亮的扇作者耳光,作者还浑然不觉(忙着数钱顾不上):

    #include <vector>

    #include <iostream>

    int i = 0;

    int j = 0;

    using namespace std;

    class CDemo{

    public:

    CDemo():str(NULL)

    {

    cout << "constructor:" << i ++ << endl;

    };

    CDemo(const CDemo& cd)

    {

    cout << "copy constructor:" << i++ << endl;

    this->str = new char[strlen(cd.str) + 1];

    strcpy(str, cd.str);

    }

    ~CDemo()

    {

    if (str)

    {

    cout << "destructor:" << j++ << endl;

    delete[] str;

    }

    };

    char * str;

    };

    int main(int argc, char ** argv) {

    CDemo d1;

    d1.str = new char[32];

    strcpy(d1.str, "trend micro");

    vector<CDemo> *a1 = new vector<CDemo>();

    a1->push_back(d1);

    delete a1;

    return 0;

    }

      解释里面白纸黑字的说“所以不需要调用delete a1”,那正确答案里的倒数第三行是什么?唯一的区别就是少了一个缩进——这本书的作者还以为用的Python啊?那多出来的一个拷贝构造函数是为什么要添加进来?做装饰的吗?如果他们有把程序敲一遍运行一下,也不会给出如此弱智的解释。这样的话,只有完全不懂C++还硬要装懂的人才能说出来。

      如果他们有把程序敲一遍运行一下,也不会发现真正的原因,因为他们会发现,这玩意儿无论是所谓的正确程序还是错误的程序,都是有编译错误的,最简单的编译错误,可是这帮人就连这点小事儿就既没本事看出来也没想到先试试。

      瞪着眼看到参考答案里有delete a1,还给人家解释说“所以不需要调用delete a1”,这错误可不容易犯:首先要眼瘸,其次要无知,最后还得爱面子,三个条件缺了哪一个都不行——还真就犯了,不过这不算最极品的,极品的在下面。

    10.1节 面试例题1

      这个题目是关于面向对象的基本概念,有点见仁见智,书上给出的答案是对象、类和继承。关于这一点,我觉得面向对象的基本概念应该是继承、封装和多态,三个都具备才算完全的面向对象语言,否则只能说是一个半成品,比如VB 6.0,以后的VB情况我就不知道了。

    10.1节 面试例题2

      这道题目不难,本身没什么问题,问题出在翻译上(又是翻译!):We use keyword class to create a class construct. (我们用关键字类来创建一个类的结构)。

      对于第一个class的翻译,我只能说,他们没翻译成“班级”,已经谢天谢地……这一次莫非又用了盗版的金山快译吗?笑话里面扔硬币做选择题的学生还知道重新扔一遍验算一下,难道这帮编书的,就不知道用谷歌翻译验算一下啊?

      这本书还有一个章节,是英语面试,罗列了一些常用词汇,我很好奇,这部分,是用什么软件做的?不会还是盗版的金山快译吧?够呛。

      

    10.2节 面试例题2

      这个题目是典型的展示了本书作者的态度有多大问题——他们的能力方面显然不及格,但是态度肯定是零分。请看题目吧,重点在解析。

      现有以下代码,则编译时会产生错误的是_______。

    struct Test

    {

    Test (int) {}

    Test () {}

    void fun() {}

    };

    int main()

    {

    Test a(1);//语句1

    a.fun();//语句2

    Test b();//语句3

    b.fun();//语句4

    return 0;

    }

    解析:Test b()是不正确的,因为它不需要预先赋值。不像Test a(1)需要预先赋值,所以Test b()改为Test b即可。但在程序中这个错误在编译时是检测不出来的。出错的是语句4 “b.fun();”,它是编译不过去的。

    答案是语句4

      对解析的解析:首先,请那些看题目做不出来,但是看了书上的解析以后明白的举一下手说说作者什么意思——我没看出来解析部分解析出了什么,如果原来不懂,看了解析以后只能更晕,因为作者本人也不懂,解析是他们装懂装出来的。

      然后,要问作者的一个问题是,既然说“Test b()是不正确的,因为它不需要预先赋值”,那么,Test b()给b预先赋了一个什么值?如果Test b()没有预先给b赋值,那凭什么说它由于“不需要预先赋值”而错误?进一步说,既然Test b()是错的,Test b是对的,那么这两个之间有什么区别?

      解析当中说,“Test b()改为Test b即可”,那么改成Test b(0)可以不可以?Test b、Test b(0)、和Test b()有什么区别呢?如果连这三个区别都不知道,怎么说、凭什么说哪个错了该改成哪个呢?作者不知道,但是我知道:作者是怎么说的呢?是胡说的;是凭什么说的呢?是凭不懂装懂说的。

      这几个问题,作者是在解析当中没有解释,也无法回答的。事实是,Test b()这样的写法在语法上是完全正确的。而且这几个问题也不是孔乙己那样“茴香豆的茴字,有几种写法”的问题,而是确实有实际用处的。我在工作中有遇到问题就是利用了Test b()这样形式的语法来解决的,简化了程序设计框架也降低了程序当中出错的可能性。如果你想知道这些问题的答案,可以发送邮件到我的邮箱:[email protected],我不在这里写出来,以免把那帮人教聪明了,其他地方,我也尽量不写出解答,原因类似。

      这个题目是抄的,所以题目挺好;答案是抄的,所以答案是对的;可惜解析是自己写的,所以解析啊……除了把作者对C++的一无所知解析的淋漓尽致以外,什么也没解析出来。

    10.3 10.4 10.5 小节

      每个小节都有问题,都是属于对语法概念自己不懂还装懂的,我没时间写出来了,先放在这里,请大家注意。虽然问题和前面的不一样,但是问题的低级程度和不懂装懂的性质都是一样的。要找到这样的问题,只需要在你不懂的时候,多问几个为什么就可以了,凡是不能自圆其说的,都是书上错了。C++语言本身是自洽的,不会出现“因为XX错了,所以要把XX换成YY就可以”这种没道理的话——如果XX错了,一定有原因,这本书上没说,不是没有,而是作者们不懂。

    序言

      没错,序言。不过序言没有犯技术问题,只是提醒一些涉世不深的同学,在序言里,可以看到一个小唐骏的精彩演出。

      请你把序言翻到第二页,看一下。第二页的前三行,是序言,然后,是一个很大的签名,一个很显眼的微软的标志以及序言人在微软的职位,接着很长一串是他的各种头衔,从微软的外聘顾问到某公司的总监加总裁,到其他讲师,到华为工程师,最后,居然还有一个省略号,明显是嫌这张纸太短放不下他的头衔么。

      这一页纸,三行序言,一个签名,二十多行头衔,还有一个省略号

      有哪本书的序言有一页零三行,作序人的签名+头衔有一页欠三行加一个省略号的?

      如果是比尔盖茨来作序,他会写上一大串:在哈弗大学读书、在哈弗大学获得……、曾经荣获美国某州县市数学竞赛、创建微软公司……?干嘛给那么多名词儿贴金呢?比比可能就签个名。

      我很好奇,他用省略号省略掉的头衔里面,有没有他小学三年级时候获得的卫生流动红旗?当然,可不能这样写,要写“荣获第一届XX市卫生红旗手称号”。

      这些头衔,只让我想起了唐骏博士的“在加州理工大学读书,然后获得博士学位”这句微妙的话。而这本书,必然是头衔多过实质,包装胜于内容,销量全靠营销。

      今后,看书如果头衔吓死人,就赶紧丢掉,一定是只是吓人的。

      设计模式这本书是计算机界最经典的书了,书名如何朴实,Design Pattern,设计模式,没有说“架构师必备宝典”“设计工作指导圣经”吧?序言有没有我都不记得了,但是肯定没什么头衔,GoF就是最大的头衔,只有三个字母,但是谁不肃然起敬?对那个一页欠三行的头衔……我只想到了一个成语“物以类聚,人以群分”。

    元宝推荐:铁手,
    • 家园 学java的,当初直接下个葵花宝典看看就上。
    • 家园 面试还是考卷,怎么全是编程
    • 家园 关于 inline

      C++中,inline实际上是一个hint,它提示(或建议)编译器此成员函数宜于展开使用,既然是建议,编译器可以不予理睬。原因很多,有可能编译器并不支持函数展开,也有可能用户寻求最短代码而非高效代码。

      C++不强制inline函数展开,这样做是有道理的。它增进了代码重用。当调用环境需要较小的代码时,inline就被忽略。而在重视代码效率的环境下,inline 就得到采用。由于使用了inline,函数可用于不同的环境中。

      另外,inline所换来的效率提高并不一定增加空间消耗。inline展开的结果之一,就是展开的代码可以在调用环境中予以优化,这样产生的代码反而可能更小。晨池例子的第二部分就说明了这个问题。

      原来的代码是 printf("%d",fact(8))

      通过展开变成 printf("%d",8*8)

      因为 8*8 是常数表达式,继续优化成64(40h)

      printf("%d", 64)

      其机器代码为

      push 40h

      push "%d"

      call printf

    • 家园 果然是面试宝典,俺要去买本

      拿来给被面试的人做题改错

      哈哈

    • 家园 写本书实在很不容易

      别太苛责,

      以你的水平也不用看这本书呀....

      • 家园 他们写这本书,写的太容易了,这个可不是苛责

        你看看那段求阶乘的代码,即使是一个刚刚学编程的学生,写成这样都没脸拿出来让人看,他们还当成例子代码,当然,如果你不懂编程,当我没说。

        一本误人子弟的烂书,偏偏营销做的好,不是要害更多的人么?只不过是为了骗钱,根本不是在写书

        • 家园 看了下,好像是过分了点

          这本书好像以前还听过,但我没看过。。。。。。

          这个行业本来就不好,作者的稿费低地令人发指,出版社只管宣传和推销不在乎书的含金量,而审稿的又全是不懂coding的,只管捉错别字,烂书就这么一本本出来了,反正外行很喜欢买这种入门书籍,所以销路向来不错

    • 家园 惭愧惭愧

      俺是06年就看过这本书的,当时找到了错误就记在旁边的空白处。虽然密密麻麻的也找了不少,但是楼主指出的致命BUG却一个都没发现。看完该书还推荐给同学(当然了,是俺的修订版),丢人啊丢人。

      这本书对于刚出校门,且有一点编程水平的人,还是有点用处的。至少能知道面试会考些啥,权当他是一个面试习题集吧。对于刚毕业的大学生,想把这些题目都搜集全,也是颇费精力的。

      作者的水平,自然就别提了,看看作者介绍就知道了,确实就是楼主说的“小唐骏”。

      • 家园 如果是一本习题集,倒还真可以

        答案也可以看,就是不能看解析,我工作好几年了,如果是刚刚毕业或者大四时候,还看不懂呢(那时候我不懂C++。。。)

        这本书,四年前就出来荼毒生灵了?!

        • 家园 四年前刚出来时买过一本

          好像是9月份发行的,我是10月买的(记不太清了)。楼主看的估计是第二版了。

          开始时还真把他当宝典了(汗)。经常为里面的问题头疼半天,等发现了是作者的弱智错误的时候,先是大怒——浪费大爷一堆时间;继而大喜——作者的水平还不如我,呵呵。

          我看的主要是C语言部分。看完后就感觉,作者连谭老板的《C语言程序设计》都没看完。

          PS:虽然如此,当年看过这本书之后,还挺感谢作者的呢。为表谢意,俺还把发现的错误都email给作者了。理所当然的没有消息。

          回去就把那本书找出来,比对一下,看看当年的错误还在不在。

分页树展主题 · 全看首页 上页
/ 5
下页 末页


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河