用C中的指针循环

我不明白指针在for循环中的作用。 *p在后面的循环中做了什么?

 char str[128] = "Some Text"; char *p; for (p = str; *p /*what does this mean?*/; p++) { // Code } 

我明白了其余的,但为什么不是像p > 3或类似的东西?
为什么独自一人?
为什么这样写呢?

在诸如for循环的条件的布尔上下文中,C中的每个expression式都计算为true(非零)或false(零)。

你想要for循环终止,当它到达string的末尾。

在C中,每个string都以字符'\0'结尾,实际上是0 。 因此,当for循环到达string的末尾时, *p计算为'\0' ,即0 ,计算结果为false,终止for循环。

for循环将终止,如果两者之间有任何的谎言; 在声明中是零(false)。 *p解引用p并返回charp指向。 根据丹尼斯·里奇 ( Dennis Ritchie)的说法, “C将string视为通常由标记终止的字符数组” 。 该标记是(ASCII)值为零的空字符。 所以,这个for循环:

 for (p = str; *p; p++) 

相当于这些

 for (p = str; *p != '\0'; p++) for (p = str; *p != 0; p++) for (p = str; p[0] != '\0'; p++) 

空终止字符的另一个名字是哨兵或根据唐纳德Knuth “虚拟价值” (艺术计算机编程,第1卷)。 以下是每个字符的string,索引(从开始的偏移量)和每个索引处的值的图表:

在这里输入图像说明

为了完整性,在这里的注释请求之后是debugging器在str占用的内存块中看到的内容:

 0x00007fffffffe6a0: 0x53 0x6f 0x6d 0x65 0x20 0x54 0x65 0x78 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00 S ome T ext 
  1. 第一行的hex值是该内存块的地址(64位)。 这就是p指向for循环开始的地方。
  2. 在第二行,您可以看到string中的字母的hex值。 你可以在这里看到一个ASCII表格。 string中的最后一个字符是hex值为0x74 t 。 之后,你有string的空字符0x00 。 然后你会看到更多的空字符,因为我内置了debugging模式,并且编译器是零初始化的。 通常你会看到垃圾(看似随机的值)
  3. 在第三行,我添加了string的字符作为参考

我知道你现在用C语言中的指针学习曲线,但最终你可以说“IC的重点”

这可以像这样重写

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

在C中,一个string必须总是以空字符结尾,这与'\ 0'或0

让我们分析一下干燥而深入的方法吧!

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


我将尝试通过引用ISO / IEC:9899(强调我的) – C99标准来解释所有必要的方面。 (Donald Knuth的话是“科学就是我们所理解的,足以向计算机解释,艺术就是我们所做的一切” )。

首先,让我们来检查一下, for -loop应该做什么!

参照ISO / IEC:9899 6.8.5“迭代报表”

语义

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

到目前为止,我猜想没有新的东西,所以让我们来看看:

6.8.5.3 for语句

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

performance如下:expression式expression式-2是 在每次执行循环体之前计算 的控制expression式 。 …

所以我们现在知道,只要你的*p的事先计算的值不为零,那么body(在你的情况下// Code )就会被执行。

…expression式-3在循环体的每次执行之后 被评估为无效expression式。

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

以下几点是不相关的,但是我把它加了,因为这使得完整的语义部分以及知道它的原因,为什么for(;;)是一个inf循环。

2 (—)子句-1和expression式-3都可以省略。 省略的expression式-2被非零常量替代。

好的,这是for循环在你的情况下干的,但是信息丰富的部分。

现在让我们回到指针算术:

6.5.6添加操作符

约束

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

所以在你的情况下,你正在加1(整数)到“指向对象”types。

什么等于增加地址的大小指向types,如图所示tomislav kostic :

CC BY-SA 3.0通过tomislav kostic

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

6.5.3.2地址和间接运算符

约束

[…]

2一元运算符的操作数应该有指针types。

语义

[…]

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

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

6.5.2.1数组下标

[…]

语义

2后缀expression式后面跟着方括号[]中的expression式是数组对象元素的下标。 下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同

所以*((p)+(0))是什么(因为p+0p …相同)等于p[0] ,所以除了评估p的对象外别无他物。

由于我们知道,for循环的expression-2是在中断迭代,如果它正在计算0 ,我们可以说它是相同的p[0] != 0

现在是最后一步

让我们看看C-Coder的朋友; JSSCA …不,等等…我们的朋友被称为… ASCII现在,正如澄清,我们可以找出0代表什么。

它是C中的NULL标记,用于指定string的结尾。


所以决定性的:

所有,这是做的是:

迭代这个for -loop的主体,直到p实际上指向地址,在那里对象的计算结果是“string结尾”–token。

要么:

p穿过string直到到达结尾。


现在只是引用我的自我; 你永远不应该忘记的东西:
(强调我的…..)

一个variables是通过一个声明types说明符 )来声明的,该声明符指定了一个可以评估为其值的左值对象的标识符

它不多也不less!


1 就是我所承诺的! ;)

