基于Linux環(huán)境的進度條實現(xiàn)方法
前言
在Linux環(huán)境下,C語言的輸入輸出控制有其獨特的魅力和實際應(yīng)用場景。本文將從回車換行和緩沖區(qū)的基礎(chǔ)知識講起,帶領(lǐng)大家探索如何在Linux環(huán)境中實現(xiàn)一個動態(tài)倒計時功能,并進一步完成一個具有交互感的進度條。通過這些內(nèi)容,你不僅可以理解C語言在Linux中的輸出行為,還能掌握如何通過代碼提升程序的可視化表現(xiàn)。無論是Linux開發(fā)初學(xué)者,還是想深入了解C語言底層實現(xiàn)的同學(xué),這篇文章都將為你帶來新的啟發(fā)。
一、預(yù)備知識
1.1 回車換行
真正意義上,回車換行其實是兩個動作,在C語言中\n卻同時完成了回車+換行的兩步動作。
- 回車:將光標(biāo)移到當(dāng)前行的最左側(cè)
- 換行:將光標(biāo)移到當(dāng)前行對應(yīng)位置的下一行
在C語言中可以使用轉(zhuǎn)義字符\n來實現(xiàn)單獨的回車行為。
如圖展示以下以前的老式鍵盤:

這種電腦鍵盤上的ENTER按鍵就是同時實現(xiàn)了回車和換行的功能,按下ENTER鍵,光標(biāo)會去到下一行的最左側(cè)的位置。
1.2 緩沖區(qū)
先看一段代碼
#include <unistd.h>
int main() {
printf("hello world\n");
sleep(2);
return 0;
}
這段代碼很簡單,現(xiàn)在屏幕上打印出hello world,接著調(diào)用sleep函數(shù)讓程序休眠兩秒,

間隔兩秒后。

接下來,我們對上面的代碼稍作修改,去掉\n再來試試。
#include <unistd.h>
int main() {
printf("hello world");
sleep(2);
return 0;
}
在去掉/n后對代碼編譯運行,先是休眠了兩秒,

接著才在屏幕上打印出hello world,并且因為沒有\n,所以打印完后沒有換行,導(dǎo)致命令行提示符就緊跟在打印結(jié)果的后面。

情景分析
那么問題來了,這段代碼是先執(zhí)行sleep,還是先執(zhí)行printf打印呢?
很多人會根據(jù)上面的現(xiàn)象猜測,這段代碼先執(zhí)行了sleep休眠,再去執(zhí)行printf打印,這樣的猜測是錯誤的!因為任何一個C語言程序,都是嚴格按照代碼的編寫順序去執(zhí)行的。
那在休眠的兩秒期間,printf的打印結(jié)果存在哪里了呢?
hello world其實是保存在了緩沖區(qū)中,緩沖區(qū)是用于臨時存儲數(shù)據(jù)的內(nèi)存空間,默認當(dāng)程序結(jié)束的時候才會將緩沖區(qū)中的內(nèi)容刷新出來。
如何強制刷新緩沖區(qū)
任何一個C語言程序運行的時候都會默認幫我們打開以下三個流:
- stdin - - - - 標(biāo)準(zhǔn)輸入流(鍵盤)
- stdout - - - - 標(biāo)準(zhǔn)輸出流(顯示器)
- stderr - - - - 標(biāo)準(zhǔn)錯誤(顯示器)

Linux下一切皆文件,這三個流都是FILE*的指針,所以任何一個C語言程序運行的時候,操作系統(tǒng)會幫我們打開以上三個文件。今天我們只需要關(guān)心stdout標(biāo)準(zhǔn)輸出流即可。我們可以通過fflush函數(shù)來刷新緩沖區(qū)。
#include <stdio.h>
#include <unistd.h>
int main(){
printf("hello world");
fflush(stdout);
sleep(2);
return 0;
}

等待兩秒后…

通過上面的分析我們可以得出,刷新緩沖區(qū)主要有以下幾種方法:
\n可以刷新緩沖區(qū)。- 程序結(jié)束也會刷新緩沖區(qū)。
fflush(stdout)可以手動刷新緩沖區(qū)。
二、倒計時
學(xué)習(xí)了上面的東西,我們可以先來實現(xiàn)一個簡單的倒計時練練手
2.1 源代碼
#include "processBar.h"
#include <unistd.h>
int main(){
int cnt = 10;
while(cnt >= 0){
printf("%-2d\r",cnt);
fflush(stdout);
sleep(1);
cnt--;
}
printf("\n");
return 0;
}
2.2 效果展示
從 10 開始計數(shù)

