為了寫出這個小程序我們先來了解幾個知識點
(一)回車和換行
先以寫作文為例子了解一下,當在一行中寫了一半,由此處位置往下一行的操作叫做換行,回到該行的開頭位置為回車。
而在c語言中\n幫我們完成了換行和回車兩個動作,那單純回車是——\r
(二)緩沖區概念
#include <stdio.h>
#include <unistd.h>int main()
{printf("hello world\n");sleep(3);return 0;
}
在LInux中如何查看sleep,man sleep即可
有什么現象:
即先打印hello world在休眠三秒
#include <stdio.h>
#include <unistd.h>int main()
{printf("hello world");sleep(3);return 0;
}
現象如下:
看到的是他是先休眠了,在打印的。那他的順序真的是這樣的嗎?
不對,在 C 語言中,printf("hello world") 先執行,但輸出內容會被暫存在標準輸出緩沖區(stdout 緩沖區) 中,并未立即顯示到屏幕。隨后執行 sleep(3) 進行休眠,3 秒后程序結束時,緩沖區會被自動刷新,內容才顯示出來。這就是為什么看起來像是 “先休眠再打印” 的原因。
為什么需要緩沖區?
標準輸出(stdout)默認是行緩沖模式:
- 當輸出內容包含換行符 \n 時,會自動刷新緩沖區
- 若沒有換行符,會積累到一定大小(通常是 4096 字節)或程序結束時才刷新
代碼中 printf 沒有換行符,因此內容暫存在緩沖區,直到程序結束才輸出,中間被 sleep(3) 打斷,造成了執行順序的 “錯覺”。
那如果我要強制刷新怎么辦了?
先了解C語言程序中默認會打開3個輸入輸出流,即標準輸入,標準輸出,標準錯誤。標準輸出為顯示器(stdout),其中Liunx下一切皆文件,那顯示器也是文件。
可以用 fflush 函數手動刷新指定流,立即輸出緩沖區內容:
#include <stdio.h>
#include <unistd.h> // 包含sleep函數的頭文件int main()
{printf("hello world");fflush(stdout); // 強制刷新標準輸出緩沖區sleep(3); return 0;
}
此時現象:
?為了看起來更順眼我們可以在后面加一個換行。
又有上面的知識點我們可以寫一個倒計時
#include <stdio.h> #include <unistd.h>int main() {int cut=10;while(cut>=0){printf("%-2d\r",cut);fflush(stdout);cut--;sleep(1);}printf("\n");return 0; }
運行結果:
細節解釋:
printf("%-2d\r",cut)為什么是%-2d
因為輸入10,顯示器會將它顯示成1 0
但是我們要的就是10,所以要寫成%2d,%2d是指定整數輸出時至少占用 2 個字符寬度,但是c語言進行輸出顯示,默認是右對齊,所以要用%-2d,-符合是強制左對齊。
?\r的作用
回車符,光標回到行首
總結:
- 每次輸出時,%-2d 確保無論數字是一位還是兩位,都占用固定的 2 個字符位置。
- 左對齊的補空格方式,能保證光標回到行首后,新數字會完全覆蓋上一次的輸出(不會留下殘留字符)。
(三)進度條的實現?
(1)?準備工作
依舊使用多文件,建立一個processbar.h,processbar.c ,test.c
其中processbar.c ,test.c包含processbar.h
(2)?實現
1. processbar.h代碼:
#pragma once
#include <stdio.h>#define NUM 102
#define STYLE '#'extern void processbar();
小細節:
1.1 extern void processbar();
這里的extern聲明表示processbar()函數在其他文件中定義,當前文件僅作聲明。在單個模塊(單文件)中確實可以省略,但在多文件項目中,使用extern可以清晰地表明這是一個外部函數,增強代碼的可讀性和規范性。
1.2. #define NUM 102
保證每次輸出的字符比上次多,定義一個數組,其中NUM為數據開辟的空間,使用宏定義方便統一修改,且可以避免在代碼中硬編碼數值,提高可維護性。
1.3. #define STYLE '#'
定義進度條的顯示字符,因為風格多變,當需要改變進度條樣式時,只需修改這一處定義即可
增強了代碼的靈活性
?2. test.c代碼:
調用這個小程序
#include "processbar.h"
#include <unistd.h>int main()
{processbar();return 0;
}
3. processbar.c程序:
#include "processbar.h"
#include<string.h>
#include<unistd.h>const char* lable="|/-\\-";//這個加載的那個圈圈void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar));//初始化數組int cnt=0;while(cnt<=100){printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%5]);fflush(stdout);//強制刷新輸出緩沖區,確保進度條實時顯示bar[cnt++]=STYLE;usleep(100000);}printf("\n");
}
運行結果:?
小細節:
3.1. const char* b="|/-\\-";
定義了一個字符數組,包含幾個特殊字符,用于實現進度條前的 "旋轉加載" 效果(類似轉圈動畫)。這里的\\是轉義字符,表示一個反斜杠。
3.2. usleep
Linux 系統中用于暫停程序執行的函數,聲明在<unistd.h>頭文件中。
作用:
讓當前進程暫停指定的微秒數(1 微秒 = 1/1000000 秒)
3.3.?"[%-100s][%d%%][%c]\r"
- [%-100s]:左對齊的 100 字符寬度區域,顯示當前進度
- [%d%%]:顯示當前百分比,%%是轉義字符,表示%
- [%c]:顯示旋轉動畫字符
- \r:回車符,使光標回到行首,實現覆蓋輸出效果
4. makefile文件:
processbar:processbar.c test.c@gcc -o $@ $^
.PHONY: clean
clean:rm -f processbar
小細節:
?4.1processbar:processbar.c test.c
- 這是一個規則,processbar 是目標(可執行文件),processbar.c 和 test.c 是依賴文件
- 意思是:要生成 processbar 這個可執行文件,需要先有 processbar.c 和 test.c 這兩個源文件
- 多依賴文件之間用空格隔開
4.2@gcc -o $@ $^
- @表示執行命令時不在終端顯示該命令本身
- $@ → 目標文件名(要生成的結果)
- $^ → 所有依賴的源文件(用來生成目標的原材料)
4.3.PHONY:?? ?clean
- 聲明 clean 是一個偽目標(不是實際的文件)
- 作用是避免當前目錄下有同名文件 clean 時,導致該規則失效
(3)進度條的調式和優化
上述進度條存在一定缺陷:
1.功能封裝不合理,復用性差
- 進度條邏輯與控制強耦合:用戶無法根據實際場景(如下載、解壓等不同任務)控制進度條的更新時機和速度,只能被動執行函數內置的固定節奏。
- 無法適配實際業務場景:實際開發中,進度條的更新應與任務進度(如下載字節數、處理數據量)綁定,而初始代碼的進度完全由函數內部的 cnt 自增控制,與真實任務進度脫節,用戶無法通過外部參數傳遞實際進度。
2. 擴展性差,難以二次使用
- 單次使用限制:初始代碼中,processbar() 函數的進度條狀態(數組 bar)是局部變量,每次調用函數都會重新初始化并執行一次完整的進度條動畫。這導致它只能正確運行一次,若用戶需要在程序中多次顯示進度條(如多個任務依次執行),必須重新調用函數,但函數內部邏輯固定,無法重置或復用狀態。
3. 用戶交互設計不合理
- 參數缺失,用戶體驗差:用戶使用時,只能調用 processbar() 函數執行一個固定的進度動畫,無法傳遞實際任務的總進度(如 “總大小 1000MB,當前下載 500MB”),導致進度條與真實任務無關,僅為一個 “虛假動畫”。
- 狀態不透明:用戶無法知道進度條當前的實際進度(如百分比),只能被動等待動畫結束,缺乏對任務進度的感知。
4. 細節實現的局限性
- 局部變量導致狀態無法延續:進度條的字符數組 bar是 processbar() 函數的局部變量,每次函數調用都會重新初始化,因此無法在多次調用中保留進度狀態,導致無法實現 “分階段更新進度”(如每次調用更新 10%)。
為了解決這個問題我將代碼進行了完善,我想要解決上面問題和形成如下進度條:
processbar.h
#pragma once
#include <stdio.h>#define NUM 102
#define TOP 100
#define STYLE '='
#define end '>'extern void processbar(int rate);
extern void Initbar();
processbar.c
#include "processbar.h"
#include<string.h>
#include<unistd.h>const char* lable="|/-\\-";
char bar[NUM];#define GREEN "\033[0;32;32m"
#define END "\033[m"void processbar(int rate)
{if(rate<0||rate>100)return;int len=strlen(lable);printf(GREEN"[%-100s]"END"[%d%%][%c]\r",bar,rate,lable[rate%len]);fflush(stdout);bar[rate++]=STYLE;if(rate<100)bar[rate]=end;
}void Initbar()
{//清空為'\0',避免首次使用時的隨機亂碼memset(bar,'\0',sizeof(bar));
}
小細節:
1.void Initbar()函數
用來解決擴展性差,難以二次使用問題,在調用進度條之前先調用這個函數。此時在processbar.h中也得進行聲明。
2.void processbar(int rate)
接收 rate 參數(0~100 的百分比),進度由外部傳入,調用者可根據實際場景(如下載了 30%)靈活控制顯示內容。
3.全局數組 char bar[NUM]
配合 Initbar() 初始化函數。數組生命周期與程序一致,進度狀態可跨多次 processbar() 調用累積,支持多次任務(例如連續下載多個文件時,每次調用 Initbar() 重置即可)。
4.#define GREEN "\033[0;32;32m"和#define END "\033[m"
ANSI 顏色控制宏:設置綠色文本,END用于恢復默認顏色
這些是博主搜的顏色替換,大家可以自己選然后改變
ANSI 顏色控制碼 - 前景色
#define FG_BLACK ? "\033[30m" ? // 黑色
#define FG_RED ? ? "\033[31m" ? // 紅色
#define FG_GREEN ? "\033[32m" ? // 綠色
#define FG_YELLOW ?"\033[33m" ? // 黃色
#define FG_BLUE ? ?"\033[34m" ? // 藍色
#define FG_MAGENTA "\033[35m" ? // 洋紅色
#define FG_CYAN ? ?"\033[36m" ? // 青色
#define FG_WHITE ? "\033[37m" ? // 白色ANSI 顏色控制碼 - 背景色
#define BG_BLACK ? "\033[40m" ? // 黑色背景
#define BG_RED ? ? "\033[41m" ? // 紅色背景
#define BG_GREEN ? "\033[42m" ? // 綠色背景
#define BG_YELLOW ?"\033[43m" ? // 黃色背景
#define BG_BLUE ? ?"\033[44m" ? // 藍色背景
#define BG_MAGENTA "\033[45m" ? // 洋紅色背景
#define BG_CYAN ? ?"\033[46m" ? // 青色背景
#define BG_WHITE ? "\033[47m" ? // 白色背景
test.c
#include "processbar.h"
#include <unistd.h>typedef void (*callback_t)(int);void downLoad(callback_t cb)
{int total=1000;int curr=0;while(curr<=total){usleep(100000);int rate=curr*100/total;cb(rate);curr+=10;}printf("\n");
}int main()
{Initbar();downLoad(processbar);return 0;
}
小細節:
1. typedef void (*callback_t)(int);
是 C 語言中定義函數指針類型的語法
作用:給一個 “參數為 int、返回值為 void 的函數指針” 起一個別名 callback_t,簡化函數指針的使用
具體解釋:
void (*)(int):這是一個函數指針的 “原型”,表示:
- 指向的函數返回值為 void(無返回值)。
- 指向的函數接收一個 int 類型的參數。
callback_t:這是我們給這個函數指針類型起的別名。
運行結果:
以上就是進度條的知識點了,后續的完善工作我們將留待日后進行。希望這些知識能為你帶來幫助!如果覺得內容實用,歡迎點贊支持~ 若發現任何問題或有改進建議,也請隨時與我交流。感謝你的閱讀!?????