“(c = *str) != 0”是什么意思?

3
int equiv (char, char);
int nmatches(char *str, char comp) {
    char c;
    int n=0;
    while ((c = *str) != 0) {  
       if (equiv(c,comp) != 0) n++;
      str++;
    }
    return (n);    
}

“(c=*str) != 0” 的实际含义是什么? 能否有人给我解释一下,或者帮助我找到正确的术语来自行搜索解释呢?

3
这会取消引用指针str并将其所指向的值赋给c,然后将c与零进行比较。 - ForceBru
*str 取自 str 的地址;c = *str 将该值赋给 c(c = *str) != 0 检查该值是否不为 NULL。在 C/C++ 中,NULL 是字符串结束标记,参见“null terminated string” https://en.wikipedia.org/wiki/Null-terminated_string。 - Dims
1
@Dims NULL 用于表示空指针,而不是空终止符。混淆这些会导致困惑。另外,请勿在评论中回答。 - user694733
@user694733 请查看我的维基链接。 - Dims
1
@ Dims 它是以空字符结尾的字符串,而不是以 NULL 结尾的字符串——该字符为 NUL 而不是 NULL。我最近自己也犯了这个错误:/ - Chris Turner
显示剩余3条评论
7个回答

5
这个表达式包含两部分:
  • c = *str - 这是从指针中获取值并将其赋给 c 的简单赋值语句。
  • val != 0 - 这是与零进行比较的表达式。
这段代码的原理是基于赋值语句是一个表达式,也就是说它有一个值。在这种情况下,赋值的结果和被赋值的值是一样的,也就是指针所指向的 char 类型的字符。因此,这个循环会追踪到以空字符结尾的字符串的末尾,并将沿途遇到的每个字符都赋值给 c 。
需要注意的是,在C语言中,!= 0部分是多余的,因为while循环的控制表达式会被隐式地与零进行比较。
while ((c = *str)) {
    ...
}

第二对括号在语法上是可选的,但是在像这样的赋值操作中,它被保留以表明该赋值是有意的。换句话说,它告诉代码读者你确实想要写一个赋值 c = *str ,而不是一个比较 c == *str ,后者在循环控制块中更为常见。第二对括号还可以抑制编译器警告。

"!= 0" 是多余的,但这个答案中额外的 "()" 也是多余的。两者都有同样的目的:消除编译器警告并明确代码不是 "c == *str"。根据风格和经验的不同,一个比另一个更受欢迎。 - chux - Reinstate Monica

2

令人困惑的是,

while ((c = *str) != 0) {

相比之下,while (c = *str) { 更易于阅读。

这样做还会将*str处的字符赋值给变量c,并且当字符串末尾被到达时即*str等于\0时,循环将终止。

在条件语句中进行赋值操作,如上所述的方法初看可能会感到困惑(与完全不同的c == *str的行为相比较),但它们是C和C++中非常有用的一部分,你需要习惯它们。


我认为你的意思是它们是等价的(即它们的等价性是重言式)。 - eerorika
1
我不确定是否应该将赋值称为“副作用”。 - Paul Ogilvie
建议使用 while ((c = *str)),以避免任何可能的误判 == 的警告。 - Blagovest Buyukliev
1
@Bathsheba:但是复杂性并不是一种美德。(Herb Sutter的有趣开场白。)我更喜欢第一种变体,我甚至会写成 while ((c = *str) != '\0'),以明确表明我正在寻找空终止符。while (c = *str) 这行代码是正确的,但我会想知道你是否真的想要使用 ==。添加括号就像一个“sic”,这是一件好事。写成 strcpy(char *d, char *s) { while (*d++ = *s++); } 可能很酷,但我不想在我的代码中出现这样的东西。 - Martin Ueding
@Bathsheba:我同意深入了解语言是一件好事。但是这个短小的strcpy有很多地方可能会出错。一个例子是*++d = *++s。或者如果运算符像(*d)++ = (*s)++那样绑定。所以我会写出更多行来。我尝试做了一个小基准测试。超短版本似乎偶尔会慢一点,但这可能没有统计学意义。然而,我确实看到较长版本不会更慢,因此我会选择它。 - Martin Ueding
显示剩余2条评论

1

(c = *str)是一个表达式,它本身就有一个值。它是一个赋值语句,赋值的值是被赋的值。因此,(c = *str)的值就是*str的值。

这段代码基本上检查刚刚分配给c*str的值是否不为0。如果不是,则调用具有该值的函数equiv

一旦分配了0,那么这就是字符串的结尾。函数必须停止从内存中读取,它也确实这样做了。


1
它循环遍历字符串str中的每个字符,将它们赋值给c,然后检查c是否等于0,这表示字符串的末尾。

实际上,代码应该使用'\0',因为它更明显是一个NUL字符。


0

我们正在通过while循环遍历str,并提取其中的每个字符,直到它等于零-字符字符串结束的主要规则。

这里是'for'循环的等效形式:

 for (int i = 0; i < strlen(str); ++i )
       std::cout << str[i];

“... until it is not equal to zero ...” 应该改为“... 直到它等于零 ...”,对吧? - alk
没错。从我的语言直接翻译的后果 :) - Hardwired
将strlen作为条件检查可能导致其变成O(N * N)的风险。编译器可能会进行优化,但我不会依赖于此。 - Bathsheba

0

这只是一段写得比较马虎的代码。其意图是从字符串 str 中复制一个字符到 c,然后检查它是否为 null 终止符。

在 C 语言中检查 null 终止符的习惯用法是直接检查是否与 '\0' 相等:

if(c != '\0')

这就是所谓的自我记录代码,因为在C语言中写空终止符的事实标准方式是使用八进制转义序列\0


另一个错误是在条件语句中使用赋值操作符。这在1980年代就被认为是不好的实践了,自那时以来,每个编译器都会对这样的代码发出警告,如“可能不正确的赋值”或类似的警告。这是不好的实践,因为赋值操作包含副作用,具有副作用的表达式应尽可能保持简单。但这也是不好的实践,因为很容易混淆“=”和“==”。
代码可以很容易地重写为更易读且安全的形式:
c = *str;
while (c != '\0')
{
  if(equiv(c, comp) != 0)
  {
    n++;
  }
  str++;
  c = *str;
}

0

你不需要使用 char c,因为你已经有了指针 char *str,而且你可以将 != 0 替换为 != '\0',以获得更好的可读性(如果不考虑兼容性)

while (*str != '\0')
{  
    if (equiv((*str),comp)
          != 0)
    { n++; }

    str++;   
}

为了理解代码的作用,您可以这样阅读它

while ( <str> pointed-to value is-not <end_of_string> )
{
    if (function <equiv> with parameters( <str> pointed-to value, <comp> )
            returned non-zero integer value)
    then { increment <n> by 1 }

    increment pointer <str> by 1 x sizeof(char) so it points to next adjacent char
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接