用于在C中使用指针的循环

MeC*_*ris 40 c arrays string pointers for-loop

我不明白指针在for循环中的作用.什么是*p在下面的循环呢?

char str[128] = "Some Text";
char *p;

for (p = str; *p /*what does this mean?*/; p++)
{
    // Code
}
Run Code Online (Sandbox Code Playgroud)

我确实了解其余的,但为什么不*p喜欢p > 3或类似的东西?
为什么一个人?
为什么这样写?

err*_*kos 53

在布尔上下文(例如for循环的条件)中,C中的每个表达式的计算结果为true(非零)或false(零).

您希望for循环在到达字符串末尾时终止.

在C中,每个字符串都以字符终止'\0',这实际上是0.因此,当for循环到达字符串的结尾时,*p求值为'\0',即0,哪个求值为false,从而终止for循环.


Man*_*dis 31

如果;语句中两者之间的任何内容为零(false),则for循环将终止.*p取消引用p并返回char,p指向.根据Dennis Ritchie的 说法,"C将字符串视为传统上由标记终止的字符数组".该标记是(ASCII)值为零的空字符.所以,这个for循环:

for (p = str; *p; p++)
Run Code Online (Sandbox Code Playgroud)

相当于这些

for (p = str; *p != '\0'; p++)
for (p = str; *p != 0; p++)
for (p = str; p[0] != '\0'; p++)
Run Code Online (Sandbox Code Playgroud)

空终止字符的另一个名称是sentinel或根据Donald Knuth "虚拟值"(Art of Computer Programming,第1卷).下面是str每个字符的字符串,索引(从起点开始的偏移量)和每个索引的值的图表:

在此输入图像描述

为了完整性,在此处的注释请求之后是调试器在str占用的内存块中看到的内容:

0x00007fffffffe6a0:
  0x53 0x6f 0x6d 0x65 0x20 0x54 0x65 0x78 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00
     S    o    m    e         T    e    x    t
Run Code Online (Sandbox Code Playgroud)
  1. 第一行的十六进制值是该存储块的地址(64位).这就是p指向for循环开始的位置.
  2. 在第二行,您可以看到字符串中字母的十六进制值.您可以在此处查看ASCII表.字符串中的最后一个字符是t十六进制值0x74.之后你有字符串的空字符0x00.然后你会看到一些空字符,因为我在调试模式下构建并且编译器零初始化.通常你会看到垃圾(看似随机的值)
  3. 在第3行,我添加了字符串的字符以供参考