直到變成 0 為止。
2.3 注意事項:
- 每打印一個數(shù)字后緊跟著打印一個
\r回車,讓光標(biāo)回到這一行最開始的位置,這樣新打印的數(shù)字就會去覆蓋掉老的數(shù)字。但是\r不會去刷新緩沖區(qū),因此在每打印完一個數(shù)字后,需要調(diào)用fflush(stdout)來刷新緩沖區(qū)。 - 這里我們需要知道,往顯示器上打印整型10,本質(zhì)上是打印了字符1和字符0,由于這兩個字符是挨在一起的,我們看起來就像是整型10。因此打印10,會占用兩個字符,而打印0~9只需要一個字符,所以
\r回車之后去覆蓋寫,只會覆蓋一個字符,對第二個字符0始終沒有影響,因此我們需要用%-2d來控制,每次打印兩個位寬的字符,-表示將這兩個字符左對齊。如果不進行格式化控制,打印出來的結(jié)果將是下面這樣:

三、進度條
3.1 源代碼
processBar.h
#pragma once #include <stdio.h> #define NUM 102 #define STYLE '=' #define TOP 100 #define BODY '$' extern void processbar();
processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";//旋轉(zhuǎn)提示
void processbar(){
char bar[NUM];
memset(bar, '\0', sizeof(bar));
int len = strlen(lable);
int cnt = 0;
while(cnt <= TOP){
printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%len]);
fflush(stdout);
bar[cnt++] = STYLE;
if(cnt < 100) {
bar[cnt] = BODY;
}
usleep(100000);//以微秒為單位進行休眠,想讓進度條10秒跑完,因為一共會循環(huán)101次,所以每次循環(huán)大概就是休眠0.1秒,100毫秒,10000微秒
}
printf("\n");
}
效果演示

