好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Try your best to make your code elegant!

Try your best to make your code elegant!

 

浅淡“人如其‘码’”

——看一道C基础笔试题有感

又是好久没有写博客,年底比较忙,《Performanced C++》系列也在努力酝酿但没有更新。不过近日出笔试题时,看到个题,以及各种不正确答案和烂代码,感触实在太多。

忘了以前在哪看过,说笔试时候写的代码虽然只有短短几行至几十行,但却能完完全全把一个coder的真实水平体现的淋漓尽致,正所谓古有“人如其文”,看他写的文章就知道他有多少斤两。对于我们coder而言,就是“人如其‘码’“,一道看似简单的笔试题,其实完全可以考察出coder至少以下n个方面:

1、代码风格和规范:从落笔第一刻开始体现

2、异常安全的“危机感”:从是否对传入参数的有效性有“敏感”开始,记住一条准则:永远不要信任用户的输入,这也是防SQL注入等手段的最基本原则之一

3、类型安全的“危机感“:很简单的其中一个方面,是否考虑过,诸如指针转换是否安全这样的问题

4、思路清晰程度:很简单, 从写下的代码是否涂了又改,改了又涂的多少程度就可以看出来。但完全没有任何涂改的答案,通常可能知道这个题的答案背下来的,那么,面试时稍加深入一问,就不攻自破

5、算法和数学水平:抽象的讲即“解决问题的能力”,就是最终是否能给出一个正确解,不管代码是否烂、有无考虑其它方面的问题

6、性能优化思想:是否有性能优化、考虑给出最优解的思想,即使最后的答案不是最优的,但在能保证正确性的基础上尝试过努力

7、对所使用的语言的理解程度:这条可能很多人忽视了。对于C语言的笔试,这一条旨在要求笔试者,尽可能从C语言的思考方式——底层,来解决问题,通常就是操作指针,而不是使用库函数甚至第三方“高级”库,假设是Java笔试,是否有OOP的设计思想。当然,明显可以看出,使用C语言笔试更有区分度。

8、平时所写或所读的代码量:通常可以有个估计,起码可以判断笔试者是否有读过某些名著,因为读过就可以把此题至少解的半斤八两

9、移植性如何:很简单,通常对于字符串操作的函数,如果想要声明int是使用的是size_t,证明你考虑过这方面的问题,起码看过一些源代码

……想起来再补充

好了,上面说了很多废话,下面看这篇文章的主要内容:

笔试题,C语言,实现strstr函数。这是一道基础得不能再基础的C语言笔试题,但我google了半天,发现能给出真正正确答案的,却不多。由此可见在真正笔试中,如此一道基础题目,如果严格按照上述几条来判定,足以刷掉80%以上的笔试者。 

假设我现在是一个笔试者,拿到此题,假设我不知道strstr是干什么用的(其实题目会告诉你,要求你实现的这个函数的作用。但是,由于strstr是标准C库函数且有点儿常用,而你不知道他干什么用,那么基本可以假定你没有用过这个函数或是用过了也不熟悉,更不会清楚其内部实现,那么也就基本可以假定,你对标准C库不熟悉,也不会清楚其内部实现,那么也就基本可以再假定,你对C语言并不熟悉,从而假定你对以下东西都不怎么熟悉:Unix/Linux、GCC/GDB……从而基本假定这个C语言的职位并不合适你),那么最好的方法就是暂时不去参加笔试,特别是C语言笔试,详细阅读《The C Programming Language K&R》、《C A Reference Manual(5th Ed)》、《UNIX环境高级编程》等世界名著,并自己在Linux下把上述书中提到的标准C库函数都coding一遍,再上战场。

现在假设我知道strstr的作用,很简单,不就在s(src)源字符串中查找目标字符串t(target)吗?找到返回第一次匹配源串中的开始指针,没找到返回NULL。

下面是一个正确的实现:  

 1   const   char * mystrstr( const   char * s,  const   char * t)
 2  {
 3       const   char  *p = s;  const   char  *q = t;
 4       if (NULL == s || NULL == t)
 5           return  NULL;
 6       while (*s)
 7      {
 8          p=s;q=t;
 9           while (*p == *q && *q !=  ' \0 ' )
10          {
11              p++;
12              q++;
13          }
14           if (*q ==  ' \0 ' )
15               return  s;
16          s++;
17      }
18       return  NULL;
19  }

  也许乍看并不觉得这段代码有什么了不起,但如果你和以下的烂代码比较(甚至都不正确),就会发现上面这段代码是多么的elegant(优雅)。

