Linux程序->進度條

進度條最終效果:

目錄

進度條最終效果:

一:兩個須知

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重置樣式,使進度條、百分比和光標按進度變色(如紅→黃→綠),所有顏色變化均由終端實時解析實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93081.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93081.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93081.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python爬蟲實戰:研究tldextract庫相關技術構建新聞網站域名分析爬蟲系統

1. 引言 網絡爬蟲作為一種自動獲取互聯網信息的技術,在數據挖掘、信息檢索、輿情分析等領域有著廣泛的應用。Python 因其豐富的庫和簡潔的語法,成為了開發爬蟲的首選語言。tldextract 是 Python 中一個強大的域名解析庫,能夠準確地從 URL 中提取頂級域名、二級域名等關鍵信…

【算法-華為機試-火星基地改造】

基地改造題目描述目標輸入輸出代碼實現題目描述 在2XXX年&#xff0c;人們發現了一塊火星地區&#xff0c;這里看起來很適合建設新家園。但問題是&#xff0c;我們不能一次性將這片地區的空氣變得適合人類居住&#xff0c;得分步驟來。 把這片火星地區想象成一個巨大的棋盤。棋…

C++入門自學Day1-- C語言的宏函數和C++內聯函數

一、函數調用開銷函數調用會涉及&#xff1a;參數壓棧&#xff08;或寄存器傳參&#xff09;跳轉到函數體返回值處理棧幀銷毀這個過程對小函數來說可能非常浪費&#xff0c;因此&#xff0c;宏函數和內聯函數的目的就是避免“函數調用的開銷”&#xff0c;通過代碼展開&#xf…

Pytorch混合精度訓練最佳實踐

混合精度訓練&#xff08;Mixed Precision Training&#xff09;是一種通過結合單精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;計算來加速訓練、減少顯存占用的技術。它在保持模型精度的同時&#xff0c;通常能帶來 2-3 倍的訓練速度提升&#x…

