前言
在linux环境下,c语言的输入输出控制有其独特的魅力和实际应用场景。本文将从回车换行和缓冲区的基础知识讲起,带领大家探索如何在linux环境中实现一个动态倒计时功能,并进一步完成一个具有交互感的进度条。通过这些内容,你不仅可以理解c语言在Linux中的输出行为,还能掌握如何通过代码提升程序的可视化表现。无论是linux开发初学者,还是想深入了解c语言底层实现的同学,这篇文章都将为你带来新的启发。
?一、预备知识?1.1 回车换行
真正意义上,回车换行其实是两个动作,在C语言中 却同时完成了回车+换行的两步动作。
回车:将光标移到当前行的最左侧换行:将光标移到当前行对应位置的下一行
在C语言中可以使用转义字符 来实现单独的回车行为。
如图展示以下以前的老式键盘:

这种电脑键盘上的ENTER按键就是同时实现了回车和换行的功能,按下ENTER键,光标会去到下一行的最左侧的位置。
?1.2 缓冲区
先看一段代码
代码语言:JavaScript代码运行次数:0运行复制
这段代码很简单,现在屏幕上打印出hello world,接着调用sleep函数让程序休眠两秒,

间隔两秒后。

接下来,我们对上面的代码稍作修改,去掉 再来试试。
代码语言:javascript代码运行次数:0运行复制
#include <unistd.h>int main() { printf("hello world"); sleep(2); return 0;}</unistd.h>
在去掉/n后对代码编译运行,先是休眠了两秒,

接着才在屏幕上打印出hello world,并且因为没有 ,所以打印完后没有换行,导致命令行提示符就紧跟在打印结果的后面。

情景分析
那么问题来了,这段代码是先执行sleep,还是先执行printf打印呢?
很多人会根据上面的现象猜测,这段代码先执行了sleep休眠,再去执行printf打印,这样的猜测是错误的!因为任何一个C语言程序,都是严格按照代码的编写顺序去执行的。
那在休眠的两秒期间,printf的打印结果存在哪里了呢?
hello world其实是保存在了缓冲区中,缓冲区是用于临时存储数据的内存空间,默认当程序结束的时候才会将缓冲区中的内容刷新出来。
如何强制刷新缓冲区
任何一个C语言程序运行的时候都会默认帮我们打开以下三个流:
stdin – – – – 标准输入流(键盘)stdout – – – – 标准输出流(显示器)stderr – – – – 标准错误(显示器)

Linux下一切皆文件,这三个流都是FILE*的指针,所以任何一个C语言程序运行的时候,操作系统会帮我们打开以上三个文件。今天我们只需要关心stdout标准输出流即可。我们可以通过fflush函数来刷新缓冲区。
代码语言:javascript代码运行次数:0运行复制
#include <stdio.h>#include <unistd.h>int main(){ printf("hello world"); fflush(stdout); sleep(2); return 0;}</unistd.h></stdio.h>

等待两秒后…

通过上面的分析我们可以得出,刷新缓冲区主要有以下几种方法:
可以刷新缓冲区。程序结束也会刷新缓冲区。fflush(stdout)可以手动刷新缓冲区。?二、倒计时
学习了上面的东西,我们可以先来实现一个简单的倒计时练练手
?2.1 源代码代码语言:javascript代码运行次数:0运行复制
#include "processBar.h"#include <unistd.h>int main(){int cnt = 10;while(cnt >= 0){printf("%-2d ",cnt);fflush(stdout);sleep(1);cnt--;}printf(" "); return 0;}</unistd.h>
?2.2 效果展示
从 10 开始计数

直到变成 0 为止。
?2.3 注意事项: 每打印一个数字后紧跟着打印一个 回车,让光标回到这一行最开始的位置,这样新打印的数字就会去覆盖掉老的数字。但是 不会去刷新缓冲区,因此在每打印完一个数字后,需要调用fflush(stdout)来刷新缓冲区。 这里我们需要知道,往显示器上打印整型10,本质上是打印了字符1和字符0,由于这两个字符是挨在一起的,我们看起来就像是整型10。因此打印10,会占用两个字符,而打印0~9只需要一个字符,所以 回车之后去覆盖写,只会覆盖一个字符,对第二个字符0始终没有影响,因此我们需要用%-2d来控制,每次打印两个位宽的字符,-表示将这两个字符左对齐。如果不进行格式化控制,打印出来的结果将是下面这样:

