用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并返回char
, p
指向。 根据丹尼斯·里奇 ( 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
- 第一行的hex值是该内存块的地址(64位)。 这就是
p
指向for循环开始的地方。 - 在第二行,您可以看到string中的字母的hex值。 你可以在这里看到一个ASCII表格。 string中的最后一个字符是hex值为
0x74
t
。 之后,你有string的空字符0x00
。 然后你会看到更多的空字符,因为我内置了debugging模式,并且编译器是零初始化的。 通常你会看到垃圾(看似随机的值) - 在第三行,我添加了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 :
现在让我们看看*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+0
与p
…相同)等于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循环由三条指令组成:
- p = str
- * p
- 的p ++
在1.我们将指针分配给数组str到p 。 在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句”的第三行代码是可以在概念上使用的一行代码:你可以将原始行的操作看作很像第三行的行为。
从图中可以看到, for
循环以*p
开始,其中p
指向str
。 此时*p
有S
当连续循环时,最后到达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);
打印variablesvar
和printf("%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 }