基于Linux環(huán)境的進(jìn)度條實(shí)現(xiàn)方法
前言
在Linux環(huán)境下,C語言的輸入輸出控制有其獨(dú)特的魅力和實(shí)際應(yīng)用場景。本文將從回車換行和緩沖區(qū)的基礎(chǔ)知識講起,帶領(lǐng)大家探索如何在Linux環(huán)境中實(shí)現(xiàn)一個動態(tài)倒計(jì)時功能,并進(jìn)一步完成一個具有交互感的進(jìn)度條。通過這些內(nèi)容,你不僅可以理解C語言在Linux中的輸出行為,還能掌握如何通過代碼提升程序的可視化表現(xiàn)。無論是Linux開發(fā)初學(xué)者,還是想深入了解C語言底層實(shí)現(xiàn)的同學(xué),這篇文章都將為你帶來新的啟發(fā)。
一、預(yù)備知識
1.1 回車換行
真正意義上,回車換行其實(shí)是兩個動作,在C語言中\n
卻同時完成了回車+換行的兩步動作。
- 回車:將光標(biāo)移到當(dāng)前行的最左側(cè)
- 換行:將光標(biāo)移到當(dāng)前行對應(yīng)位置的下一行
在C語言中可以使用轉(zhuǎn)義字符\n
來實(shí)現(xiàn)單獨(dú)的回車行為。
如圖展示以下以前的老式鍵盤:
這種電腦鍵盤上的ENTER
按鍵就是同時實(shí)現(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
后對代碼編譯運(yùn)行,先是休眠了兩秒,
接著才在屏幕上打印出hello world
,并且因?yàn)闆]有\n
,所以打印完后沒有換行,導(dǎo)致命令行提示符就緊跟在打印結(jié)果的后面。
情景分析
那么問題來了,這段代碼是先執(zhí)行sleep
,還是先執(zhí)行printf
打印呢?
很多人會根據(jù)上面的現(xiàn)象猜測,這段代碼先執(zhí)行了sleep
休眠,再去執(zhí)行printf
打印,這樣的猜測是錯誤的!因?yàn)槿魏我粋€C語言程序,都是嚴(yán)格按照代碼的編寫順序去執(zhí)行的。
那在休眠的兩秒期間,printf
的打印結(jié)果存在哪里了呢?
hello world
其實(shí)是保存在了緩沖區(qū)中,緩沖區(qū)是用于臨時存儲數(shù)據(jù)的內(nèi)存空間,默認(rèn)當(dāng)程序結(jié)束的時候才會將緩沖區(qū)中的內(nèi)容刷新出來。
如何強(qiáng)制刷新緩沖區(qū)
任何一個C語言程序運(yùn)行的時候都會默認(rèn)幫我們打開以下三個流:
- stdin - - - - 標(biāo)準(zhǔn)輸入流(鍵盤)
- stdout - - - - 標(biāo)準(zhǔn)輸出流(顯示器)
- stderr - - - - 標(biāo)準(zhǔn)錯誤(顯示器)
Linux下一切皆文件,這三個流都是FILE*
的指針,所以任何一個C語言程序運(yùn)行的時候,操作系統(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ū)。
二、倒計(jì)時
學(xué)習(xí)了上面的東西,我們可以先來實(shí)現(xiàn)一個簡單的倒計(jì)時練練手
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 開始計(jì)數(shù)
直到變成 0 為止。
2.3 注意事項(xiàng):
- 每打印一個數(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
來控制,每次打印兩個位寬的字符,-
表示將這兩個字符左對齊。如果不進(jìn)行格式化控制,打印出來的結(jié)果將是下面這樣:
三、進(jìn)度條
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);//以微秒為單位進(jìn)行休眠,想讓進(jìn)度條10秒跑完,因?yàn)橐还矔h(huán)101次,所以每次循環(huán)大概就是休眠0.1秒,100毫秒,10000微秒 } printf("\n"); }
效果演示
3.2 代碼分析
進(jìn)度條往右走的實(shí)現(xiàn)原理
- 進(jìn)度條的可視化:
bar
表示進(jìn)度條的當(dāng)前狀態(tài),用字符填充進(jìn)度條并逐步延長。cnt
代表當(dāng)前進(jìn)度百分比(從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); // 強(qiáng)制刷新輸出緩沖 bar[cnt++] = STYLE; // 填充進(jìn)度條中的下一個字符 if(cnt < 100) { bar[cnt] = BODY; // 設(shè)置進(jìn)度條下一位置的占位符(非滿狀態(tài)) } usleep(100000); // 延遲0.1秒 }
分析逐步展開:
- 初始狀態(tài):
cnt
從0
開始,bar
數(shù)組全為空字符,進(jìn)度條未顯示任何填充內(nèi)容。- 動態(tài)提示符從
lable
的第一個字符開始(|
)。
- 每次循環(huán)中:
- 動態(tài)更新輸出:
- 使用
printf
打印格式化輸出:[%-100s]
:打印一個左對齊的進(jìn)度條,長度為100
字符。[cnt%%]
:打印當(dāng)前百分比。[lable[cnt % len]]
:顯示旋轉(zhuǎn)提示符,cnt % len
保證提示符循環(huán)顯示。
- 使用
- 刷新進(jìn)度條:
bar[cnt++] = STYLE
:在bar
數(shù)組的第cnt
位置填充進(jìn)度條樣式字符STYLE
。- 如果
cnt < 100
,在下一個位置設(shè)置占位符BODY
(非滿狀態(tài)時)。
- 延遲:
usleep(100000)
延遲0.1秒,控制進(jìn)度條更新的速度。
- 覆蓋上一行:
- 使用
\r
回到行首,使當(dāng)前輸出覆蓋上一行,達(dá)到刷新效果。
- 使用
- 動態(tài)更新輸出:
- 終止條件:
- 當(dāng)
cnt > TOP
時退出循環(huán),表示進(jìn)度條已完成。
- 當(dāng)
3.3 實(shí)際使用場景
上面的processBar.c
中為了演示進(jìn)度條的原理,在里面寫了一個while
循環(huán)來模擬,但實(shí)際上的進(jìn)度條并不是這樣用的。以下載東西為例,作為一個進(jìn)度條,它本身并不知道下載了多少,它只會提供一個接口,在下載東西的時候,調(diào)用這個接口,然后將已經(jīng)下載好的比率作為參數(shù)傳給進(jìn)度條模塊,它會根據(jù)比率打印出對應(yīng)的進(jìn)度條樣式。
版本一
//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);//模擬下載花費(fèi)時間 cur += 10;//循環(huán)下載了一部分,更新進(jì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);//模擬下載花費(fèi)時間 cur += 10;//循環(huán)下載了一部分,更新進(jì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)輸出功能是一項(xiàng)非常實(shí)用的技能。從回車換行的基礎(chǔ)概念到炫酷的進(jìn)度條展示,我們一步步地感受到了C語言的強(qiáng)大控制力以及其在終端交互中的無限潛力。希望本文能幫助你更好地理解Linux環(huán)境下C語言的這些核心知識點(diǎn),同時也為你的編程旅程增添更多的趣味與技巧!期待你在實(shí)踐中創(chuàng)造更多精彩!
以上就是基于Linux環(huán)境的進(jìn)度條實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于Linux實(shí)現(xiàn)進(jìn)度條的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
新版ubuntu20.04 使用root用戶登錄系統(tǒng)的詳細(xì)教程
這篇文章主要介紹了新版ubuntu20.04 使用root用戶登錄系統(tǒng)的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08ubuntu16.10安裝docker17.03.0-ce并配置國內(nèi)源和加速器
這篇文章主要介紹了ubuntu16.10安裝docker17.03.0-ce并配置國內(nèi)源和加速器,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05