问题描述
在这种情况下,while
循环是否像 for
循环一样工作?另外,str1-str2
字符串减法的结果是什么?
#include <stdio.h>
int fun(char *str1) {
char *str2 = str1;
while (*++str1);
return (str1 - str2);
}
int main() {
char *str = "GeeksQuiz";
printf("%d",fun(str));
return 0;
}
解决方法
请注意,您在这里使用的是指针而不是字符串,因此从末尾开始,str1-str2
是指针算术。
如您所知,字符串应以空值结尾,因此在内存中“GeeksQuiz”实际上是一个字符数组,其具有下一个值:GeeksQuiz\0。这样,while(*++str1);
将遍历此数组的值,直到到达 \0。
总而言之,此函数将返回字符串中的字符数。
该函数的目的是计算字符串的长度。就是这个while循环
while(*++str1);
迭代直到遇到终止零字符 '\0'
。假设在 while 循环之后,指针 str1
将指向终止零字符 '\0'
而指针 str2
由于初始赋值将指向字符串的开头>
char *str2 = str1;
所以差值 str1-str2
将产生字符串的长度。字符串的长度确定为字符串中终止零字符 '\0'
之前的字符数。
但是该功能有一个错误。如果用户将传递一个空字符串 ""
,该字符串在内部表示为一个字符数组,其中一个元素等于终止零字符 { '\0' }
,则该函数调用未定义的行为。因此,空字符串的第一个字符包含终止零字符 '\0'
。然而,在 while 循环中,指针 str1
首先递增,然后已经检查下一个字符是否是终止零字符 '\0'
。
这就是while循环
while(*++str1);
可以改写成以下方式
while ( ( ++str1,*str1 != '\0' ) );
正如最初看到的,指针 str1 递增。
除了这个缺陷之外,函数参数应该有限定符const
,因为在函数内传递的字符串没有被改变。此外,函数的返回类型应该是无符号整数类型,例如 size_t(它是执行相同任务的标准 C 字符串函数 strlen
的返回类型。)
可以通过以下方式声明和定义函数
size_t fun( const char *s )
{
const char *t = s;
while( *t ) ++t;
return t - s;
}
这是一个演示程序。
#include <stdio.h>
size_t fun( const char *s )
{
const char *t = s;
while( *t ) ++t;
return t - s;
}
int main(void)
{
const char *s = "";
printf( "The length of the string \"%s\" is equal to %zu\n",s,fun( s ) );
s = "1";
printf( "The length of the string \"%s\" is equal to %zu\n",fun( s ) );
s = "12";
printf( "The length of the string \"%s\" is equal to %zu\n",fun( s ) );
s = "123";
printf( "The length of the string \"%s\" is equal to %zu\n",fun( s ) );
return 0;
}
程序输出为
The length of the string "" is equal to 0
The length of the string "1" is equal to 1
The length of the string "12" is equal to 2
The length of the string "123" is equal to 3
,
我通过 clang-format
[见注 1] 运行您的代码以确认我对您在那里进行的奇怪的 while
循环的怀疑,并且我想出了它作为正确格式您的代码:
#include <stdio.h>
int fun(char *str1)
{
char *str2 = str1;
while (*++str1)
;
return (str1 - str2);
}
int main()
{
char *str = "GeeksQuiz";
printf("%d",fun(str));
return 0;
}
就我个人而言,我会这样写,以使 while
循环非常明显。您可以在此处运行此代码:https://onlinegdb.com/BkxlKb75GO。
#include <stdio.h>
int fun(char *str1)
{
char *str2 = str1;
while (*++str1)
{
// do nothing
}
return (str1 - str2);
}
int main()
{
char *str = "GeeksQuiz";
printf("%d",fun(str));
return 0;
}
然而,这对于 fun()
函数来说更易读,我也将其重命名为 count_num_chars_in_str()
:
int count_num_chars_in_str(char *str1)
{
char *str2 = str1;
while (*str1 != '\0')
{
str1++;
}
return str1 - str2;
}
较短的名称可能是 num_chars_in_str()
、str_length()
或 strlen()
。 strlen()
已经存在(see here 和 here),这正是它所做的。它可以包含在标头 string.h
中,并且是 C 和 C++ 标准的一部分。这是它的描述on cplusplus.com:
size_t strlen ( const char * str );
获取字符串长度
返回 C 字符串 str 的长度。
C 字符串的长度由终止空字符决定:AC 字符串等于字符串开头和终止空字符之间的字符数(不包括终止空字符本身)。
这不应与保存字符串的数组的大小混淆。
因此,运行上述任何程序,输出为 9
。程序所做的就是计算传入的字符串中非空字符(其中空字符是 0
或 '\0'
--相同的东西)的数量,即 GeeksQuiz
这个案例。 GeeksQuiz
包含 9 个非空字符。
顺便说一下,你从哪里得到你的代码?请发布链接和参考资料。您应该始终参考您的来源。
while (*++str)
只是将 str
指针一次一个字符地递增,直到找到空终止符 (0
),它出现在字符串的末尾,在最后一个字符之后字符在其中。一旦发生这种情况,就会取两个字符指针之间的差异,从而导致 z
之后的空终止符的地址位置与第一个字符的地址位置之间的差异。在字符串中,即 G
。这2个字符的内存地址相差9个字符。
原始版本不仅可读性差,而且还存在错误。对于这个测试用例,它应该打印 0
,但它打印的是 1
:
char *str = "\0";
printf("%d",fun(str));
我在 count_num_chars_in_str()
中更易读的版本也修正了这个错误。
教训:不要编写不可读或混淆的代码。
[注 1] 我通过 clang-format
运行它的方式是我只是将您的原始代码复制粘贴到 main.c
文件中,然后将其复制到我的 eRCaGuy_CodeFormatter 存储库中,然后跑了./run_clang-format.sh
。
循环 while (*++str1);
递增指针,读取更新后的 str1
指向的字节,并测试该字节是否为空,如果不是则停止,否则什么也不做并重复。使用显式语句而不是空语句 ;
:
while (*++str1 != '\0')
continue;
return (str1 - str2);
计算指针 str1
和 str2
的差值,并将该值作为 int
返回。如果两个指针指向同一个数组并计算它们之间的元素数,则定义它们的差异。
该函数尝试计算字符串参数的长度,但对于空字符串会失败,因为 str1
总是在测试前递增,因此对于空字符串会跳过偏移量 0
处的空终止符细绳。该行为是未定义的,因为代码然后读取超出字符串的末尾。对于非空字符串,它打印非空字符的数量,也就是字符串的长度:fun("GeeksQuiz")
返回 9。
这是修改后的版本:
#include <stdio.h>
int fun(const char *str) {
const char *start = str;
while (*str != '\0')
str++;
return str - start;
}
int main() {
const char *str = "GeeksQuiz";
printf("length of \"%s\" is %d\n",str,fun(str));
return 0;
}
,
在这种情况下,while
循环是否像 for
循环一样工作?
我会说所有 while
循环就像 for
循环,反之亦然。
任何时候都有循环
while(condition)
{ /* do something */; }
您可以将其替换为等效的 for
循环:
for(; condition; )
{ /* do something */; }
反之,只要有 for
循环
for(initial_expression; test_expression; increment_expression)
{ /* do something */; }
您可以(几乎)用等效的 while
循环替换它:
initial_expression;
while(test_expression) {
/* do something */;
increment_expression;
}
(两者之间有一个小区别,但只有在循环中使用 continue
语句时才会显示。)
如果你被困在一个 C 编译器损坏的荒岛上(或者你被困在一个喜欢提出“诡计”问题的教师的教室里),并且你不得不编写一个 C 程序而不使用 { {1}} 关键字,您可以:您可以使用 for
编写所有循环,而不会丢失功能。