在深入之前,我想就C expression一个简单的规则

当C需要expression式布尔值时 ,当expression式比较等于零时推断为false值,否则为true值。 也就是说,每当写一个

 if(expr) 

其中expr是任何expression式,编译器本质上就像它被写成一样

 if((expr) != 0) 

现在回到你的问题:

*p在后面的循环中做了什么?

在C中,string以空字符'\0'结尾。

在这里输入图像说明

每个字符都有一个十进制等值。 这个'\0'是一个ASCII转义字符 。 '\0'的十进制等值为0

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

这是如何工作的:

在这里输入图像说明


1 *p求值时, *p的值从内存中取出。 这个值是expression式*p的值。

* P Haiku

从诗意上说,我试图在循环中performance* p的挣扎:

勇敢的C * p(rogrammers)

在和蔼的循环中

NUL将阻止他们

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


一点点stream行

在这里输入图像说明

小时代码大使 Jessica Alba


* p在循环中做了什么?

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

 for (p = str; *p; p++) 

为了实现这个目标,我们首先研究一元运算符 “*”是如何在C中工作的:“一元运算符*是间接运算符或参数运算符; 当应用于指针时,它访问指针指向的对象。“(B. Kernighan和D. Ritchie(2))

所以* p只是p指向的值

在这里输入图像说明

1.1仔细看看for循环

for循环由三条指令组成:

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

在1.我们将指针分配给数组strp 。 在C中,以下作业具有相同的效果:

 p = &str[0]; p = str; 

“根据定义,数组的typesvariables或expression式的值是数组元素0的地址”(K&R(2))。 此外,我们有“在评估一个[我] ,C转换为*(A + I)立即。 …。 它遵循&a [i]a + i是相同的“(K&R(2))。 如果我们把i = 0 ,我们得到上面的分配。

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

1.2问题的核心

让我们来谈谈你的问题的核心。 循环的第二个expression式控制退出条件:评估指令“* p”,如果为false,则退出循环。 这意味着“* p”等价于“* p!= 0”或者用语言表示: 当p指向的值为零时,退出

现在,要知道* p是零的时候,我们记得数组str已经被初始化如下:

 char str[128] = "Some Text"; 

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

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

1.3看到相信

一张图片胜过千言万语,下面是循环的graphics表示:

在这里输入图像说明

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"); } 

愿原力与你同在!


参考

(1)Vol。 I,基础algorithm,1.1节(1968年)

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

很久以前,在一个距离遥远的PDP上,资源稀缺,名字变短: i为索引, p为指针,请早日绝地的程序员。

隐式testing告诉了条件空间的真相。 一个*就是他们所有的types,相信p并把它推到string的末尾。

到目前为止,他们使用for(e = s;*e;e++)最熟悉和优雅的循环来蔑视C ++帝国及其群集的ctors,dtors和vile迭代器。 对模板的裸位和字节,exception和模糊的types,只有勇敢者还敢为C打,而且void *

它利用了string的终结符(最终由for循环find)将是一个ASCII NUL ,它是一个零,这也正好是false ,因此终止了for循环。

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

我试图满足不同时候提到的赏金者的欲望。 为了简单起见,我把答案限制在三行三行,因为(正如贝尔曼在他的三则规则中所说的) “我三次告诉你是真的” (这个答案的主题)。

技术

当expression式*p值为0并且这个评估是在循环的每次迭代之前执行的时候, for循环的真值就会终止它,注意在C 0是假的,其他的都是真的 – 在其他世界里这是一个非常广泛的定义!

指针variablesp被初始化一次,以p = str指向数组的开始,并且p在每次迭代结束时递增,所以*p在每次迭代中访问数组的连续元素。