?三、进度条?3.1 源代码?processBar.h代码语言:javascript代码运行次数:0运行复制
#pragma once#include <stdio.h>#define NUM 102#define STYLE '=' #define TOP 100#define BODY '$'extern void processbar();</stdio.h>
?processBar.c代码语言:javascript代码运行次数:0运行复制
#include "processBar.h"#include <string.h>#include <unistd.h>const char* lable = "|/-";//旋转提示void processbar(){ char bar[NUM]; memset(bar, ' ', sizeof(bar));int len = strlen(lable);int cnt = 0;while(cnt ?效果演示<figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174494341687674.jpg" alt="基于Linux环境的进度条实现"></figure>?3.2 代码分析<p>进度条往右走的实现原理</p>进度条的可视化: bar表示进度条的当前状态,用字符填充进度条并逐步延长。cnt代表当前进度百分比(从0到100)。动态旋转提示: lable是旋转提示符,依次显示|, /, -, ,用来模拟动态效果。每次刷新屏幕: 使用 回到行首并覆盖之前的内容,fflush(stdout)刷新输出缓冲区,确保显示即时更新。通过usleep(100000)控制刷新间隔(每0.1秒更新一次)。<p>while循环逻辑分析:</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" style="max-width:90%" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">while(cnt <p>分析逐步展开:</p>初始状态: cnt从0开始,bar数组全为空字符,进度条未显示任何填充内容。动态提示符从lable的第一个字符开始(|)。每次循环中: 动态更新输出: 使用printf打印格式化输出: [%-100s]:打印一个左对齐的进度条,长度为100字符。[cnt%%]:打印当前百分比。[lable[cnt % len]]:显示旋转提示符,cnt % len保证提示符循环显示。刷新进度条: bar[cnt++] = STYLE:在bar数组的第cnt位置填充进度条样式字符STYLE。如果cnt TOP时退出循环,表示进度条已完成。完成状态: 输出" "换行符,表示进度条结束。?3.3 实际使用场景<p>上面的processBar.c中为了演示进度条的原理,在里面写了一个while循环来模拟,但实际上的进度条并不是这样用的。以下载东西为例,作为一个进度条,它本身并不知道下载了多少,它只会提供一个接口,在下载东西的时候,调用这个接口,然后将已经下载好的比率作为参数传给进度条模块,它会根据比率打印出对应的进度条样式。</p><p>版本一</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">//processBar.h#pragma once#include <stdio.h>#define NUM 102#define STYLE '='#define TOP 100#define BODY '>'extern void processbar(int ret);</stdio.h>
代码语言:javascript代码运行次数:0运行复制
//processBar.c#include "processBar.h"#include <string.h>#include <unistd.h>const char* lable = "|/-";//V2版本char bar[NUM] = {' '};//定义在全局避免每一次函数调用都会重现创建 void processbar(int ret){if(ret 100){ return;}if(ret == 0){ //当比率为0的时候将数组全置为' 'memset(bar, ' ', sizeof(bar));}int len = strlen(lable);printf("[%-100s][%d%%][%c] ", bar, ret, lable[ret%len]);fflush(stdout);bar[ret++] = STYLE;if(ret 代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">//main.cint main(){ int total = 1000;//假设总共要下载1000个G int cur = 0;//当前下载的 while(cur <p>版本二</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">//processBar.h#pragma once#include <stdio.h>#define NUM 102#define STYLE '='#define TOP 100#define BODY '>'extern void processbar(int ret);</stdio.h>
代码语言:javascript代码运行次数:0运行复制
//processBar.c#include "processBar.h"#include <string.h>#include <unistd.h>#define NONE "[m"#define red "[0;32;31M"#define GREEN "[0;32;32m"#define LIGHT_BLUE "[1;34m"#define LIGHT_PURPLE "[1;35m"const char* lable = "|/-";//V2版本char bar[NUM] = {' '};void processbar(int ret){if(ret 100)//合理性判断{return;}if(ret == 0)//当比率为0的时候将数组全置为' '{memset(bar, ' ', sizeof(bar));}int len = strlen(lable);printf("["LIGHT_BLUE"%-100s"NONE"]""[%d%%][%c] ", bar, ret, lable[ret%len]); fflush(stdout); bar[ret++] = STYLE;if(ret 代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">//main.c#include "processBar.h" #include <unistd.h> typedef void (*callback_t) (int); //模拟一种安装或者下载 void Downbload(callback_t ct) { int total = 1000;//假设总共要下载1000个MB int cur = 0;//当前下载的 while(cur <p>效果展示</p> <figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174494341614821.jpg" alt="基于Linux环境的进度条实现"></figure>结语<p>在Linux环境中,掌握C语言的缓冲区管理和动态输出功能是一项非常实用的技能。从回车换行的基础概念到炫酷的进度条展示,我们一步步地感受到了C语言的强大控制力以及其在终端交互中的无限潜力。希望本文能帮助你更好地理解Linux环境下C语言的这些核心知识点,同时也为你的编程旅程增添更多的趣味与技巧!期待你在实践中创造更多精彩!</p></unistd.h>