【Linux取经路】进度条小程序

文章目录

  • 一、预备知识
    • [1.1 回车换行](#1.1 回车换行)
    • [1.2 缓冲区](#1.2 缓冲区)
  • 二、倒计时
    • [2.1 注意事项](#2.1 注意事项)
  • 三、进度条
    • [3.1 源代码](#3.1 源代码)
    • [3.2 代码分析](#3.2 代码分析)
    • [3.2 实际使用场景](#3.2 实际使用场景)

一、预备知识

1.1 回车换行

一般意义上的回车换行是两个独立的独立的动作,而C语言中的\n则同时完成了回车和换行的工作。回车是将光标移动到当前行的做开始(最左侧),换行是将光标水平方向保持不变,竖直方向向下平移一行。C语言中可以通过转义字符\r实现回车。

我们电脑键盘上的EBTER按键则是同时实现了回车和换行的功能,当按下ENTER键,光标会去到下一行的最开始的位置。

1.2 缓冲区

📖先看一个现象

cpp 复制代码
#include <unistd.h>
 int main()                                     
{
     printf("Hello Linux!\n");                                                                                                                                                   
                                                                                                                                                                                           
     sleep(2);
     return 0;
}

这段代码很简单,现在屏幕上打印出Hello Linux!,接着调用sleep函数让程序休眠两秒。接下来,我们对上面的代码稍作修改,去掉\n再来试试。

cpp 复制代码
#include <unistd.h>
 int main()                                     
{
     printf("Hello Linux!");                                                                                                                                                   
                                                                                                                                                                                           
     sleep(2);
     return 0;
}

通过动图可以看到,在去掉/n后对代码编译运行,先是休眠了两秒,接着才在屏幕上打印出Hello Linux!,并且因为没有\n,所以打印完后没有换行,导致bash命令行就紧跟在打印结果的后面。

📖现象分析

很多小伙伴会根据上面的现象猜测,这段代码先执行了sleep休眠,再去执行printf打印,这样的猜测是错误的,因为任何一个C程序,都是严格按照代码的顺序去执行。既然这样的话,先执行printf,再执行sleep,那在休眠的两秒期间,printf的打印结果在哪里呢?由于最终Hello Linux!还是出现在我们的屏幕上,所以在这两秒期间,Hello Linux一定是被保存起来了,其实就是保存在缓冲区 中。缓冲区就是C语言维护的一段内存。默认当程序结束的时候才会将缓冲区中的内容刷新出来

📖如何强制刷新缓冲区

任何一个C程序运行的时候都会默认帮我们打开以下三个流:

  • stdin - - - - 标准输入流(键盘)
  • stdout - - - - 标准输出流(显示器)
  • stderr - - - - 标准错误(显示器)

Linux下一切介文件,这三个流都是FILE*的指针,所以任何一个C程序运行的时候,操作系统会帮我们打开以上三个文件。今天我们只需要关心stdout标准输出流即可。我们可以通过fflush函数来刷新缓冲区。

cpp 复制代码
#include <unistd.h>
 int main()                                     
{
     printf("Hello Linux!");                                                                                                                                                   
     fflush(stdout);//刷新缓冲区                                                                                                                                                                                   
     sleep(2);
     return 0;
}

通过运行结果可以看出,这一次虽然在打印的时候也没有加\n,但取先把Hello Linux!打印出来,然后再休眠两秒。

小Tips:通过上面的分析我们可以得出,刷新缓冲区主要有以下几种方法:

  • \n可以刷新缓冲区。
  • 程序结束也会刷新缓冲区。
  • fflush(stdout)可以手动刷新缓冲区。

二、倒计时

有了上面的知识储备,我们先来实现一个简单的倒计时练练手。

📖源代码

cpp 复制代码
#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.1 注意事项

📖回车、刷新缓冲区

由于倒计时,是用新数字去覆盖老数字,因此每打印一个数字后不能用\n进行换行,否则就会像下面这样:

这里的正确做法是,每打印一个数字后紧跟着打印一个\r回车,让光标回到这一行最开始的位置,这样新打印的数字就会去覆盖掉老的数字。但是\r不会去刷新缓冲区,因此在每打印完一个数字后,需要调用fflush(stdout)来刷新缓冲区。

📖格式化控制

这里我们需要知道,往显示器上打印整型10,本质上是打印了字符1和字符0,由于这两个字符是挨在一起的,我们看起来就像是整型10。因此打印10,会占用两个字符,而打印0~9只需要一个字符,所以\r回车之后去覆盖写,只会覆盖一个字符,对第二个字符0始终没有影响,因此我们需要用%-2d来控制,每次打印两个位宽的字符,-表示将这两个字符左对齐。如果不进行格式化控制,打印出来的结果将是下面这样:

三、进度条

3.1 源代码

📖processBar.h

cpp 复制代码
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='                                                                                                                                                                         
#define TOP 100
#define BODY '>'
extern void processbar();

📖processBar.c

cpp 复制代码
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";//旋转提示
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);//以微秒为单位进行休眠,想让进度条10秒跑完,因为一共会循环101次,所以每次循环大概就是休眠0.1秒,100毫秒,10000微秒
    }
    printf("\n");
}

📖效果演示

3.2 代码分析

📖进度条往右走的实现原理

进度条向右走动的原理就是,这一次比上一次多打印一点内容。因此我们可以定义一个字符数组bar,通过循环每次往字符数组里面追加字符,然后将这个字符数组打印出来,由于每次循环都会往数组里追加字符,所以就会导致下一次打印出来的内容比这一次的多,视觉上就感觉进度条在往右走。又因为进度条始终是在同一行往右走的,所以每打印完一次要用\r,让光标回到当前行的最开始位置,下一次打印就会产生覆盖的效果。其次是进度条的风格,这里我们定义了标识符常量STYLE 来表示进度条的风格。

📖while循环逻辑分析

因为进度条是从0~100%,中间有101个跨度,因此循环的次数就是101次,因此cnt的范围是[0,100],这里用TOP来表示区间的右端点100。整个循环会执行101次打印动作和101次字符追加动作,因为总共会追加101个字符,再加上末尾的\0,一共就是102个字符,因此表示数组大小的NUM就是102。最初将数组中的内容全部初始化为\0,这样,第一次打印的就是一个空串什么也没有,对标0%,打印完后进行追加,在数组下标为cnt的位置(也就是下标为0的位置)追加了一个=,下标为cnt+1的位置(也就是下标为1的位置)追加一个>,第二次打印出来的就是=>,对标1%。当到进度到达100%的时候,我们希望打印出来的进度条右边没有>,因为100%对应的是最后一次打印,也就是当cnt == 100的时候,此时我们希望打印出100个=即可,这意味着,当执行这次打印时,数组下标为99的位置存储的是一个=并且下标为100的位置是\0,前者简单,当cnt == 99的时候字符串追加的时候会把其设置成=,要满足后者,我们就要加一个判断条件当cnt < 100的时候才能将bar[cnt]设置成>,否则不能修改bar[cnt]

3.2 实际使用场景

上面的processBar.c中为了演示进度条的原理,在里面写了一个while循环来模拟,但实际上的进度条并不是这样用的。以下载东西为例,作为一个进度条,它本身并不知道下载了多少,它只会提供一个接口,在下载东西的时候,调用这个接口,然后将已经下载好的比率作为参数传给进度条模块,它会根据比率打印出对应的进度条样式。

📖版本一

cpp 复制代码
//processBar.h
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='
#define TOP 100
#define BODY '>'
extern void processbar(int ret);
cpp 复制代码
//processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";
//V2版本
char bar[NUM] = {'\0'};//定义在全局避免每一次函数调用都会重现创建                                                                                                                                                                   
void processbar(int ret)
{
	if(ret <0 || ret > 100)//合理性判断
	{
		return;
	}
	if(ret == 0)//当比率为0的时候将数组全置为'\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;
	}
}
cpp 复制代码
//main.c
int main()
{                                                 
    int total = 1000;//假设总共要下载1000个G  
    int cur = 0;//当前下载的  
    while(cur <= total)                                  
    {                                                    
        processbar(cur * 100 / total);                   
        usleep(50000);//模拟下载花费时间                 
        cur += 10;//循环下载了一部分,更新进度           
    }                                                    
   return 0;   
}

📖版本二

cpp 复制代码
//processBar.h
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='
#define TOP 100
#define BODY '>'
extern void processbar(int ret);
cpp 复制代码
//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)//当比率为0的时候将数组全置为'\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;
	}
}
cpp 复制代码
//main.c
#include "processBar.h"                                                                                                  
#include <unistd.h>                                                                                                  

typedef void (*callback_t) (int);                                                                                      
//模拟一种安装或者下载                                                                                                       
void Downbload(callback_t ct)                                                                                                  
{                                                                                                                                
	int total = 1000;//假设总共要下载1000个MB                                                                                      
	int cur = 0;//当前下载的                                                                                                  
	while(cur <= total)                                                                                   
	{                                                                                                                    
		int rate = cur*100/total;                                                                                         
		ct(rate);                                                                                  
		usleep(50000);//模拟下载花费时间                                                                                     
		cur += 10;//循环下载了一部分,更新进度                                                         
	}                                                                                                  
	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;
}

📖效果演示


🎁结语:

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!

相关推荐
ChristXlx2 分钟前
Linux安装mysql(虚拟机适用)
linux·mysql
知星小度S4 分钟前
系统核心解析:深入操作系统内部机制——基础I/O探秘:文件描述符、重定向与Shell的I/O魔法(二)
linux·i/o
小周学学学6 分钟前
vcenter的SMB备份
运维·服务器·vmware·虚拟化
软件测试大叔18 分钟前
CentOS 7.6 安装 nvtop 完整教程
linux·centos·性能监控
speedoooo26 分钟前
未来的App不再需要菜单栏?
前端·ui·容器·小程序·web app
BullSmall34 分钟前
Apache Doris 精细化调优配置指南
linux·运维·服务器·database
QT 小鲜肉38 分钟前
【Linux命令大全】001.文件管理之chattr命令(实操篇)
linux·运维·服务器·笔记
落羽的落羽41 分钟前
【C++】哈希扩展——位图和布隆过滤器的介绍与实现
linux·服务器·开发语言·c++·人工智能·算法·机器学习
猿究院_xyz1 小时前
微信小程序与echarts联动安卓真机测试出现黑色阴影
前端·javascript·微信小程序·小程序·echarts
b***25111 小时前
18650与21700电芯电池组PACK自动化生产线的核心差异与协同发展
运维·自动化