我知道你现在正处于陡峭的学习曲线,用C指针,但最终你会说"IC点"

  • 这个答案有很大的潜力,但我觉得它缺少一张图片,一个来自Ritchie的Knuth,一个来自Ritchie的引用(用斜体字表示).这肯定会得到我的赏金,因为你已经有了一顶很酷的帽子. (2认同)
  • @Ike:如果ManisNikolaidis添加带有十六进制转储的第三行会更好,以说明字符在内存中的编码方式以及如何将空字符拼写为''\ 0'并编码为'00`来终止字符串. (2认同)

小智 19

这可以像这样重写

for (p = str; *p != '\0'; p++)
{
    // Code
}
Run Code Online (Sandbox Code Playgroud)

在C中,字符串必须始终以空字符结尾,该字符与'\ 0'或0.相同.


hac*_*cks 14

在深入研究之前,我想在C中陈述关于表达的简单规则

当C需要的表达的布尔值,一个false值是推断当表达式比较等于,和一true值,否则.也就是说,无论什么时候写

if(expr)
Run Code Online (Sandbox Code Playgroud)

expr任何表达式都在哪里,编译器本质上就像它被写成一样

if((expr) != 0)  
Run Code Online (Sandbox Code Playgroud)

现在回答你的问题:

什么是*p在下面的循环呢?

在C中,字符串由空字符终止'\0'.

在此输入图像描述

每个字符都有一个十进制等值.这'\0'是一个ASCII转义字符.十进制当量'\0'0.

因此,*p循环中的表达式只是检查指向的内存地址处的字符的十进制等效p值是零还是非零.当p到达字符串的末尾并找到第一个'\0'字符时,表达式*p返回1一个零值.零表示false在C中.这相当于测试*p != '\0'*p != 0如上所述.

这是它的工作原理:

在此输入图像描述


1*p然后评估的值*p是从存储器中取出.该值是表达式的值*p.

  • 我觉得我必须为这个答案再发一笔赏金——因为我认为它具有我所见过的最具教育意义的价值。 (2认同)
  • 哇!现在我觉得我必须让杰西卡说话:P (2认同)

dhe*_*ein 13

让我们分析它干燥但深入的方式!

或者正如D. Ritchie所说:让我们用汇编语言的力量和汇编语言的便利来做.


我将尝试通过参考ISO/IEC:9899(强调我的) - C99标准来解释所有必要的方面.(帖子风格的动机是唐纳德克努特的短语"科学是我们理解得足以向计算机解释的东西.艺术就是我们所做的一切.")

首先,让我们检查for-loop 究竟应该做什么!

参考ISO/IEC:9899 6.8.5"迭代语句"

语义

4迭代语句导致一个称为循环体的语句重复执行,直到控制表达式比较等于0.

到目前为止我没想到任何新东西,所以让我们开始吧:

6.8.5.3 for语句

1声明 for ( clause-1 ; expression-2 ; expression-3 ) statement

行为如下:表达式表达-2是控制表达式每次执行之前计算循环体....

因此,我们现在知道// Code,只要预先评估的值*p不为零,就会执行正文(在您的情况下).

...表达式-3 在每次执行循环体后被计算为void表达式.[...]

所以现在我们知道,(我假设挖掘p++的定义不是必要的?!)对于每次迭代p递增,所以可能会有变化*p.

以下几点没有关系,但是我正在添加它,因为这使得语义部分for完整并且很好地知道它的原因,为什么for(;;)是inf循环.

2 (---)可以省略子句-1和表达式3.省略的表达式-2由非零常量替换.

好的,这是for循环在您的情况下所做的干燥但信息丰富的部分.

现在让我们来看看指针算术:

6.5.6加法运算符

约束

2另外,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针,另一个操作数应具有整数类型.(递增相当于添加1.)

因此,在您的情况下,您将1(整数)添加到"指向对象的指针"类型.

相当于通过其指向类型的sizeof增加地址,如tomislav kostic的图片中所示:

由Tomislav kostic创作的CC BY-SA 3.0

现在让我们看看*p实际做了什么.

6.5.3.2地址和间接运营商

约束

[...]

2一元*运算符的操作数应具有指针类型.

语义

[...]

4一元*运算符表示间接.如果操作数指向函数,则结果是函数指示符; 如果它指向一个对象,则结果是指定该对象的左值.如果操作数的类型为''指向类型'',则结果的类型为''type''.如果为指针分配了无效值,则unary*运算符的行为未定义.

这又有点干了1但为了更好地理解,这可以通过以下方式进行逆向工程:

6.5.2.1数组下标

[...]

语义

2后缀表达式后跟方括号[]中的表达式是数组对象元素的下标名称.下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同.

那么*((p)+(0))是什么(因为p+0是相同的p......很明显)等于p[0],没有别的任务作为评价p的对象.

而且因为我们知道,expression-2如果正在评估0,for循环正在中断迭代,我们可以说它是相同的p[0] != 0.

现在是最后一步

让我们看看C-Coder的朋友; JSSCA......不,等等......我们的朋友被叫了...... ASCII现在澄清了,我们可以弄清楚0它代表什么.

它是NULL标记,在C中指定字符串的结尾.


如此确凿:

所有,这样做是:

迭代该循环的主体for,直到p实际指向地址,其中对象评估为"字符串结束"-token.

要么:

让我们p通过字符串直到到达结尾.


现在只是为了引用自己; 你永远不应该忘记的事情:(
强调我的......)

变量通过声明(类型说明符)声明,该声明位于标识符之前,该标识符命名可以计算其值的左值对象

它既不多也不少!


1 那就是我所承诺的!;)

  • ...需要简单、常规的答案,即使是一个基本问题也可以解决许多人不知道的复杂主题和领域,如果我们想像您一样在技术上深入了解它们,或者像其他人那样有趣——有时,至少对于某些学生来说,有趣是帮助他们学习科目而不感到无聊的先决条件。学习方式多种多样——从视觉到数学,再到技术性和文字性——很多人都从不同的学习方式中受益。我希望这些答案的多样性能够向未来的读者展示这一点。 (2认同)

ter*_*ill 9

*p Haiku

诗意地说,我试图在循环中代表*p的挣扎:

勇敢的C*p(程序员)

在whilederness循环

NUL将阻止他们

这是一首ha句诗,它由三行组成,第一行和最后一行有5个音节,中间行有7个.另一个例子是@Samidamaru(海库诗人,见下面的评论):第一个p等于str,然后p递增,直到*p为NUL.


一点点流行

在此输入图像描述

代码大使小时,杰西卡阿尔芭


*p在循环中做了什么?

按照Jessica(引用D. Knuth(1))的想象,我们将尝试在for循环中看到*p的含义:

for (p = str; *p; p++)
Run Code Online (Sandbox Code Playgroud)

为了达到这个目标,我们首先检查一元运算符 "*" 如何在C中工作:"一元运算符*是间接或引用运算符; 当应用于指针时,它访问指针指向的对象."(B. Kernighan和D. Ritchie(2))

所以*p只是p指向的值:

在此输入图像描述

1.1仔细研究for循环

for循环由三个指令组成:

  1. p = str
  2. *p
  3. 的p ++

在1.我们将指向数组str的指针分配给p.在C中,以下分配具有相同的效果:

p = &str[0];
p = str; 
Run Code Online (Sandbox Code Playgroud)

"根据定义,数组类型的变量或表达式的值是数组元素零的地址"(K&R(2)).此外,我们有"在评估a [i]时,C立即将其转换为*(a + i).....因此&a [i]a + i是相同的"(K&R(2)).如果我们把i = 0,我们获得上述分配.

我们现在可以说,在for循环的开头,p指向str的第一个元素.

1.2问题的核心

让我们转到第2点,这是你问题的核心.循环的第二个表达式控制退出条件:评估指令"*p",如果为false则循环退出.这意味着"*p"等同于"*p!= 0"或用词:当p指向的值为零时,退出.

现在,为了理解*p为零时我们记得数组str已经初始化如下:

char str[128] = "Some Text";
Run Code Online (Sandbox Code Playgroud)

和:"所有字符串常量都包含一个空终止字符(\ 0)作为它们的最后一个字符"(gnu-manual).所以实际存储在内存中的字符串最后有一个\ 0:"Some Text\0".

在第三条指令p ++中,指针p前进到str数组的下一个元素,因此,在第9次迭代时*p变为0(或者\ 0,NULL,NUL,请参阅@Joe的答案)和循环退出.

1.3相信

一张图片胜过千言万语,这里是循环的图形表示:

在此输入图像描述

1.4另一个例子:在不同的例子中使用*p的相同用法

在下面的代码片段中, *p以相同的方式使用,但在while循环中使用:

#include <stdio.h>
int main() {
    char str[128] = "We all scream for ice cream!";
    char *p = str;
    // here we see again the loop exit condition *p == '\0'
    while(*p) {
        printf("%c", *p);
        p++;
    }
    printf("\n");
}
Run Code Online (Sandbox Code Playgroud)

愿原力与你同在!


参考

(1)卷.I,基本算法,第1.1节(1968)

(2)C编程语言第94-99页

  • @Ike&terence hill,如果你想使用它,我会接受我的paypal帐户的参考和每周付款:MasterHaikuPoet@paypal.com (3认同)
  • 天哪,这是一个很棒的答案,它实际上有一个*嵌入*杰西卡阿尔芭的图片!好样的!我不知道现在该做什么 - 你和Zaibis之间正在进行一场激烈的竞争!我觉得你的答案更适合问题的初学者级别,但Zaibus具有那种无可置疑的技术精确性.无论如何,无论发生什么事,你们两个都是我眼中的赢家! (2认同)

Joe*_*Joe 5

它利用了这样一个事实:字符串的终止符(最终由 for 循环找到)将是 ASCII NUL,它是零,它也恰好计算为false,从而终止 for 循环。

值得注意的是 0、false、NULL 和 ASCII NUL 之间的区别和相似之处。看这个问题:NULL、'\0' 和 0 之间有什么区别

  • 在布尔上下文中使用“*p”并不是糟糕的代码,它是所有 C 程序员都熟悉的标准习惯用法。 (3认同)