Qt C++動態庫SDK在Visual Studio 2022使用(C++/C#版本)

01 將C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平臺下 SDK 的集成。2.1 Visual Studio 平臺下 C/C環境配置及集成到 IDE 中xxx.lib 和 xxx.dll 適合在 Windows 操作系統平臺使用&#xff0c;這里以 VS2022 環境為例。2.1.1 C/C 工程環境配置與集成1、C# SDK 接口…

大語言模型 LLM 通過 Excel 知識庫 增強日志分析,根因分析能力的技術方案(2):LangChain + LlamaIndex 實現

文章大綱 1 技術原理總覽 2 詳細實現步驟(含代碼) 2.1 環境準備 2.2 Excel → LlamaIndex 節點 2.3 構建向量索引(FAISS 本地) 2.4 Google Cloud 向量檢索(可選替換 FAISS) 2.5 LangChain 問答鏈 A. RAG 模式(向量檢索 + LLM 生成) B. SQL 模式(無 RAG,直接查表) 2.…

提升ARM Cortex-M系統性能的關鍵技術:TCM技術解析與實戰指南

文章目錄引言一、TCM基礎架構與工作原理1.1 TCM的物理特性1.2 與緩存機制的對比1.3 ARM Cortex-M系列對TCM的支持二、TCM的典型應用場景2.1 實時中斷處理2.2 低功耗模式下的待機代碼2.3 高性能算法執行2.4 系統初始化階段的關鍵代碼三、實戰指南&#xff1a;在STM32H7上配置和優…

大數據之路:阿里巴巴大數據實踐——大數據領域建模綜述

為什么需要數據建模 核心痛點 數據冗余&#xff1a;不同業務重復存儲相同數據&#xff08;如用戶基礎信息&#xff09;&#xff0c;導致存儲成本激增。計算資源浪費&#xff1a;未經聚合的明細數據直接參與計算&#xff08;如全表掃描&#xff09;&#xff0c;消耗大量CPU/內存…

實戰演練1:實戰演練之命名實體識別

實戰演練1:實戰演練之命名實體識別 命名實體識別簡介 代碼 命名實體識別簡介 什么是命名實體識別任務 命名實體識別(Named Entity Recognition,簡稱NER)是指識別文本中具有特定意義的實體,主要包括人名、地名、機構名、專有名詞等。通常包括兩部分: (1)實體邊界識別。(2)確定…

數據結構基礎內容(第七篇:堆、哈夫曼樹)

# 堆 Heap 優先隊列(Priority Queue) 結構性:用 *數組* 表示的完全二叉樹; 有序性:任一結點的關鍵字是其子樹所有結點的最大值(或最小值) * “最大堆(MaxHeap)”,也稱“大頂堆”:最大值 * “最小堆(MinHeap)”,也稱“小頂堆” :最小值 主要操作有: ? MaxHeap Create( i…

CS231n-2017 Lecture7訓練神經網絡(二)筆記

本節主要是神經網絡的動態部分&#xff0c;也就是神經網絡學習參數和搜索最優超參數的過程梯度檢查&#xff1a;進行梯度檢查&#xff0c;就是簡單地把解析梯度與數值計算梯度進行比較&#xff0c;防止反向傳播的邏輯出錯&#xff0c;僅在調試過程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未顯示項目根目錄問題

問題&#xff1a; 在IDEA中編寫代碼時&#xff0c;發現左上方只顯示項目的子模塊&#xff0c;未顯示根項目名稱。 如圖所示&#xff0c;未顯示子模塊的根項目&#xff1a;問題分析 頂層根目錄未被識別為項目根目錄&#xff0c;需要手動添加識別。 問題解決 進入File – Project…

OpenCV 圖像變換全解析:從鏡像翻轉到仿射變換的實踐指南

前言處理圖像時&#xff0c;翻轉、旋轉、平移等操作很常用。OpenCV 提供了簡單的方法實現這些變換&#xff0c;本文帶你快速學會用它做圖像翻轉和仿射變換。1 圖像翻轉(圖像鏡像旋轉)在OpenCV中&#xff0c;圖片的鏡像旋轉是以圖像的中心為原點進行鏡像翻轉的。cv2.flip(img,fl…

【運維】Linux運維命令記錄

重置root密碼使用命令重新設置一下root賬戶的密碼 passwd root根據提示設置一下密碼&#xff0c;然后使用sudo -i 時輸入密碼就可以切換到root賬戶了ssh登陸以后&#xff0c;要用sudo -i命令給用戶提權&#xff0c;提到超級管理員&#xff0c;然后輸入密碼才有用

PandasAI連接LLM進行智能數據分析

1. 引言 Pandas是一個數據分析開源組件庫&#xff0c;提供了高性能、易用的數據結構和數據分析工具。它的核心的功能是其DataFrame對象&#xff0c;這是一個帶有行和列標簽的二維表格數據結構&#xff0c;支持缺失數據處理、時間序列功能、靈活的數據輸入輸出方法、數據對齊和…

Spring之【Bean的生命周期】

目錄 1、生成BeanDefinition BeanDefinitionRegistry接口 DefaultListableBeanFactory實現類 2、合并BeanDefnition AbstractBeanFactory類 3、BeanFactoryPostProcessor的方法回調 AbstractApplicationContext類 PostProcessorRegistrationDelegate類 4、BeanPostPro…

搜狐新聞直播間適配HarmonyOs實現點贊動畫

01背景介紹隨著新聞客戶端鴻蒙單框架系統適配工作的推進&#xff0c;從原來的基礎功能到現在已經適配全功能的85%以上。與此同時&#xff0c;我們也在持續深入挖掘鴻蒙系統的特性&#xff0c;以提升整體應用的質量與用戶體驗。在這一過程中&#xff0c;動畫作為增強交互與視覺體…

83、設置有人DTU設備USR-M100采集傳感器數據,然后上傳阿里云服務

基本思想:設置M100 采集傳感器數據 一、首先將DTU設備USR-M100連接路由器上,然后使用python代碼搜索同一局域網設備, import platform import sys import os import time import threadinglive_ip = 0def get_os():os = platform.system()if os == "Windows":re…

P1019 [NOIP 2000 提高組] 單詞接龍

題目描述單詞接龍是一個與我們經常玩的成語接龍相類似的游戲&#xff0c;現在我們已知一組單詞&#xff0c;且給定一個開頭的字母&#xff0c;要求出以這個字母開頭的最長的“龍”&#xff08;每個單詞都最多在“龍”中出現兩次&#xff09;&#xff0c;在兩個單詞相連時&#…

詳解力扣高頻SQL50題之1633. 各賽事的用戶注冊率【簡單】

傳送門&#xff1a;1633. 各賽事的用戶注冊率 題目 用戶表&#xff1a; Users -------------------- | Column Name | Type | -------------------- | user_id | int | | user_name | varchar | -------------------- user_id 是該表的主鍵(具有唯一值的列)。 該表中的每行包…