進度條最終效果:
目錄
進度條最終效果:
一:兩個須知
1:緩沖區
①:C語言自帶緩沖區
②:緩沖區的刷新策略
2:回車和換行的區別
二:倒計時程序
三:入門板進度條的實現
四:升級版進度條的實現
1:預留中括號
2:箭頭
3:動態百分比
4:旋轉光標
五:模擬下載場景
1:對進度條的封裝
2:下載程序的編寫
3:串聯兩個函數:
a:調用函數
b:函數回調
六:低速下載場景代碼的問題
七:高速下載進度條代碼
八:總代碼:
①:不上色版本
②:上色版本
一:兩個須知
實現進度條之前,我們要先理解兩個點
1:緩沖區
例子1:
#include<stdio.h>
#include<unistd.h>int main()
{printf("you can see me!\n");sleep(3);return 0;
}
我們預期是先打印出"you can see me!"然后換行,3秒后再顯式提示符:?
運行結果:
解釋:與我們預期一致
例子2:
#include<stdio.h>
#include<unistd.h>int main()
{printf("you can see me!");sleep(3);return 0;
}
我們預期是先打印出"you can see me!",3秒后再同一行顯式提示符
運行結果如下:
解釋:與我們預期不符,它是3秒后,把"you can see me!"和提示符在一行打印出來
首先我們能確定的是,打印語句無論是哪一種,一定都是第一句就被執行的了,所以肯定是某個東西影響到了打印語句的效果是先顯式到屏幕上,還是后顯示到屏幕上罷了,而我們的區別僅僅只有換行'\n',所以緩沖區及其刷新策略如下:
①:C語言自帶緩沖區
在C語言中,每個文件都有自己的緩沖區,代碼本質也是在一個文件里面編寫,所以每個代碼,比如上面的test.c就有自己的緩沖區,緩沖區就像一個裝快遞的車子,裝滿了才發車,這樣效率更高,本質就是用空間換時間!
②:緩沖區的刷新策略
C語言緩沖區的三種刷新策略:
①:無刷新 沒有緩沖區
②:行刷新->遇見\n刷新(顯示器)
③:全刷新->緩沖區滿了才刷新(普通文件)
兩種特殊情況:
①:強制刷新(fflush()函數)
②:進程退出的時候(進度條不加\n顯示不出來 程序退出顯示出來的原因)
刷新要結合普通的刷新策略和特殊情況來判斷,現在我們再來理解一下例子1和2的現象原因
例子1:因為我們是對顯示器寫入,所以其遇見\n才會刷新,這就是為什么在sleep函數之前,就已經把"you can see me!"打印了出來,因為緩沖區滿足被刷新的條件
例子2:雖然我們是對顯示器寫入,但是我們沒有\n,所以我們要么使用特殊情況中的fflush函數,或者是進程退出的時候,這就是為什么進程退出的時候,"you can see me!"和提示符一起打印了
建議看看博主的這篇文章:Linux->基礎IO-CSDN博客,你會對緩沖區和刷新策略理解更透徹
2:回車和換行的區別
我們平時鍵盤上的回車,其實是有兩個功能,換行+移動到新行首,這就是為什么按一下就能到下一行的開始位置:
所以我們代碼中的'\n',就是鍵盤上的回車(換行+移動到新行首),這就是為什么我們連續的printf包含'\n'會呈現清晰的打印結果
我們的進度條很明顯是在同一行不同的打印,所以'\n'是不行的!要用'\r'!
但是呢,\r 需要一些例子來說明其的特性,才能為我們之后的進度條實現打好基礎,所以,現在實現一個倒計時程序的代碼中體會\r的特性
二:倒計時程序
例子1:
期望是實現一個建議的10秒倒數到1秒的效果
#include<stdio.h>
#include<unistd.h>int main()
{int count = 9;while(count){printf("%d\r",count);count--;sleep(1);}return 0;
}
運行效果:
解釋:什么都沒打印,最后打印了提示符?!因為我們知道\r 只是把光標移動到同一行的起始為止,所以打印的結果9會被8封蓋,8會被7覆蓋.....最后提示符會覆蓋掉最后一次的1,然后程序結束時,緩沖區刷新,刷新除了提示符!
本質就是我們向顯示屏打印,沒有換行,只能緩沖區滿了才刷新,而這么點內容根本不會滿,所以最后程序退出,強制刷新了緩沖區!所以\r一定要配合強制刷新函數fflush使用,才能起到效果!
修改后的代碼和效果如下:
#include<stdio.h>
#include<unistd.h>int main()
{int count = 9;while(count){printf("%d\r",count);fflush(stdout);count--;sleep(1);}//printf("\n");return 0;
}
運行效果:
?
解釋:雖然達到了效果,但是最后一次不應該被提示符覆蓋,所以我們要在代碼循環的外面加上打印\n(將上面代碼的注釋放開即可),如下效果
例子2:
唯一的區別就是count從10開始倒數
#include<stdio.h>
#include<unistd.h>int main()
{int count = 10;while(count){printf("%d\r",count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}
運行結果:
解釋:為什么是10到90到80....
因為9去覆蓋10,只能覆蓋到第一個字符,以此類推,就產生了這種效果!所以我們使用\r要預留空間,也就是設置為%-2d即可!
修改后的代碼和效果如下:
#include<stdio.h>
#include<unistd.h>int main()
{int count = 10;while(count){printf("%-2d\r",count);fflush(stdout);count--;sleep(1);}printf("\n");return 0;
}
運行效果:?
最后我們實現出了一個倒計時程序,也總結出了使用\r的三條性質:
總結:
1:\r之后一定要緊接fflush
2:最后一次打印完后 要\n 避免提示符覆蓋打印內容
3:\r 對%d的預留很重要務必牢記這三點 其會貫徹整個進度條代碼
三:入門板進度條的實現
所以我們就可以不斷的'\r'的方式去打印一個遞增的字符串,不就能夠實現進度條了嗎?該字符串的字符類型我們可以自己設置,你想要一串*號,一串=號都可以
所以我們需要一個101大小的字符數組,一開始全都是'\0',我們打印一次,就往該數組內部增加一個字符=,即可!
Q:為什么數組是101大小,不是100?
A:因為預留最后那一個位置給'\0',才能安全的進行最后一次的打印!
代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101//定義數組的大小
#define S '='//定義進度條的字符類型int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));//把數組全設置為'\0'int i = 0;while(i<=100){printf("%s\r",bar);fflush(stdout);//強制刷新buffer[i++] = S;usleep(100000);//0.1秒更新數組且打印一次,可以比sleep函數更快} printf("\n");//避免提示符覆蓋進度條return 0;
}
效果如下:
解釋:=號剛好有100個,符合預期
Q1:為什么是“連續增加的等號”?而不是把上一次z字符數組清空然后再打印新的字符數組?
A1:因為\r 不會清除之前的內容,只是把移動光標到行首,然后進行下一次的字符數組打印,而字符數組一次比一次多,所以就會呈現出連續遞增的感覺?
為什么稱這個代碼是入門級呢,因為其有缺陷:
Q2:數組的最終狀態是怎么樣的?
A2:整個數組全是(buffer[0]
?到?[100]
)全是?'='(
共 101 個),我們給最后一個位置預留存放\0,根本沒有存放,因為當i為99的時候,此時進來打印后,buffer[99]='=',然后i變成了100,再次進入循環打印數組,此時的數組[0]~[99]都是'=',共100個,所以上面GIF圖是100個,此時打印后,buffer[100] =‘=',也就是第101個元素也被變成了'=',此時i++為101,跳出循環!
驗證:在最外面再打印一次數組:
符合我們的分析,數組的確是有101個'='?
Q3:既然 bar 數組的所有元素(bar[0]~bar[100])都被賦值為 '=',沒有 '\0',為什么最外面 printf("%s", bar) 仍然能正確停止,并且每次都能打印 101 個 '='?按照 C 字符串的規則,printf 應該一直往后讀,直到隨機遇到 '\0' 才停止才對啊?
A3:這在VS下運行的確是隨機的,但是在Linux下就不是了,因為涉及到了Linux的內存布局,可能是bar
?的相鄰內存可能是?'\0'等原因,不必在意
所以入門級別的代碼,為了安全,應該一開始設置MAX為102才對:
如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 102//定義數組的大小
#define S '='//定義進度條的字符類型int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));//把數組全設置為'\0'int i = 0;while(i<=100){printf("%s\r",bar);fflush(stdout);//強制刷新buffer[i++] = S;usleep(100000);//0.1秒更新數組且打印一次,可以比sleep函數更快} printf("\n");//避免提示符覆蓋進度條return 0;
}
如果你非要保持101 ,修改一下while的條件和內部執行順序也行:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define type '='int main()
{char bar[MAX];memset(bar,'\0',sizeof(bar));int i = 0;while(i<=99){bar[i++] = type;printf("%s\r",bar);fflush(stdout);usleep(100000);} printf("\n");printf("%s\n",bar);return 0;
}
注:博主在下面的實現中 會全程使用這個類型的代碼?!
最后的這兩種寫法,都可以讓安全得打印100個'='
四:升級版進度條的實現
有入門進度條的效果,我們能看出缺了一些細節:?
①:預留中括號
②:箭頭的設置
③:動態百分比
④:旋轉光標
1:預留中括號
也就是說,進度條應該是被一個中括號括起來的,而且中括號,一開始就再最開始和最末尾:
代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define type '-'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;while(i<=99){buffer[i++] = type;printf("[%-100s]\r",buffer);//預留中括號 %-100s的形式fflush(stdout);usleep(100000);} printf("\n");return 0;
}
運行效果:
2:箭頭
也就是說,不要只是一條線,在線的最前面加一個箭頭,更有指向性一點,所以我們就要在每次讓i下標對應的元素變成'-'的同時,還要讓i++對應的元素變成'>',代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body; buffer[i] = head;printf("[%-100s]\r",buffer);fflush(stdout);usleep(100000);} printf("\n");return 0;
}
效果如下:
解釋:乍一看好像沒什么問題,但是其實如果你數一下,會發現'-'和'>'的數目為101,這意味著我們只有101個空間,卻全被使用了,沒有給'\0'預留,無法保證被安全得打印;且進度條加載滿了之后,箭頭應該消失才對,綜合這兩點來看,我們不用改變數組的大小,而是讓i=99的時候呢,我們讓下標99對應的第100個元素'>'變成-即可(buffer[i++] = body;?),而不在進行i++后,讓下標100對應的第101個元素再變成>"(buffer[i] = head;),所以我們在賦值'>'處加一個判斷即可如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s]\r",buffer);fflush(stdout);usleep(100000);} printf("\n");return 0;
}
運行效果:
解釋:和最上面的原來效果相比,不僅的確變短了1個字符,總數為100,其次最后加載滿的時候,箭頭也消失了
?
3:動態百分比
用一個動態百分比來反映進度條的進展,這是進度條必不可少的一部分,但是非常簡單,只需要把i打印出來不就可以了嗎?代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%]\r",buffer,i);fflush(stdout);usleep(100000);} printf("\n");return 0;
}
運行效果:
Q1:i不是最大只有99嗎?為什么最后打印出來能到100%?
A1:因為i進入循環,對i下標賦值body后會后置++的
Q2:[%d%%]是什么意思??
A2:在格式化字符串中,%
?是特殊符號(如?%d
、%f
),如果想直接輸出?%
,必須用?%%
?轉義。
4:旋轉光標
日常中的進度條的旁邊都有一個圓圈一直再轉,或者一個豎線在順時針旋轉,這樣即顯得不那么單調,當我們進度條由于網速等原因卡住的時候,正在旋轉的旋轉光標也會告訴我們進度條只是由于網速被卡住了,而不是程序卡了
所以我選擇使用?|
,?/
,?-
,?\
?四個符號循環,模擬順時針旋轉,實現旋轉光標,代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
const char *rotate="|/-\\";//旋轉光標數組
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int n = strlen(rotate);//旋轉光標數組的個數n 用來后面打印的時候取模int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%][%c]\r",buffer,i,rotate[i%n]);fflush(stdout);usleep(100000);} printf("\n");return 0;
}
運行效果:
解釋:打印的時候,取旋轉光標數組的下標,正好可以用一直改變的i來模上旋轉光標數組的元素個數n,這樣就能一直取到[0,3]?;注意,"|-/\\"中的\\依舊是轉義!
其他光標推薦:
用"..oo00OO00oo.."
效果如下:
所以,升級版進度條的最終代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 101
const char *rotate="..oo00OO00oo..";
#define body '-'
#define head '>'int main()
{char buffer[MAX];memset(buffer,'\0',sizeof(buffer));int n = strlen(rotate);//旋轉光標數組的個數n 用來后面打印的時候取模int i = 0;buffer[0]=head;while(i<=99){buffer[i++] = body;if(i < 99) buffer[i] = head;printf("[%-100s][%d%%][%c]\r",buffer,i,rotate[i%n]);fflush(stdout);usleep(100000);} printf("\n");return 0;
}
五:模擬下載場景
但是,進度條怎么可能是一個獨立的程序,其往往是用來反映其他東西的,比如下載的進度?所以,下面手動得模擬一個反映下載進度的場景:
我們用1024*1024*1024? 也就是1GB來表示我們需要下載的總量,然后我們每次(間隔時間自己設定)下載一個隨機數,其范圍在0kb ~ 1mb之間,然后當總量被減到零的時候,我們就下載完成!
1:對進度條的封裝
所以我們要對我們之前寫的代碼進行管理一下,我們把之前的程序封裝成一個函數,這樣我們再寫一個下載函數,然后就可以在main中完成函數的調用了
封裝如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MAX 101
const char *rotate = "..oo00OO00oo..";
#define body '-'
#define head '>'void processbar()
{char buffer[MAX];memset(buffer, '\0', sizeof(buffer));int n = strlen(rotate); // 旋轉光標數組的個數n 用來后面打印的時候取模int i = 0;buffer[0] = head;while (i <= 99){buffer[i++] = body;if (i < 99)buffer[i] = head;printf("[%-100s][%d%%][%c]\r", buffer, i, rotate[i % n]);fflush(stdout);usleep(100000);}printf("\n");
}int main()
{rocessbar();return 0;
}
2:下載程序的編寫
首先我們定義一個下載的總量為1個G:
#define FILESIZE 1024*1024*1024
以及下載函數的框架:
void download()
{}
①:種隨機數種子
下載的速率不是固定的,所以用隨機數來代替正好可以模擬:
void download()
{srand(time(NULL)*11);//乘11只是為了讓結果更隨機int one = rand()%(1024*1024);//讓每次下載的速度在0kb~1mb之間
}
?
②:讓每次下載循環起來,得到余量
//模擬一種場景
void download()
{srand(time(NULL)*11);int total = FILESIZE;//需要下載的總大小while(total){usleep(5000); //下載動作int one = rand()%(1024*1024);//一次下載的大小total -= one;//剩余的大小if(total < 0) total = 0;//不一定剛好減到0 所以當其<0 直接讓其為0}
}
解釋:每次下載的大小one,用total減等one,total就是動態的余量,但是余量有可能最后一次減到負數,所以當其是負數的時候,我們將其賦值0,方便退出循環
③:得到下載的進度
下載的進度,不就是已經下載的大小比上總大小嗎;而為什么要求下載的進度呢?因為我們要將這個數傳給進度條程序,讓其能夠更具速度打印進度條,所以必然的我們在后面還要對我們的進度條函數進行改造的,下載進度rate如下:
void download()
{srand(time(NULL)*11);int total = FILESIZE;while(total){usleep(5000); //下載動作int one = rand()%(1024*1024);total -= one;if(total < 0) total = 0;// 當前的進度是多少?int download = FILESIZE - total;double rate = (download*1.0/(FILESIZE))*100; // 0 23.4 35.6, 56.6printf("download: %f\n", rate);}
}
解釋:
?double rate = (download*1.0/(FILESIZE))*100;?
?我們的速度肯定是要double類型才真實一點,所以兩個整數相除,怎么獲得浮點數呢?download*1.0/FILESIZE即可,但是為什么要乘100呢?因為不乘就是0~1之間的double類型,而我我們平時生活中下載都是56.7%這種0~100之間的double類型,所以我們給其乘100
下面我們運行一下這個下載函數:
重點:要明白的是,我們的下載跨度最多0.1%,因為最大下載1mb,而總大小1個G,所以你單次下載最多也才占到總大小的%0.1,為什么我要這么寫,這意味著,我們的processbar函數就可以無腦的套用之前的部分代碼,比如無腦的將buffer數組進行遞增寫入字符,因為如果前一次是1.5 后一次是1.6,其實數組根本不用變,因為兩次作為下標取整之后都是1,只有當其加到二點幾的時候,才會取整得到2,才能改變數組,才會影響進度條的效果!所以即使下載速錄在擴大10被,也就波動到1%,依舊可以復用前面的代碼!
這樣寫的好處是,復用前面的代碼,壞處是,生活中的下載可能跨度遠遠大于了1%,所以在后面我們還要對總代碼升級一次,讓其可以應對跨度大的下載場景!
至此我們的下載函數就寫完了,下一步就是把下載函數和進度條函數關聯起來,有兩種常見方法:
3:串聯兩個函數:
a:調用函數
所以我們的processbar函數要改造一下,參數要接受每次循環產生的rate:
改造如下:
#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋轉光標數組的個數n 用來后面打印的時候取模// 該句不能放在全局 因為其要在編譯時運行if (rate <= 1.0) buffer[0] = head; // 第一次下載在1%以內 第一個符號就是箭頭printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}
解釋:我們的?processbar函數 中肯定不會自己循環了,而是當下載函數傳過來一個參數,則其在打印對飲的進度條,而我們不用擔心兩次傳過來的數據,波動較大,因為我們在上文說過,你傳過來的兩個double類型差值不會超過1,這就意味著,大部分代碼不用改變,依舊是遞增式的往buffer數組里面插入數據,但有幾個點需要說明一下:
①:
rotate[(count++) % n]
我們的旋轉光標的下標,不能再用i 了,因為沒i了,所以我們需要自建一個全局變量count,來作為其下標,其要++,才能讓光標動起來,當然你也可以用static修飾count寫進函數內,一樣的
②:
if (rate < 99)buffer[(int)rate + 1] = head;
因為作為數組buffer的下標需要整形,所以我們進行強轉為整形后再作為下標,而至于+1,是因為我們要把下一個字符變成箭頭'>';而當其取整后為99的時候,代表其在上行代碼已經將下標為99也就是對應的第100個元素變成'-'了,所以我們的箭頭不要了,即美觀又安全
③:
if (rate >= 100.0)printf("\n");
最后下載完成后,我們要打印一次換行,避免被提示符覆蓋
所以總代碼如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋轉光標數組的個數n 用來后面打印的時候取模// 該句不能放在全局 因為其要在編譯時運行if (rate <= 1.0)buffer[0] = head; // 第一次下載在1%以內 第一個符號就是箭頭printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}// 模擬一種場景
void download()
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下載動作int one = rand() % (1024 * 1024);total -= one;if (total < 0)total = 0;// 獲取當前下載比率(已經下載大小/總大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;processbar(rate);}
}int main()
{download();return 0;
}
運行效果:
b:函數回調
我們定義一個processbar函數指針的類型,然后讓download函數參數使用這個類型,就可以在download函數內部使用了,其實感覺沒什么必要哈哈哈,但還是講一下吧,代碼如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate); // 旋轉光標數組的個數n 用來后面打印的時候取模// 該句不能放在全局 因為其要在編譯時運行if (rate <= 1.0)buffer[0] = head; // 第一次下載在1%以內 第一個符號就是箭頭printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}typedef void((*pro_bar)(double));
// 模擬一種場景
void download(pro_bar pb)
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下載動作int one = rand() % (1024 * 1024);total -= one;if (total < 0)total = 0;// 獲取當前下載比率(已經下載大小/總大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;pb(rate);}
}int main()
{download(processbar);return 0;
}
六:低速下載場景代碼的問題
六作為拓展講解,想要了解可以看一下
上面的這個代碼代碼用進度條反映了一個偽下載的場景,但是其實不完美的!因為其對下載較快的場景會出錯!
例子:將download中的單次one的下載速度上限提高至原來的15倍,此時單次最高占到了1.5%,也就是意味著如果你上次是0.9,下次正好單次下載最快,達到了2.4,也就是第一個的取整下標是0,第二次的取整下標是2,這會導致下標1對應的元素沒被賦值為'-',所以我們覺得其應該是保留了原本的'\0',這會導致,哪里第一次被跳過了沒被賦值'-',其就應該停留在那個地方,那真的是這樣嗎?
首先我們先單獨使用download函數來驗證高速率下傳遞給processbar函數的(int)rate是否會跳躍:
所以我們把單詞下載one的速率提高20倍,然后打印所有的(int)rate查看是否會跳躍:
void download(pro_bar pb)
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000); // 下載動作int one = rand() % (1024 * 1024*15);total -= one;if (total < 0)total = 0;// 獲取當前下載比率(已經下載大小/總大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;printf("%d ",(int)rate);//pb(rate);}
}
打印結果:
解釋:隨便就能找到兩個下標被跳過了,所以按照我們的預想,4下標仍然為'\0',所以往后的每次打印遇到'\0'都會停止,也就是你的光標雖然在旋轉,百分比雖然在增加,但是你每次\r打印的應該都一樣!也就是我認為?buffer[4]
?應保留初始值?\0
(即空白),導致進度條在 4%
?位置中斷。
但是效果如下:
原因如下:
這與?VS Code 終端的渲染機制?和?字符串輸出的特性,不作深究,所以我們的想法是對的,但是受到了終端的影響,所以看起來的結果很奇怪
所以我們可以把buffer數組全部設置為空格字符' ',然后最后一次設置為'\0'(為了安全打印),這樣我們就可以看到斷裂的進度條了:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define MAX 101
#define body '-'
#define head '>'
#define FILESIZE 1024 * 1024 * 1024char buffer[MAX];
const char *rotate = "|-/\\";
int count = 0;void processbar(double rate)
{int n = strlen(rotate);if (rate <= 1.0)buffer[0] = head;printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);buffer[(int)(rate)] = body;if (rate < 99.0)buffer[(int)rate + 1] = head;if (rate >= 100.0)printf("\n");
}typedef void((*pro_bar)(double));
// 模擬一種場景
void download(pro_bar pb)
{// srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(100000); // 下載動作int one = rand() % (1024 * 1024 * 50);//50倍更好觀察結果total -= one;if (total < 0)total = 0;// 獲取當前下載比率(已經下載大小/總大小)int download = FILESIZE - total;double rate = (download * 1.0 / (FILESIZE)) * 100;pb(rate);}
}int main()
{memset(buffer, ' ', sizeof(buffer));//buffer數組全部設置為空格字符' 'buffer[101] = '\0';//最后一個設置為'\0' 安全打印download(processbar);return 0;
}
?
Q:進度條斷裂我理解了,但是為什么會有這么多的箭頭,而不是"---- ----? ----? ? ---->"這樣?
A:
由圖可知就能知道為什么箭頭這么多,本質就是跳躍會導致覆蓋箭頭失敗!
七:高速下載進度條代碼
其實非常簡單,我們在processbar函數中接收到一個下標,我們就把數組一開始到這個下標的內容全部置為'-',然后再把下標對應的內容置為'>'即可,其實一開始就可以講這個,為什么不講呢,本質是通過低級的代碼引出更多的問題,不然我們不懂為什么不斷裂,不知道可以用空格字符來展現斷裂,甚至有的人都不知道低速代碼的弊端,所以,講下還是有必要的!
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate) {int n = strlen(rotate);memset(buffer, body, (int)rate); // 直接填充'-'到當前下標buffer[(int)rate] = head; // 再把頭部換成箭頭printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);if (rate >= 100) printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download() {srand(time(NULL) ^ 1023);int total = FILESIZE;while (total) {usleep(10000);int one = rand() % (50 * 1024 * 1024); // 擴大隨機范圍(最大5MB/次,卻依舊不會斷裂)total -= one;if (total < 0) total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main() {download();return 0;
}
50倍:
15倍:
?
注:高速代碼可完全替代低速代碼,而不是僅僅只可用于高速下載!
八:總代碼:
我們對高速下載代碼,上個色吧~
①:不上色版本
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;void processbar(double rate)
{int n = strlen(rotate);memset(buffer, body, (int)rate); // 直接填充'-'到當前下標buffer[(int)rate] = head; // 再把頭部換成箭頭printf("[%-100s][%.1f%%][%c]\r", buffer, rate, rotate[(count++) % n]);fflush(stdout);if (rate >= 100)printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download()
{srand(time(NULL) ^ 1023);int total = FILESIZE;while (total){usleep(10000);int one = rand() % (50 * 1024 * 1024); // 擴大隨機范圍(最大5MB/次,卻依舊不會斷裂)total -= one;if (total < 0)total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main()
{download();return 0;
}
②:上色版本
讓所有元素(進度條 百分比 光標)都能根據進度改變顏色:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>#define MAX 101
char buffer[MAX] = {'\0'};
const char *rotate = "|-/\\";
#define body '-'
#define head '>'
int count = 0;// ANSI 顏色代碼
#define COLOR_RED "\033[31m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_GREEN "\033[32m"
#define COLOR_CYAN "\033[36m"
#define COLOR_RESET "\033[0m"void processbar(double rate) {int n = strlen(rotate);memset(buffer, body, (int)rate);buffer[(int)rate] = head;// 根據進度選擇顏色(紅→黃→綠)const char *color;if (rate < 30) color = COLOR_RED;else if (rate < 70) color = COLOR_YELLOW;else color = COLOR_GREEN;// 旋轉光標單獨用青色const char *spin_color = COLOR_CYAN;// 打印帶顏色的所有元素printf("[%s%-100s%s][%s%.1f%%%s][%s%c%s]\r", color, buffer, COLOR_RESET, // 進度條部分color, rate, COLOR_RESET, // 百分比部分spin_color, rotate[(count++) % n], COLOR_RESET); // 旋轉光標fflush(stdout);if (rate >= 100) printf("\n");
}#define FILESIZE (1024 * 1024 * 1024)void download() {srand(time(NULL) ^ 1023);int total = FILESIZE;while (total) {usleep(10000);int one = rand() % (10 * 1024 * 1024); // 單次最多下載10MBtotal -= one;if (total < 0) total = 0;double rate = ((FILESIZE - total) * 100.0) / FILESIZE;processbar(rate);}
}int main() {download();return 0;
}
效果:
上色原理:
通過插入ANSI轉義碼(如\033[31m
表示紅色)臨時修改終端文本顏色。代碼中動態選擇顏色后,用\033[0m
重置樣式,使進度條、百分比和光標按進度變色(如紅→黃→綠),所有顏色變化均由終端實時解析實現。