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(n2的时间复杂度,熟悉算法的同学,当然知道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
 

你可能感兴趣的:(code)