引入
我们在写 C 语言题目时,经常会碰见类似于
数字 分隔符 数字 分隔符 数字 分隔符
这样的输出。比如下面这段代码:
1=1
1+2=3
1+2+3=6
1+2+3+4=10
如果用循环的话,这个加号是个大问题。直接用
printf("%d+")
,最后面会多一个加号;用
printf("+%d")
则最前面会多一个加号。
想要解决,则必须判断当前输出的是否为第一个或者最后一个数字,然后做特殊处理。
新思路
有人就发现了,转义字符里有一个
\b
,这是个退格字符,能不能用它把多余的加号给删了呢?
那就试试呗,先输出个从 1 加到 5 试试
#include <stdio.h>
#include <stdlib.h>
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 <stdio.h>
#include <stdlib.h>
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 <stdio.h>
#include <stdlib.h>
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 <stdio.h>
#include <stdlib.h>
#include <windows.h>
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 <stdio.h>
#include <stdlib.h>
#include <windows.h>
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;
}
演示