条件中的条件条件分配是一种不良习惯吗?


35

假设我想编写一个在C中连接两个字符串的函数。我的编写方式是:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

但是,K&R在他们的书中以不同的方式实现了它,尤其是在while循环的条件部分中尽可能多地包括:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

首选哪种方式?是鼓励还是不鼓励以K&R的方式编写代码?我相信我的版本会更容易被其他人阅读。


38
别忘了,K&R于1978年首次出版。从那时起,我们的编码方式发生了一些小变化。
corsiKa '16

28
在电传打字员和面向行的编辑者时代,可读性已经大为不同。将所有这些东西拼凑成一行以前易读。
user2357112支持莫妮卡

15
我很震惊,他们有索引和与'\ 0'的比较,而不是类似的内容while (*s++ = *t++); (我的C语言非常生锈,我需要在此处使用运算符优先级吗?)K&R是否发布了该书的新版本?他们的原始书具有极为简洁和惯用的代码。
user949300

4
可读性是非常个人的事情-即使在电传打字时代也是如此。不同的人喜欢不同的风格。大量的指令填充与代码生成有关。在那些日子里,某些指令集(例如Data General)可能会将多项操作塞进一条指令中。同样,在80年代初期,有一个神话认为使用括号会产生更多指令。我必须生成汇编器才能向代码审阅者证明这是一个神话。

10
请注意,这两个代码块不相同。所述第一码块将不复制终止'\0't(所述while第一出口)。这将使结果s字符串不终止'\0'(除非内存位置已被清零)。第二个代码块将'\0'在退出while循环之前复制终结点。
Makyen '16

Answers:


80

总是要比清晰更聪明。在过去的几年中,最好的程序员是没人能理解的代码。他们说:“我无法理解他的密码,他一定是个天才。” 如今,最好的程序员是任何人都可以理解的代码。现在,计算机时间比程序员的时间便宜。

任何傻瓜都可以编写计算机可以理解的代码。好的程序员编写人类可以理解的代码。(福勒先生)

因此,毫无疑问,我会选择选项A。这是我的明确答案。


8
非常笨拙的意识形态,但是事实仍然是条件赋值并没有错。最好是尽早退出循环或在循环之前和循环内部复制代码。
Miles Rout

26
@MilesRout有。任何有副作用的代码都出了问题,这些副作用是您所不希望的,即传递函数参数或评估条件。甚至没有提到那if (a=b)很容易被误认为if (a==b)
亚瑟·哈维利切克

12
@Luke:“我的IDE可以清理X,因此这不是问题”,这令人信服。如果这不是问题,那么为什么IDE使其易于“修复”?
凯文

6
@ArthurHavlicek我同意您的一般观点,但是在条件语句中带有副作用的代码确实并不少见:while ((c = fgetc(file)) != EOF)这是我想到的第一个。
Daniel Jour

3
+1“考虑到调试首先要比编写程序难一倍,如果编写时尽可能聪明,那么如何调试它呢?” BWKernighan
Christophe'Oct

32

与图兰斯·科尔多瓦(TulainsCórdova)的答案相同,黄金法则是确保编写可理解的代码。但是我不同意这个结论。黄金法则意味着编写代码,最终将维护您的代码的典型程序员可以理解。而且,你是谁在典型的程序员谁最终会维护你的代码是最好的裁判。

对于不是以C开头的程序员,由于您已经知道的原因,第一个版本可能更容易理解。

对于那些使用C风格长大的人来说,第二个版本可能更容易理解:对他们来说,代码的作用是同等理解的,对他们来说,它为什么会按原样编写以及对他们来说却留下了更少的问题。 ,较少的垂直空间意味着可以在屏幕上显示更多上下文。

您将不得不依靠自己的良好意识。您想让哪些读者最容易理解您的代码?此代码是为公司编写的吗?那么目标受众可能是该公司中的其他程序员。这是一个个人爱好项目,只有您自己可以从事吗?那么您就是您自己的目标受众。您要与他人共享此代码吗?那么其他人就是您的目标受众。选择与该受众群体匹配的版本。不幸的是,没有单一的鼓励方式。


14

编辑:该行s[i] = '\0';已添加到第一个版本,从而按照下面的变体1中的描述进行了修复,因此这不再适用于问题代码的当前版本。

第二个版本的独特优势是correct,而第一个版本则不是-不会正确终止目标字符串的空值。

