目录
1. \r 和 \n
\n 表示换行,\r 表示换到当前行的开始。
举例说明:
写一个程序,当程序运行到打印字符串时,遇到 \r 就把光标回到最开始,遇到 \n 就换行。执行完 \r \n 之后光标移动到下一行开头处。(注:\r \n 和 \n 效果相同),程序如下:
、
2 缓冲区的概念
2.1 示例1
将代码改乘如下形式,printf 中没有 \r \n,保存并退出,图示如下:
执行 make 生成 myfile 可执行文件,运行 myfile,观察到闪烁 3s 后打印了 Hello World!
问题:
1、源代码中 sleep 是在 printf 后的,程序都是从上到下执行,为什么不是先打印再延迟 3s 出现命令行呢?
答: 在程序中,printf 先被执行,但是由于由 sleep 函数,导致先被打印的 printf 函数没有被刷新,而是被保存在缓冲区内。
2、为什么加了 \n 后数据就显示出来了呢?
答: 因为缓冲区有自己的刷新策略。字符串暂时以行缓冲的方式保存在缓冲区里,行缓冲指遇到了换行符 \n 就把换行符在内的之前的所有内容全部刷新出来。由于最后进程退出,曾经缓冲区里的数据就被刷新出来。
2.2 示例2
看如下程序:
如果只有 \r 表示光标回到改行开始,但是数据并没有被移除,该程序先打印 printf 内容,然后光标回到最开始,printf 里的内容在显示之前都是存放在缓冲区里的,fflush(stdout) 表示清空输出缓冲区里的内容并把缓冲区内容输出。之后执行 sleep 函数延迟 1s 打印提示符,程序如下:
此时终端上首先显示 Hello World!,图示如下:
一秒之后 Hello World! 被提示符覆盖。 如果不加 \r 打印 printf 的内容就不会被覆盖,图示如下:
2.3 示例3
看如下程序:
当程序执行到 \n 时,缓冲区里的内容立刻被刷新出来,然后每隔 1s 执行打印的动作,图示如下:
![]()
输出结果如图所示:
如果把程序中的 \n 换成 \r,那么在输出的时候就是 9\r 、8\r 。。。刷新的时候先把 9 刷新出来然后光标回到开始,下次打印就会把 9 替换掉,图示如下:
此时保存退出运行可执行程序,发现终端最后什么都没有,因为打印的数据都放在缓冲区,没刷新数据就不能进行显示,图示如下:
![]()
需要 fflush(stdout) 进行刷新,图示如下:
此时再运行就完成了一个基本的倒计时,图示如下:
注意: 凡是向显示器打印的所有内容都是字符,如 **printf(“%d”,123);**向屏幕中打印的是字符 1、字符 2 和字符 3。如果将上述代码的 i 改为 10,那么再运行就会导致 0 总是显示,后面的数据仅仅覆盖第一个字符,图示如下:
所以 应该改为 printf(“2d\r”,i);表示预留出两个字符的空间,把没有覆盖掉的 0 也给覆盖掉。
3 实现一个进度条
进度条样式如下:
3.1 初始代码
首先编写如下代码:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define SIZE 101
void process()
{
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//由于换行符\n刷新了缓冲区,所以这里直接输出bar里面的内容
printf("[%s]\n",bar);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
bar[i] = '#';
i++;
sleep(1);
}
}
结果及分析:
运行此段代码可以看到如下结果,当在输入和输出中遇到换行符 \n 时,执行真正的I/O操作。由于 printf 里面有 \n ,行缓冲区遇到 \n 就会输出行缓冲区里面的内容,即字符串 bar,直到最后退出循环程序结束,图示如下:
3.2 加入缓冲区进行修正
以上并不是我们想得到的进度条,所以还需要对代码进行修正:
1、由于进度条不会出现换行,所以每次打印的时候需要回到当前行的开始,不需要换行,所以将 printf 里面的 \n 替换成 \r。
代码如下:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define SIZE 101
void process()
{
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//将\n替换成\r,让其每次先进行打印,然后回到行首
printf("[%s]\r",bar);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
bar[i] = '#';
i++;
sleep(1);
}
}
此时再保存退出,重新 make 并执行,发现光标一直在下一行的行首没有动,因为打印的数据被暂存在缓冲区,只有程序结束后或者缓冲区满了才进行打印。针对此情况,需要对代码进行进一步的改进,图示如下:
2、加入 fflush(stdout) 刷新缓冲区内容,使其每次循环都将缓冲区里的内容进行输出。
代码如下:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define SIZE 101
void process()
{
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//将\n替换成\r,让其每次先进行打印,然后回到行首
printf("[%s]\r",bar);
//刷新缓冲区里面的内容
fflush(stdout);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
bar[i] = '#';
i++;
sleep(1);
}
}
运行结果如下:
3.3 变成左对齐
但是此时该进度条依旧不符合要求,所以还需进一步改进:让右括号固定位置保持不动,填写100s表示在格式控制里预留100个字符。
代码如下:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define STYLE '#'
#define SIZE 101
void process()
{
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//由于换行符\n刷新了缓冲区,所以这里直接输出bar里面的内容
//让右括号固定位置保持不动,填写100s表示在格式控制里预留100个字符
printf("[%100s]\r",bar);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
fflush(stdout);
bar[i] = STYLE;
i++;
usleep(10000);
}
}
重新编译运行,会发现此时是从最右侧开始的,图示如下:
而且在执行完之后命令行会覆盖掉一部分,所以针对这两个问题进行改进,图示如下:
因为C语言中默认为右对齐,所以将 [%100s] 改为 [%-100s],变成左对齐即可,最后添加 **printf(“\n”);**另起一行打印命令行提示符,就不会覆盖掉打印的字符串了。
代码如下:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define STYLE '#'
#define SIZE 101
void process()
{
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//由于换行符\n刷新了缓冲区,所以这里直接输出bar里面的内容
//让右括号固定位置保持不动,填写-100s表示在格式控制里预留100个字符,且左对齐
printf("[%-100s]\r",bar);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
fflush(stdout);
bar[i] = STYLE;
i++;
usleep(10000);
}
printf("\n");
}
保存退出编译运行,可以看到这时的进度条已经基本符合要求,但是还缺少一些东西,继续修改代码,图示如下:
3.4 以百分比的形式显示进度条的进度
对 printf 函数参数做出一些改进,让其以百分比的形式显示进度条的进度。
代码如下:
printf("[%-100s][%d%%]\r",bar,i);
3.5 添加旋转光标
最后再添加旋转光标,进度条就完成了!那么如何添加旋转光标呢?同一个位置依次输入 “ *| / - \ * ” 然后依次循环下去就相当于一个旋转光标。
代码如下:
#include "proc.h"
#include <string.h>
#include <unistd.h>
#define STYLE '#'
#define SIZE 101
void process()
{
const char *label = "|/-\\";
//由于未来会包含最多100个‘#’又因为字符串都以’\0为结尾,所以最终'#'满了之后需要预留一个字节的空间来保存'\0',所以字符串长度定为101
char bar[SIZE];
//向数组不断添加'#',打印的时候以bar的形式不断对缓冲区进行刷新,每新增一个'#',后面跟'\0'
//为了避免麻烦,在最开始的时候就把数组全部初始化成'\0'
memset(bar,'\0',sizeof(bar));
int i = 0;
//循环101次
while(i <= 100)
{
//由于换行符\n刷新了缓冲区,所以这里直接输出bar里面的内容
//让右括号固定位置保持不动,填写-100s表示在格式控制里预留100个字符,且左对齐
printf("[%-100s][%d%%][%c]\r",bar,i,label[i%4]);
//设置i位置为'#',当i==100时,数组的第101个位置也被置为'#'
fflush(stdout);
bar[i] = STYLE;
i++;
usleep(100000);
}
printf("\n");
}
保存退出编译运行最后得到符合要求的进度条,图示如下:
4 创建的文件结构
创建如下几个文件: