這篇文章我們一起來(lái)完成我們Linux中的第一個(gè)小程序——進(jìn)度條

1. 對(duì)回車(\r)和換行(\n)的理解
1.1 概念理解
在C語(yǔ)言中,字符可以分為可顯字符(printable characters)和控制字符(control characters)。
可顯字符是指可以在屏幕或打印輸出上顯示的字符,它們包括數(shù)字、字母、標(biāo)點(diǎn)符號(hào)、符號(hào)等??娠@字符可以直接被用戶看到,并且在文本處理、顯示和通信中起到重要作用。
控制字符是一類在計(jì)算機(jī)中具有特殊含義的字符,它們通常不可見或只能以特殊方式顯示。這些字符用于控制文本的格式、編輯和通信等方面。
這里我們要重點(diǎn)理解兩個(gè)控制字符——\n
和\r
\r
表示回車,即將光標(biāo)移動(dòng)到當(dāng)前行的起始位置\n
表示換行,即將光標(biāo)向下移動(dòng)一行
但是我們平時(shí)用的比如C語(yǔ)言打印的時(shí)候加一個(gè)\n換行
或者在編輯文本的時(shí)候敲enter
鍵
他不僅進(jìn)行了換行并且光標(biāo)也移到了起始位置。
但是其實(shí)這是兩個(gè)步驟,先移到下一行,再移動(dòng)到起始位置。
不過(guò)呢,在常見的計(jì)算機(jī)系統(tǒng)中,換行通常會(huì)伴隨回車操作。
1.2 測(cè)試
下面我們來(lái)測(cè)試幾個(gè)程序。
首先我來(lái)寫一個(gè)makefile,我們待會(huì)寫完代碼可以直接用:
然后我來(lái)寫一個(gè)test.c
那這里面我就用到了換行\(zhòng)n
那我來(lái)運(yùn)行看一下:
我們看到這里就成功打印出來(lái)了hello world,并且進(jìn)行了換行(且伴隨了回車)。
所以后面的命令提示符就打印到了下一行,并且在開頭位置。
然后我們把\n
換成\r
試一下:
再來(lái)make然后運(yùn)行
我們看到這次什么都沒打印
那為什么啥都沒打印呢?
那其實(shí)就是跟最后的
\r
有關(guān)系,printf打印的時(shí)候,前面的hello world都沒問(wèn)題,但是最后遇到\r(回車),就把光標(biāo)移到了最左邊起始位置。
所以后面打印命令提示符的時(shí)候就把hello world覆蓋掉了。
2. 緩沖區(qū)的理解
下面我們來(lái)理解一下緩沖區(qū)的概念
緩沖區(qū)(Buffer)是計(jì)算機(jī)系統(tǒng)中用于臨時(shí)存儲(chǔ)數(shù)據(jù)的一塊內(nèi)存區(qū)域。它通常用于處理輸入和輸出操作,以提高效率和性能。
緩沖區(qū)相當(dāng)于一個(gè)中間層,位于數(shù)據(jù)的來(lái)源和目的地之間。當(dāng)進(jìn)行輸入或輸出操作時(shí),數(shù)據(jù)先暫時(shí)存儲(chǔ)在緩沖區(qū)中,然后再批量地傳輸?shù)侥繕?biāo)位置或從源位置讀取出來(lái)。這樣可以減少對(duì)源位置或目標(biāo)位置的直接讀寫次數(shù),從而提高數(shù)據(jù)傳輸效率。
2.1 觀察現(xiàn)象
下面我們還是來(lái)觀察兩個(gè)程序
先看第一個(gè):
這里用了一個(gè)函數(shù)sleep
sleep() 函數(shù)用于在程序中暫停執(zhí)行一段時(shí)間,sleep() 函數(shù)的參數(shù)是以秒為單位的等待時(shí)間。它的作用是讓程序進(jìn)入休眠狀態(tài),停止執(zhí)行指定的時(shí)間間隔,然后再繼續(xù)執(zhí)行后續(xù)的代碼。
在Linux或UNIX系統(tǒng)中,可以包含 <unistd.h> 頭文件,使用 sleep() 函數(shù)。而在Windows系統(tǒng)中,可以包含 <windows.h> 頭文件,使用 Sleep() 函數(shù)。
然后我們觀察一下結(jié)果
我這里給的是截圖,這里如果大家自己測(cè)試可能會(huì)觀察的更好一點(diǎn)
我們看到,這里先打印了hello world,然后進(jìn)行休眠(因?yàn)槲覀兪褂昧藄leep)
休眠結(jié)束,就打印了新的命令行。
然后我們看第二個(gè):
跟上面的區(qū)別就是我把\n去掉了
然后我們?cè)賮?lái)運(yùn)行
這次我們會(huì)觀察到它是先休眠
休眠結(jié)束然后才打印hello world,并且新的命令行直接跟在hello world后面,因?yàn)槲覀儧]有換行。
那通過(guò)對(duì)比兩次程序的結(jié)果,我們能得出:
帶\n的時(shí)候是先打印hello world,后休眠;而不帶\n是先休眠,后打印hello world。
那這樣的話,不帶\n的時(shí)候,好像是先執(zhí)行了sleep函數(shù),然后才執(zhí)行printf去打印。
是這樣嗎?
當(dāng)然不是的,我們知道程序默認(rèn)是按照從上到下順序執(zhí)行的。
所以肯定是先執(zhí)行printf,再執(zhí)行sleep,毋庸置疑。
2.2 原因解釋
那為什么我們看到的是先休眠,后打印,兩個(gè)程序打印的時(shí)機(jī)為什么不一樣呢?
??,我們上面有提到緩沖區(qū)的概念:
緩沖區(qū)相當(dāng)于一個(gè)中間層,位于數(shù)據(jù)的來(lái)源和目的地之間。當(dāng)進(jìn)行輸入或輸出操作時(shí),數(shù)據(jù)先暫時(shí)存儲(chǔ)在緩沖區(qū)中,然后再批量地傳輸?shù)侥繕?biāo)位置或從源位置讀取出來(lái)。
也就是是,不管我們有沒有加\n,我們的hello world這個(gè)字符串都會(huì)被暫存到緩沖區(qū)里面。
那為什么兩個(gè)程序打印的時(shí)間不一樣呢?
原因其實(shí)是因?yàn)閮蓚€(gè)程序的緩沖區(qū)刷新的時(shí)機(jī)不同。
在大多數(shù)編程語(yǔ)言和操作系統(tǒng)中,緩沖區(qū)被用來(lái)暫時(shí)存儲(chǔ)要輸出或被讀取的數(shù)據(jù),直到達(dá)到一定條件后才會(huì)將其發(fā)送到目標(biāo)位置(如屏幕、文件、網(wǎng)絡(luò)等)。這個(gè)條件通常是緩沖區(qū)滿了、遇到換行符、或者主動(dòng)進(jìn)行緩沖區(qū)刷新的操作。
當(dāng)程序結(jié)束時(shí),通常會(huì)自動(dòng)刷新輸出緩沖區(qū)。這意味著在程序執(zhí)行完成后,輸出緩沖區(qū)中的所有數(shù)據(jù)將被寫入到相應(yīng)的輸出設(shè)備(如終端或控制臺(tái))并在屏幕上顯示出來(lái)。
所以我們可以認(rèn)為,遇到\n的時(shí)候就會(huì)觸發(fā)緩沖區(qū)刷新操作。
而程序結(jié)束也會(huì)刷新緩沖區(qū)。
那現(xiàn)在,我想大家就明白了,為什么上面兩個(gè)程序的結(jié)果有差異?
第一個(gè)程序我們加了\n,所以執(zhí)行printf時(shí)遇到\n就會(huì)刷新緩沖區(qū),那么hello world就直接顯示到了顯示器上。
所以是先打印,后休眠。
而第二個(gè)程序,沒有\(zhòng)n,我們也沒有手動(dòng)刷新緩沖區(qū),所以直到程序結(jié)束是刷新緩沖區(qū),hello world 才會(huì)顯示到顯示器上。
因此是先休眠,后打印。
那有了緩沖區(qū)的理解,我們?cè)賮?lái)看上面最開始演示的那個(gè)程序:
現(xiàn)在在hello world后面加一個(gè)\r。
我們運(yùn)行看看
休眠結(jié)束啥沒打印,新的命令提示符就出來(lái)了。
那這個(gè)我們上面其實(shí)解釋過(guò),因?yàn)閈r的緣故,使得光標(biāo)移到了最左邊起始位置,所以后面的命令提示符就把先打印出來(lái)的hello world覆蓋了。
那我現(xiàn)在修改一下:
fflush這個(gè)函數(shù)可以刷新緩沖區(qū)給,那這樣就相當(dāng)于我們提前刷新了一下緩沖區(qū),這樣休眠就在打印后面了,方便我們觀察。
此時(shí)我們?cè)龠\(yùn)行
這下我們就看清楚了,并不是啥也沒打印。
而是hello world打印之后,光標(biāo)回到了最左邊,然后后面打印的命令提示符就把hello world覆蓋掉了。
當(dāng)然如果把\r去掉,就不會(huì)被覆蓋了
3. 倒計(jì)時(shí)小程序
那基于上面講的內(nèi)容,我們一起來(lái)實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)小程序練練手:
怎么做呢?
大家看這樣寫是不是就行了
這里從9開始倒計(jì)時(shí),i從9到0,循環(huán)打印,\r保證每個(gè)數(shù)字打印之后都把光標(biāo)移到起始位置,fflush刷新緩沖區(qū),這樣使得每個(gè)數(shù)字可以分開顯示,每次循環(huán)i都可以刷新出來(lái),然后休眠1秒,顯示下一個(gè)數(shù)字。
我們運(yùn)行看一下
這個(gè)大家可以自己寫寫運(yùn)行一下,截圖看著不方便。
具體的效果就是從9開始,9、8、7、6、5、4、3、2、1、0一次交替顯示。
但是當(dāng)前這樣寫,最終0顯示完之后,這一行就被新的命令行覆蓋了。
所以我們可以加一個(gè)換行
這樣最后倒計(jì)時(shí)這一行就不會(huì)被覆蓋了。
但是呢,我們的程序還有一些問(wèn)題
我們剛才倒計(jì)時(shí)9到0都是一個(gè)數(shù),占一個(gè)位置,所以后面的剛好覆蓋前面的,那如果是從10開始呢?
效果就成這樣了。
因?yàn)楹竺娑际且晃粩?shù),只能覆蓋一個(gè)位置,后邊的0就一直顯示,不受影響。
實(shí)際上我們無(wú)論打印什么類型的數(shù)據(jù),顯示器上顯示的內(nèi)容都是一個(gè)個(gè)的字符,打印整數(shù)時(shí),它們也會(huì)以字符的形式顯示在屏幕上。計(jì)算機(jī)內(nèi)部使用二進(jìn)制表示整數(shù),但在顯示器上呈現(xiàn)給用戶時(shí),需要將其轉(zhuǎn)換為對(duì)應(yīng)的字符形式。
那怎么解決呢?也很簡(jiǎn)單:
我們指定域?qū)捑托辛恕?br>printf可以用格式控制串
"%md"
輸出域?qū)挒閙的十進(jìn)制整數(shù)(默認(rèn)左對(duì)齊,-m則右對(duì)齊)
然后我們?cè)賮?lái)運(yùn)行
就可以了。
4. 進(jìn)度條小程序
那我先來(lái)大致說(shuō)一下我們最后要實(shí)現(xiàn)的一個(gè)進(jìn)度條的樣式:
就是一個(gè)大的【】,里面預(yù)留出來(lái)100個(gè)字符的空間,我們填充#,當(dāng)然你也可以用其他的,1%就打印一個(gè)#,2%就兩個(gè),以此類推,后面可以顯示一下具體是百分之幾,隨著#增加不斷遞增直到100%。
其實(shí)它大致的思路和上面的倒計(jì)時(shí)是一樣的:
就是不斷的顯示并覆蓋之前的內(nèi)容。
那接下來(lái)我們就來(lái)實(shí)現(xiàn)一下。
我呢想給它寫成一個(gè)多文件的形式:
我先創(chuàng)建這樣3個(gè)文件。
先寫進(jìn)去這些內(nèi)容。
然后把Makefile也寫一下:
4.1 基本思路及實(shí)現(xiàn)
然后,我們來(lái)寫實(shí)現(xiàn)進(jìn)度條的函數(shù)process:
首先我們可以先開一個(gè)數(shù)組,把進(jìn)度條需要的100個(gè)字符的空間預(yù)留出來(lái)。
那我們打印的時(shí)候可以直接打印#組成的字符串,那字符串的話就要再給\0開一個(gè)空間。
然后我們可以給buf數(shù)組全部初始化為\0,這樣我們后續(xù)添加#就不用考慮\0的問(wèn)題了。
然后我們循環(huán)打印并不斷添加#就行了,當(dāng)然我們這里還應(yīng)該使用\r不斷的回車,使每一次新打印的覆蓋之前的,并且每次循環(huán)printf之后要使用fflush刷新緩沖區(qū),這樣才能每次循環(huán)都?jí)虼蛴〕鰜?lái)內(nèi)容,要不然程序結(jié)束之前一直留存在緩沖區(qū)。
我們運(yùn)行看看效果:
4.2 改進(jìn)及優(yōu)化
上面的實(shí)現(xiàn),根據(jù)實(shí)際的運(yùn)行效果我們可以發(fā)現(xiàn)兩個(gè)問(wèn)題:
首先第一個(gè)休眠時(shí)間設(shè)置成1秒有點(diǎn)長(zhǎng)了,這樣跑到100%需要100秒
所以我們可以選擇把sleep函數(shù)換成usleep。
sleep的參數(shù)是以秒為單位的,而usleep是以微秒為單位的。
我們可以設(shè)置成0.1秒休眠時(shí)間
運(yùn)行一下:
這次速度確實(shí)快了
但是
第二個(gè)問(wèn)題:進(jìn)度條這一行顯示完畢,新出現(xiàn)的命令行會(huì)把進(jìn)度條的一部分覆蓋掉。
怎么解決?
很簡(jiǎn)答,加個(gè)換行就行了
再看:
這次就沒事了。
當(dāng)然我可以加一個(gè)宏,這樣后面替換進(jìn)度條的樣式就很方便:
然后我們?cè)傩薷囊幌?,改成這種:
類似一個(gè)箭頭,改一下代碼:
看一下效果
但是這樣最后停下來(lái),還有一個(gè)箭頭,好像有點(diǎn)不好看。
向前推進(jìn)的時(shí)候顯示箭頭,100%的時(shí)候不顯示,我們?cè)賮?lái)修改一下
加一個(gè)判讀就行了
4.3 增加百分比顯示
那一般進(jìn)度條后面還有顯示百分比,我們也來(lái)加一個(gè):
運(yùn)行一下
4.4 增加旋轉(zhuǎn)光標(biāo)
然后我們?cè)賮?lái)在后面增加一個(gè)旋轉(zhuǎn)光標(biāo):
可以通過(guò)循環(huán)顯示這四個(gè)字符
| / - \
來(lái)模擬一個(gè)旋轉(zhuǎn)的過(guò)程(注意\
要用轉(zhuǎn)義字符\\
)
我們來(lái)運(yùn)行看看效果
就可以了。
4.5 給進(jìn)度條配色
在C語(yǔ)言中,可以使用ANSI轉(zhuǎn)義序列來(lái)輸出不同的顏色。ANSI轉(zhuǎn)義序列是一系列的字符組合,用于控制終端的文本樣式和顏色。
關(guān)于配色方案,網(wǎng)上可以找到很多相關(guān)的資料,大家有興趣可以按照自己的喜好去配色,我這里簡(jiǎn)單演示一下:
比如我配個(gè)紅色
來(lái)個(gè)綠色
還有背景顏色也可以設(shè)置:
試一下,來(lái)個(gè)青色
也可以同時(shí)設(shè)置字體顏色/前景顏色和背景顏色
我們來(lái)個(gè)字體紅色,背景青色文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-563934.html
大家可以按自己的喜好設(shè)置。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-563934.html
到了這里,關(guān)于【Linux系統(tǒng)編程】Linux第一個(gè)小程序——進(jìn)度條的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!