“状态分配”允许非常简洁地表达“ 检查空字符之前复制每个字符”的概念,并以某种方式使编译器的优化更加容易,尽管如今许多软件工程师发现这种代码的可读性较差。如果您坚持使用第一个版本,则必须

  1. 在第二个循环结束后添加空终止(添加更多代码,但是您可以说可读性值得)或
  2. 将循环体更改为“首先分配,然后检查或保存分配的字符,然后递增索引”。在循环的中间检查条件意味着打破循环(降低清晰度,大多数纯粹主义者不赞成这样做)。保存分配的字符将意味着引入一个临时变量(降低清晰度和效率)。在我看来,这两者都会消灭优势。

正确胜于可读和简洁。
user949300

5

TulainsCórdova和hvd的答案很好地涵盖了清晰度/可读性方面。让我将范围界定作为支持条件分配的另一个原因。条件中声明的变量仅在该语句的范围内可用。您以后不能偶然使用该变量。该用于循环已经做了好久了。同样重要的是,即将发布的C ++ 17 ifswitch引入了类似的语法

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope

3

否。这是非常标准的标准C风格。您的示例是一个糟糕的示例,因为它应该只是一个for循环,但总的来说没有错

if ((a = f()) != NULL)
    ...

例如(或同时使用)。


7
这有问题。!= NULL和它在C条件中的亲缘关系都是轻而易举的,只是在这里安抚那些对值是true或false(反之亦然)的概念不满意的开发人员。
乔纳森·

1
@jcast不,包含更加明确!= NULL
Miles Rout

1
不,更明确地说(x != NULL) != 0。毕竟,这就是C 真正在检查的内容,对吗?
乔纳森·

@jcast不,不是。检查某种事物是否等于假不是您用任何语言编写条件语句的方式。
Miles Rout

“检查某些事物是否等于假不是您用任何语言编写条件语句的方式。” 究竟。
乔纳森·

2

在K&R时代

  • “ C”是便携式汇编代码
  • 被汇编语言思想的程序员所使用
  • 编译器没有做太多优化
  • 大多数计算机具有“复杂指令集”, 例如,while ((s[i++]=t[j++]) != '\0')将映射到大多数CPU上的一条指令(我希望使用Dec VAC)

有几天

  • 大多数阅读C代码的人都不是汇编代码程序员
  • C编译器进行了大量优化,因此,更易于阅读的代码可能会转换为同一机器代码。

(有关始终使用花括号的说明-第一组代码由于具有一些“不需要的”而占用更多空间{},以我的经验,这些通常可以防止编译器严重合并的代码并允许使用错误的“;”位置进行错误处理。被工具检测到。)

但是,在过去,该代码的第二版会被阅读。(如果我做对了!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}

2

即使完全能够做到这一点,也是一个非常糟糕的主意。俗称“世界上的最后一个错误”,如下所示:

if (alert = CODE_RED)
{
   launch_nukes();
}

虽然你可能不会犯了一个错误这是非常,严重的,它很容易不小心搞砸了,并在你的代码库导致难以发现错误。大多数现代编译器都会在条件内为分配插入警告。它们在那里是有原因的,您最好注意它们,并避免这种构造。


在发出这些警告之前,我们将编写该代码,CODE_RED = alert以便产生编译器错误。
伊恩(Ian)

4
@Ian Yoda有条件的被称为。他们很难读。不幸的是他们的必要性。
梅森惠勒

经过一段非常简短的介绍性“习惯”之后,Yoda的条件并不比正常条件难读。有时它们更具可读性。例如,如果您具有一系列的ifs / elseifs,则在左侧针对该条件进行测试以得到更高的强调会比IMO稍有改进。
user949300 '16

2
@ user949300两个词:斯德哥尔摩综合症:P
梅森·惠勒

2

两种样式都格式正确,正确且适当。哪一种更合适,很大程度上取决于您公司的风格准则。现代IDE将通过使用实时语法表皮来方便地使用这两种样式,这些语法显式地突出显示了可能会引起混乱的区域。

例如,以下表达式由Netbeans突出显示:

if($a = someFunction())

以“偶然指派”为理由。

在此处输入图片说明

为了明确地告诉Netbeans:“是的,我真的想这样做...”,可以将表达式包装在一组括号中。

if(($a = someFunction()))

在此处输入图片说明

归根结底,这一切都归结为公司风格准则以及可用于促进开发过程的现代工具的可用性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.