确实do
/while
循环非常罕见。我认为这是因为很多循环的形式
while(something needs doing)
do it;
一般来说,这是一个很好的模式,并且它具有通常需要的特性,即如果不需要做任何事情,循环就会运行零次。
但是偶尔,无论如何,您肯定要至少进行一次循环旅行,这是有充分理由的。我最喜欢的例子是:将整数转换为其十进制表示为字符串,即实现printf("%d")
,或半标准itoa()
函数。
为了说明,这里是一个相当简单的实现itoa()
。这不是“传统”的表述。如果有人好奇,我将在下面更详细地解释它。但关键是它体现了规范算法,重复除以10以从右边挑选数字,并且使用普通while
循环编写......这意味着它有一个错误。
#include <stddef.h>
char *itoa(unsigned int n, char buf[], int bufsize)
{
if(bufsize < 2) return NULL;
char *p = &buf[bufsize];
*--p = '\0';
while(n > 0) {
if(p == buf) return NULL;
*--p = n % 10 + '0';
n /= 10;
}
return p;
}
如果你没有发现它,错误是如果你要求它转换整数,这段代码什么也不返回——一个空字符串0
。所以这是一个例子,当“无”可做时,我们不希望代码什么都不做——我们总是希望它至少产生一个数字。所以我们总是希望它至少完成一次循环。所以do
/while
循环只是门票:
do {
if(p == buf) return NULL;
*--p = n % 10 + '0';
n /= 10;
} while(n > 0);
所以现在我们有一个循环,它通常在到达 0时停止n
,但如果n
最初是 0——如果你传入一个 0——它会"0"
根据需要返回字符串。
正如所承诺的,这里有更多关于itoa
这个例子中的函数的信息。你传递给它的参数是: anint
来转换(实际上是 an unsigned int
,这样我们就不必担心负数);要渲染到的缓冲区;以及该缓冲区的大小。它返回一个char *
指向缓冲区的指针,指向渲染字符串的开头。(或者NULL
如果它发现你给它的缓冲区不够大,它就会返回。)这个实现的“非传统”方面是它从右到左填充数组,这意味着它不必反转最后的字符串——也意味着它返回给你的指针通常不是到缓冲区的开头。所以你必须使用它返回给你的指针作为要使用的字符串;你不能调用它,然后假设你交给它的缓冲区是你可以使用的字符串。
最后,为了完整起见,这里有一个小测试程序来测试这个版本itoa
。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int n;
if(argc > 1)
n = atoi(argv[1]);
else {
printf("enter a number: "); fflush(stdout);
if(scanf("%d", &n) != 1) return EXIT_FAILURE;
}
if(n < 0) {
fprintf(stderr, "sorry, can't do negative numbers yet\n");
return EXIT_FAILURE;
}
char buf[20];
printf("converted: %s\n", itoa(n, buf, sizeof(buf)));
return EXIT_SUCCESS;
}