另外要提一句,Linux Kernel/Gcc并非如此实现这个函数,因为这个函数的算法和性能都不是最优的,在上述几个方面的6并不能得到高分。但如果在笔试中能写出这样的答案,通常已经可以得到满分了。Why?因为上述这段代码,首先一点:自己完全从底层(还不够底层?那只能嵌入汇编了)实现这个函数,而没有调用其它任何库函数。我想这是C语言笔试最基本的常识之一。说到这里又要吐槽了,我年轻时候参加一加做金融类软件的公司的笔试,C++的,其中有一道题,把一个Unix时间戳(1970年开始的秒数)转换为当前时间。OK,小case,我写了半天,自己经过计算完成了这个函数。结果面试时,面试官一看就愣了,问我,这个函数要这样实现吗?我一听以为自己写的有问题,没想到面试官说:“你不会用时间函数吗? 不会用时间函数库吗?不管哪个语言都有时间函数吧?!需要自己来实现吗?!你自己写,可能写对吗?!这个时间计算很复杂的,你怎么可能那么快就写出来了?!”于是接下来,愣的就是我了,我当时想问一句“你需要写代码吗?不会买产品外包吗?不管什么需求都可以买到产品或外包吧?!需要自己来开发吗?!……”不过我还是忍住了,毕竟笔试者是弱势群体。有趣的是,这位奇葩的面试官还通过了我的笔试,让我过几天再去复试,我没敢去。我也不知道这样的人怎么能成为该公司的技术管理人员的。我只知道,我回家后,把当天写的时间转换函数又写了一遍,在Linux上跑了几个小时没发现计算有错误。吐槽了那么多,想说的无非就是,既然选择做技术,就要尽可能专业,因为这个行业,是“科学”,实在不是“差不多”、“马马虎虎”、“年底写个报告”、“忽悠忽悠“就OK的行业,如果不能适应这样苛刻的规则,转行是最好的出路。 

偏题了。接着说上面的笔试题。上面给出的答案,算法并不是最优的,但其它方面做的很好,而且最容易理解。它是O(n 2 )  的时间复杂度,熟悉算法的同学,当然知道KMP或其它算法,可以将它优化至O(N)。当然,因为此题考察的并非是KMP算法,而是上述几个方面中除算法优化的其它所有方面。

下面开始看烂代码。

烂代码一:看似OK,其实根本就不正确的代码(出处我就不转了),你能发现哪有问题吗?

 1   // 烂代码1
 2   char * _strstr( char * s1,  char * s2)
 3  {
 4       char  * p, *r;
 5      p=s1;
 6      r=s2;
 7      assert(s2 && s1);
 8       while (*r!= ' \0 ' )
 9      {
10           while (*p++==*r++);
11           if (*p== ' \0 ' )
12               return  s2;
13           else
14          {
15              p=s1;
16              r=++s2;
17          }
18      }
19       return  NULL;

考虑最简单的一个test case:s2为"abcdef",s1为“deg",上述代码竟然输出s2中'd'的地址!,就是认为在s2中可以找到"deg“!

另外,写这个代码的同学,把s2和s1的参数逻辑顺序搞反了,也就是说,标准C库函数strstr的第一个参数,应是源串,第二个参数是目的串(就是要在源串中查找的串)。这位同学刚好到了过来,不仅代码阅读别扭,而且这种与标准C库相背的行为,也是非常不提倡的,同时,代码中的assert判断虽然体现了异常处理思想,但手法十分业余。此答案得分为0,因为连起码的正确性都不能保证。

烂代码二:改进了一下,但还是不对,你能看出哪又有问题吗?  

 1   char * _strstr( char * s1,  char * s2)
 2  {
 3       char  * p, *r;
 4      p=s1;
 5      r=s2;
 6       if (NULL == s1 || NULL == s2)
 7           return  NULL;
 8       while (*p!= ' \0 ' )
 9      {
10           while (*p == *r)
11          {
12              p++;
13              r++;
14          }
15           if (*p== ' \0 ' )
16               return  s1;
17           else
18          {
19              p=s2;
20              r=++s1;
21          }
22      }
23       return  NULL;
24  }

  再考虑一个最简单的test case:s1,s2均为"abcdef",上述代码竟然可能输出NULL!认为没有匹配到目标字符串。得0分。

烂代码三:可以正确工作的代码,但风格实在太差,又是for,又是临时变量,两次和'\0'的比较竟然风格不一致……这位同学,可以将你的代码写的elegant一点吗?

 1   char * my_strstr(  char * str1,  char * str2 )
 2  {
 3     if  (NULL == str1 || NULL == str2)
 4    {
 5          throw ;
 6   }
 7   
 8    char  *p = NULL;
 9    char  *q = NULL;
10    const   char  v =  ' \0 ' ;
11    for  ( int  i= 0 ; v != str1[i]; ++i)
12   {
13     p = &str1[i];
14     q = str2;
15     
16      while  (v != *q && *q == *p)
17     {
18        ++p;
19        ++q;
20    }
21    
22     if  ( ' \0 '  == *q)
23    {
24           return  &str1[i];
25    }
26   }
27   
28    return  NULL; 

另外,既然是C代码,throw从何而来?这就是用C++来写C的例证,通常就是对所使用的语言没有较深入的理解,而是只知皮毛,通常也是各种低效垃圾代码产生的前罩。得分5(满分10)。

现在你知道了,这么基础的一道题,为什么那么多人写不对?并不是说这些笔试者水平就一定差,但通常就是细心和认真的程度不够,即使是在自己的笔试中。 

更多烂代码就不再举例了,写这篇文章的目的,就是希望自己和众园友能引以为戒, Try your best to make your code elegant! 

iCC Develop Center

 

分类:  C/C++ 技术积淀 ,  C面试常见问题

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Try your best to make your code elegant!的详细内容...

  阅读:39次

上一篇: HSF源码

下一篇:素数算法