引入

我们在写 C 语言题目时,经常会碰见类似于 数字 分隔符 数字 分隔符 数字 分隔符 这样的输出。比如下面这段代码:

1=1

1+2=3

1+2+3=6

1+2+3+4=10

如果用循环的话,这个加号是个大问题。直接用 printf("%d+"),最后面会多一个加号;用 printf("+%d") 则最前面会多一个加号。 想要解决,则必须判断当前输出的是否为第一个或者最后一个数字,然后做特殊处理。

新思路

有人就发现了,转义字符里有一个\b,这是个退格字符,能不能用它把多余的加号给删了呢? 那就试试呗,先输出个从 1 加到 5 试试

#include

#include

int main(void)

{

int sum = 0;

for (int i = 1; i <= 5; i++)

{

printf("%d+", i);

sum += i;

}

printf("\b");

printf("=%d\n", sum);

return 0;

}

好像没什么毛病,对吧? 让我们再提交到判题平台上试试 加号居然没删掉,而且还多了个点出来!

真实含义

我们把上面的代码稍稍改动一下

#include

#include

int main(void)

{

int sum = 0;

for (int i = 1; i <= 5; i++)

{

printf("%d+", i);

sum += i;

}

printf("\b");

//printf("=%d\n", sum); //<---- 注释这一行

return 0;

}

运行效果: 这段代码和上面的一模一样,只是把等号后面的输出给删掉了而已。 但是,最后的加号居然神奇地又出现了! 这是为什么呢?

我们先来看一下“退格”究竟为何含义。 \b 字符的确是退格字符,但此退格非“删除”。“退格”就是字面含义上的退格,即“往前退一格”,相当于你在 Word 里按一下左方向键。

也就是说,\b 并不能删除上一个字符,它只是把光标往前移了一下而已。

那开头的代码为什么能正常输出呢? 很简单,因为后面输出的字符覆盖掉了前面的字符,因此看起来好像是把上一个字符给删了。 为了更清晰的表示这个过程,我做了一个动图。(偷一下懒,图里只制作了三个数字求和,但原理是一样的。) 事实上退格键在早期打印机上的作用就是“往前退一格”,后来退格键的含义变了,变成了“往前退一格 + 删除一个字符”。

实际输出与显示

问题还没有完全解决:为什么在本地可以,但是上传到判题平台就不行了呢? 因为“显示的内容 ≠ 输出的内容”。

请看以下代码:

#include

#include

int main()

{

printf("123\n");

printf("123\b\n");

printf("123\b4\n");

return 0;

}

运行结果

然而,我们把它编译,然后把程序的输出结果重定向到文件里,得到的结果是这样的:

123

123

1234

这段输出在不同的地方显示的内容可能不相同 Windows 记事本:一个框

Visual Studio 2015:啥也没有 Visual Studio 2019:一个带空心圈的实心框 Sublime: 可以看到我们的 \b 字符,也就是 ASCII 码为 0x08 的字符被原样输出了出来, 在文本编辑器里并没有实现退格的效果。 判题平台上使用的就是类似的方法,把程序的输出直接导出,传到网站上显示,但浏览器可不认 \b,于是就显示为了一个红点。

实际应用

利用这个退格字符,我们可以做一个进度条出来 第一种:

#include

#include

#include

int main()

{

int index = 0;

char ch[] = {'|', '\\', '-', '/'};

while (1)

{

putchar(ch[index]);

index++;

if (index >= 4)

index = 0;

Sleep(200); // Sleep(200) 的作用是延时 200 毫秒(0.2 秒)再继续执行下面的代码

putchar('\b');

}

return 0;

}

演示

第二种:

#include

#include

#include

int main()

{

//假设要做一个耗时较长的操作

//为了更好的用户体验,我们需要一个进度条

double progress = 0.1; //当前进度

int length = 15; //进度条字符长度

for (progress = 0.1; progress <= 1; progress += 0.05)

{

//先输出 length 个 \b,把光标倒到开头去

//也可以直接用一个 \r

for (int j = 0; j < length + 2; j++)

putchar('\b');

putchar('[');

//已经完成部分的进度条

int count = (int)(length * progress);

for (int j = 0; j < count; j++)

putchar('#');

//未完成部分的进度条

for (int j = 0; j < length - count; j++)

putchar(' ');

putchar(']');

Sleep(100);

}

return 0;

}

演示