当由*p读取的数组元素是0'\0'终止符来表示C“string”的结尾时,expression式*p将因此计算为0 (假),但是在str初始化,因为它是由编译器自动提供的。

抒情

expression真相

青年不理解

阅读Ritchie和Knuth

怪诞的

杰西卡·阿尔芭(Jessica Alba)是一个非常有知识的好女演员,他从观察计算机技术的发展中吸取了一些真理,正如这些引言所揭示的那样:

“每五年,我觉得我是一个完全不同的人。”

“这关乎你的产品以及它的performance,要么是有效的,要么是没有。”

ha句:

 WHY for (p=str; *p; p++) IS for (p=str; p[0] != 0; p++) THINK for (i=0; str[i]; ++i) 

EDITED

这是一些额外的细节:

“ha句”的第二行代码等同于第一行。 原帖在代码注释中问“这是什么意思”。 第二行通过等价性来表明答案。 * p表示p [0]for循环中的第二个子句关心p [0]是否等于零。

“ha句”的第三行代码是可以在概念上使用的一行代码:你可以将原始行的操作看作很像第三行的行为​​。

str中的字符串

从图中可以看到, for循环以*p开始,其中p指向str 。 此时*pS

当连续循环时,最后到达str[9] ,其中'\0'表示NULL

此时,条件语句for (p = str; *p; p++)等于NULL所以代码将从for循环中断开。

这是循环的条件部分。
如果没有满足这个条件,那么循环不会被执行。
*p解引用指针p并返回stringstr指向的字符。
C风格的stringstr由值\0结尾。
循环遍历每个字符(使用p )直到条件不满足。

在C中, 0\0值就像false的含义,即不符合条件。
任何其他的价值就像是true的意思,即满足条件。

简而言之, p遍历str每个字符,一旦遇到string终止字符\0就停止。

为什么不使用p而不是*p
因为p是一个指针并且包含一个地址。 有时很难甚至不可能只使用地址算术。 这不是很好的做法,使代码难以阅读。
*p是解引用的指针并包含 p指向的值。 在这种情况下,使用p指向的值很容易,因为您知道string被\0终止。 作为条件( if ,等等) *p等于*p != '\0'

首先,你需要掌握一个指针的概念,就像名字所指的那样。 指针包含variables的地址。

  int var=0; int *p; int p=&var; 

在这个代码中, p是一个指针和printf("%d",p); 打印variablesvarprintf("%d",*p); 打印本例中为0的variablesvar值。

其次,你必须理解数组是如何工作的。数组是一种数据结构,可以存储相同types的固定大小的SEQUENTIAL元素集合。

  int array[3]={9,8,7}; printf("%d",array[0]); //prints what is on 1st position,9 printf("%d",array[1]); //prints what is on 2nd position,8 printf("%d",array[2]); //prints what is on 3rd position,7 

operator []对于数组只是用户友好的工作。 最后三行代码可以用下面几行代替(而且它们也是一样的):

  printf("%d",*(array+0)); //prints what is on 1st position,9 printf("%d",*(array+1)); //prints what is on 2nd position,8 printf("%d",*(array+2)); //prints what is on 3rd position,7 

array是指向array第一个元素的指针(包含数组中的第一个元素的地址),所以取消引用它,我们得到第一个元素的值,例如*array 。 我们知道数组是seqential ,这意味着array+1指向array+1第二个元素,所以取消引用它可以得到第二个元素的值,例如*(array+1)等等。 数组的内存段

同样也适用于string,因为它们是字符数组,除了string在string结尾处具有“\ 0”(空字符)。

  char str[128] = "Some Text"; char *p; for (p = str; *p; p++) { printf("%c",*p); } 

这个程序打印stringstr

p = str //将stringstr的第一个字符的地址赋值给p ,我们不会丢失string中第一个字符的轨迹,所以我们使用p not str来迭代

*p //这个expression式意味着*p!=0所以这是真的,直到到达string的末尾,记住ascii中的'0'具有整数值48

p++ //在块的结尾添加+1到p来获取下一个字符的地址

可以这样解释:

 for( initialization ; Conditional Expression ; expression3) { Code here will execute while 2nd Expression(Conditional Expression) is true true means non-zero value '\0' is equivelant to 0,so when *p equal '\0' : loop will terminate }