“while(* s ++ = * t ++)”如何复制一个string?
我的问题是,这个代码是做什么的(来自http://www.joelonsoftware.com/articles/CollegeAdvice.html ):
while (*s++ = *t++);
该网站说,上面的代码复制一个string,但我不明白为什么…
它与指针有关吗?
这相当于:
while (*t) { *s = *t; s++; t++; } *s = *t;
当t
指向的字符是'\0'
,while循环将终止。 在此之前,它会将指向的字符复制到指向的字符,然后递增s
和t
指向数组中的下一个字符。
这在封面上有这么多:
while (*s++ = *t++);
s
和t
variables是指针(几乎可以肯定是字符), s
是目的地。 以下步骤说明了正在发生的事情:
- t(
*t
)的内容被复制到s(*s
),一个字符。 -
s
和t
都递增(++
)。 - 赋值(复制)返回被复制的字符(到
while
)。 - 一直持续到该字符为零(
C
中string结尾)。
实际上,它是:
while (*t != 0) { *s = *t; s++; t++; } *s = *t; s++; t++;
但写得更加紧凑。
假设s
和t
是指向string的char *
s(并且假定s
至less和t
一样大)。 在C中,string全部以0
结尾(ASCII“NUL”),对吗? 那么这是做什么的:
*s++ = *t++;
首先, *s = *t
,将*s = *t
的值复制到*s
。 然后,它确实s++
,所以现在指向下一个字符。 然后它t++
,所以指向下一个字符。 这与运算符优先级和前缀与后缀增量/减量有关 。
运算符优先级是运算符parsing的顺序。 举个简单的例子,看看:
4 + 2 * 3
这是4 + (2 * 3)
还是(4 + 2) * 3
? 那么,我们知道它是第一个因为优先 – 二进制*
(乘法运算符)比二进制+
(加法运算符)具有更高的优先级,并且首先被parsing。
在*s++
,我们有一元*
(指针解引用运算符)和一元++
(后缀增量运算符)。 在这种情况下, ++
具有比*
更高的优先级(也被称为“绑定更紧密”)。 如果我们说++*s
,我们将增加*s
而不是*s
指向的地址,因为前缀增量具有较低的优先级*作为解引用,但是我们使用后缀增量,优先级较高。 如果我们想要使用前缀增量,我们可以完成*(++s)
,因为括号会覆盖所有较低优先级,强制++s
先到达,但这会产生不希望的副作用,即留空string开头的字符。
请注意,仅仅因为它具有更高的优先级并不意味着它首先发生。 后缀增量具体发生在使用该值后 ,为什么*s = *t
发生在s++
之前。
所以现在你明白了*s++ = *t++
。 但他们把它放在一个循环中:
while(*s++ = *t++);
这个循环什么都不做 – 动作全部在这个状态。 但是检查一下这个条件 – 如果*s
是0,它返回“false”,这意味着*t
是0,这意味着它们在string的末尾(对于ASCII“NUL”是yay)。 所以这个循环只要有t
字符就循环,并把它们尽职地复制成s
,一路递增。 当这个循环退出时, s
被NUL终止,并且是一个正确的string。 唯一的问题是, s
指向最后。 保持另一个指针,指向s
的开头(即while()
循环之前的s
) – 这将是您复制的string:
char *s, *string = s; while(*s++ = *t++); printf("%s", string); // prints the string that was in *t
或者,检查一下:
size_t i = strlen(t); while(*s++ = *t++); s -= i + 1; printf("%s\n", s); // prints the string that was in *t
我们从长度开始,所以当我们结束的时候,我们做了更多的指针algorithm,把s
放回到开始位置。
当然,这个代码片段(以及我所有的代码片段)为了简单起见,忽略了缓冲区问题。 更好的版本是这样的:
size_t i = strlen(t); char *c = malloc(i + 1); while(*s++ = *t++); s -= i + 1; printf("%s\n", s); // prints the string that was in *t free(c);
但是你已经知道了,否则你很快就会在每个人喜欢的网站上提出一个问题。 ;)
*实际上,它们具有相同的优先顺序,但这是通过不同的规则来解决的。 在这种情况下,它们的优先级有效。
while(*s++ = *t++);
为什么人们认为这相当于:
while (*t) { *s = *t; s++; t++; } *s = *t; /* if *t was 0 at the beginning s and t are not incremented */
当它显然不是。
char tmp = 0; do { tmp = *t; *s = tmp; s++; t++; } while(tmp);
更喜欢它
编辑:更正了编译错误。 tmp
variables必须在循环之外声明。
这个神秘的方面是操作的顺序。 如果您查看C语言规范,则说明在此情况下,操作顺序如下所示:
1. * operator 2. = (assignment) operator 3. ++ operator
所以while循环变成了英文:
同时(一些条件): 把地址“t”的地址复制到地址“s”的地址。 通过一个地址位置增加“s”。 一个地址位置增加“t”。
现在,什么是“一些条件”? C lang规范还指出赋值expression式的值是赋值本身,在这种情况下是*t
。
因此,“某些条件”是“ t
指向非零的东西”,或者以一种更简单的方式,“而位置t
处的数据不是NULL
”。
它通过将由' t
'指向的string中的字符复制到' s
'指向的string中来工作。 对于每个字符副本,两个指针都是递增的。 当循环find一个NUL
字符(等于零,因此退出)时终止。
提示:
- 运算符'='是做什么的?
- expression式“a = b”的价值是什么? 例如:如果你做“c = a = b”c得到了什么值?
- 什么终止一个Cstring? 它评估真假吗?
- 在“* s ++”中,哪个运算符具有更高的优先级?
忠告:
- 使用strncpy()来代替。
它复制一个string,因为数组总是通过引用传递的,string只是一个char数组。 基本上发生的是(如果我正确地记住这个词)指针算术。 这里有更多来自维基百科的c数组信息 。
您将存储从s中取消引用的值,然后通过++移动到下一个索引。
假设你有这样的事情:
char *someString = "Hello, World!";
someString指向string中的第一个字符 – 在本例中为“H”。
现在,如果你增加一个指针:
someString++
someString现在将指向“e”。
while ( *someString++ );
将循环,直到someString指向的任何东西变为NULL,这是什么信号的string的末尾(“NULL Terminated”)。
和代码:
while (*s++ = *t++);
等于:
while ( *t != NULL ) { // While whatever t points to isn't NULL *s = *t; // copy whatever t points to into s s++; t++; }
Brian W. Kernighan和Dennis M. Ritchie 的C编程语言 (K&R)对此进行了详细的解释。
第二版,页面104:
5.5字符指针和function
一个string常量 ,写成
"I am a string"
是一个字符数组。 在内部表示中,数组以空字符
'\0'
结尾,以便程序能够find结尾。 因此存储的长度比双引号之间的字符数多一个。也许最常见的string常量是作为函数的参数,如
printf("hello, world\n");
在程序中出现这样的string时,通过字符指针访问;
printf
接收一个指向字符数组开头的指针。 也就是说,一个string常量通过指向其第一个元素的指针来访问。string常量不需要是函数参数。 如果
pmessage
被声明为char *pmessage;
那么声明
pmessage = "now is the time";
分配给
pmessage
一个指向字符数组的指针。 这不是一个string副本; 只涉及指针。 C不提供任何操作符来处理整个string作为一个单位。这些定义有一个重要的不同:
char amessage[] = "now is the time"; /* an array */ char *pmessage = "now is the time"; /* a pointer */
amessage
是一个数组,只是足够大以容纳字符序列和'\0'
来初始化它。 数组中的单个字符可能会通过amessage
更改而始终引用相同的存储空间。 另一方面,pmessage
是一个指针,初始化为指向一个string常量; 指针可能随后被修改为指向其他地方,但是如果尝试修改string内容,结果是不确定的。+---+ +--------------------+ pmessage: | o-------->| now is the time \0 | +---+ +--------------------+ +--------------------+ amessage: | now is the time \0 | +--------------------+
我们将通过研究从标准库改编的两个有用函数的版本来阐述指针和数组的更多方面。 第一个函数是
strcpy(s,t)
,它将stringt
复制到strings
。 只是说s = t
会很好,但是这个复制了指针,而不是字符。要复制这些字符,我们需要一个循环。 arrays版本是第一个:/* strcpy: copy t to s; array subscript version */ void strcpy(char *s, char *t) { int i; i = 0; while((s[i] = t[i]) != '\0') i ++; }
相比之下,下面是带指针的
strcpy
版本:/* strcpy: copy t to s; pointer version 1 */ void strcpy(char *s, char *t) { while((*s = *t) != '\0') { s ++; t ++; } }
由于参数是按值传递的,
strcpy
可以以任何方式使用参数s
和t
。 在这里,他们很方便地初始化指针,这些指针每次沿着数组一个字符前进,直到终止t
的'\0'
被复制到s
。在实践中,
strcpy
不会像上面显示的那样写入。 有经验的C程序员会更喜欢/* strcpy: copy t to s; pointer version 2 */ void strcpy(char *s, char *t) { while((*s++ = *t++) != '\0') ; }
这将
s
和t
的增量移动到循环的testing部分。*t++
的值是在t
增加之前指向的字符。 后缀++
不会改变t
直到取得这个字符。 以同样的方式,字符在s
递增之前被存储在旧s
位置。 这个字符也是与'\0'
进行比较来控制循环的值。 最终结果是字符从t
复制到s
,直到并包括终止'\0'
。作为最后的缩写,注意与
'\0'
的比较是多余的,因为问题仅仅是expression式是否为零。 所以这个函数可能写成/* strcpy: cope t to s; pointer version 3 */ void strcpy(char *s, char *t) { while(*s++ = *t++); }
虽然这看起来似乎很神秘,但是符号的方便程度是相当可观的,而且应该掌握这个习语,因为在C程序中你会看到频繁的。
标准库(
<string.h>
)中的strcpy
返回目标string作为其函数值。
这是本节相关部分的结尾。
PS:如果你喜欢读这本书,可以考虑购买K&R的副本 – 这并不昂贵。
开始一段时间循环….
* s = * t先走,这指的是什么指向什么点。 即它将tstring中的一个字符复制到sstring中。
什么是分配传递给while条件…任何非零是“真”,所以它会继续,0是假的,它将停止….只是发生在一个string的结尾也是零。
s ++和t ++它们增加指针
这一切都开始了
所以它一直在分配循环,移动指针,直到达到string结束的0
是的,它使用指针,并且在评估while条件的同时做所有的工作。 C允许条件expression式有副作用。
“*”运算符取消指针s和t。
递增运算符(“++”)在赋值之后递增指针s和t。
循环终止在一个空字符的条件,在C中评估为假。
一个额外的评论….这不是安全的代码,因为它没有做任何事情,以确保s有足够的内存分配。
我提供以下答案的问题已被封闭,作为这个问题的一个重复,所以我在这里复制了相关部分的答案。
while循环的实际语义解释是这样的:
for (;;) { char *olds = s; // original s in olds char *oldt = t; // original t in oldt char c = *oldt; // original *t in c s += 1; // complete post increment of s t += 1; // complete post increment of t *olds = c; // copy character c into *olds if (c) continue; // continue if c is not 0 break; // otherwise loop ends }
s
和t
保存的顺序, s
和t
递增的顺序可以互换。 *oldt
到c
的保存可以在保存之后和使用c
之前的任何时候发生。 c
和olds
的分配可以在c
和olds
保存后随时发生。 在我的信封背面,至less有40种不同的解释。
是的,这确实与指针有关。
读取代码的方法是这样的:“指针”s指向的值(在该操作之后得到递增)获得由指针“t”指向的值(在该操作之后递增;该操作的整个值计算为复制字符的值;遍历此操作,直到该值为零“由于stringnull终结符的值是零('/ 0')的字符值,因此循环将迭代,直到string从t指向的位置复制到s指向的位置。
那么这是真正的只是在字符的情况下,如果没有\ 0和它是一个整数数组的程序将崩溃,因为会有一个地址的元素不是数组或指针的一部分,如果系统有使用malloc分配的内存然后系统将继续给内存