3.2 代碼分析
進度條往右走的實現(xiàn)原理
- 進度條的可視化:
bar表示進度條的當(dāng)前狀態(tài),用字符填充進度條并逐步延長。cnt代表當(dāng)前進度百分比(從0到100)。
- 動態(tài)旋轉(zhuǎn)提示:
lable是旋轉(zhuǎn)提示符,依次顯示|,/,-,\,用來模擬動態(tài)效果。
- 每次刷新屏幕:
- 使用
\r回到行首并覆蓋之前的內(nèi)容,fflush(stdout)刷新輸出緩沖區(qū),確保顯示即時更新。 - 通過
usleep(100000)控制刷新間隔(每0.1秒更新一次)。
- 使用
while循環(huán)邏輯分析:
while(cnt <= TOP) {
printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt % len]);
fflush(stdout); // 強制刷新輸出緩沖
bar[cnt++] = STYLE; // 填充進度條中的下一個字符
if(cnt < 100) {
bar[cnt] = BODY; // 設(shè)置進度條下一位置的占位符(非滿狀態(tài))
}
usleep(100000); // 延遲0.1秒
}
分析逐步展開:
- 初始狀態(tài):
cnt從0開始,bar數(shù)組全為空字符,進度條未顯示任何填充內(nèi)容。- 動態(tài)提示符從
lable的第一個字符開始(|)。
- 每次循環(huán)中:
- 動態(tài)更新輸出:
- 使用
printf打印格式化輸出:[%-100s]:打印一個左對齊的進度條,長度為100字符。[cnt%%]:打印當(dāng)前百分比。[lable[cnt % len]]:顯示旋轉(zhuǎn)提示符,cnt % len保證提示符循環(huán)顯示。
- 使用
- 刷新進度條:
bar[cnt++] = STYLE:在bar數(shù)組的第cnt位置填充進度條樣式字符STYLE。- 如果
cnt < 100,在下一個位置設(shè)置占位符BODY(非滿狀態(tài)時)。
- 延遲:
usleep(100000)延遲0.1秒,控制進度條更新的速度。
- 覆蓋上一行:
- 使用
\r回到行首,使當(dāng)前輸出覆蓋上一行,達到刷新效果。
- 使用
- 動態(tài)更新輸出:
- 終止條件:
- 當(dāng)
cnt > TOP時退出循環(huán),表示進度條已完成。
- 當(dāng)
3.3 實際使用場景
上面的processBar.c中為了演示進度條的原理,在里面寫了一個while循環(huán)來模擬,但實際上的進度條并不是這樣用的。以下載東西為例,作為一個進度條,它本身并不知道下載了多少,它只會提供一個接口,在下載東西的時候,調(diào)用這個接口,然后將已經(jīng)下載好的比率作為參數(shù)傳給進度條模塊,它會根據(jù)比率打印出對應(yīng)的進度條樣式。
版本一
//processBar.h #pragma once #include <stdio.h> #define NUM 102 #define STYLE '=' #define TOP 100 #define BODY '>' extern void processbar(int ret);
//processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";
//V2版本
char bar[NUM] = {'\0'};//定義在全局避免每一次函數(shù)調(diào)用都會重現(xiàn)創(chuàng)建
void processbar(int ret){
if(ret <0 || ret > 100){
return;
}
if(ret == 0){ //當(dāng)比率為0的時候?qū)?shù)組全置為'\0'
memset(bar, '\0', sizeof(bar));
}
int len = strlen(lable);
printf("[%-100s][%d%%][%c]\r", bar, ret, lable[ret%len]);
fflush(stdout);
bar[ret++] = STYLE;
if(ret < 100){
bar[ret] = BODY;
}
}
//main.c
int main(){
int total = 1000;//假設(shè)總共要下載1000個G
int cur = 0;//當(dāng)前下載的
while(cur <= total) {
processbar(cur * 100 / total);
usleep(50000);//模擬下載花費時間
cur += 10;//循環(huán)下載了一部分,更新進度
}
return 0;
}
版本二
//processBar.h #pragma once #include <stdio.h> #define NUM 102 #define STYLE '=' #define TOP 100 #define BODY '>' extern void processbar(int ret);
//processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
#define NONE "\033[m"
#define RED "\033[0;32;31M"
#define GREEN "\033[0;32;32m"
#define LIGHT_BLUE "\033[1;34m"
#define LIGHT_PURPLE "\033[1;35m"
const char* lable = "|/-\\";
//V2版本
char bar[NUM] = {'\0'};
void processbar(int ret){
if(ret <0 || ret > 100)//合理性判斷{
return;
}
if(ret == 0)//當(dāng)比率為0的時候?qū)?shù)組全置為'\0'{
memset(bar, '\0', sizeof(bar));
}
int len = strlen(lable);
printf("["LIGHT_BLUE"%-100s"NONE"]""[%d%%][%c]\r", bar, ret, lable[ret%len]);
fflush(stdout);
bar[ret++] = STYLE;
if(ret < 100){
bar[ret] = BODY;
}
}
//main.c
#include "processBar.h"
#include <unistd.h>
typedef void (*callback_t) (int);
//模擬一種安裝或者下載
void Downbload(callback_t ct) {
int total = 1000;//假設(shè)總共要下載1000個MB
int cur = 0;//當(dāng)前下載的
while(cur <= total) {
int rate = cur*100/total;
ct(rate);
usleep(50000);//模擬下載花費時間
cur += 10;//循環(huán)下載了一部分,更新進度
}
printf("\n");
}
int main(){
printf("Downbload 1:\n");
Downbload(processbar);
printf("Downbload 2:\n");
Downbload(processbar);
printf("Downbload 3:\n");
Downbload(processbar);
printf("Downbload 4:\n");
Downbload(processbar);
return 0;
}
效果展示

結(jié)語
在Linux環(huán)境中,掌握C語言的緩沖區(qū)管理和動態(tài)輸出功能是一項非常實用的技能。從回車換行的基礎(chǔ)概念到炫酷的進度條展示,我們一步步地感受到了C語言的強大控制力以及其在終端交互中的無限潛力。希望本文能幫助你更好地理解Linux環(huán)境下C語言的這些核心知識點,同時也為你的編程旅程增添更多的趣味與技巧!期待你在實踐中創(chuàng)造更多精彩!
以上就是基于Linux環(huán)境的進度條實現(xiàn)方法的詳細內(nèi)容,更多關(guān)于Linux實現(xiàn)進度條的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
新版ubuntu20.04 使用root用戶登錄系統(tǒng)的詳細教程
這篇文章主要介紹了新版ubuntu20.04 使用root用戶登錄系統(tǒng)的詳細教程,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
ubuntu16.10安裝docker17.03.0-ce并配置國內(nèi)源和加速器
這篇文章主要介紹了ubuntu16.10安裝docker17.03.0-ce并配置國內(nèi)源和加速器,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05

