【简易进度条的实现】

独夜无伴守灯下,清风对面吹.............................................................................................

文章目录

前言

一、【行缓冲区的引入】

1、【问题提出】

2、【\r和\n】

3、【简易倒计时程序】

二、【简易进度条的实现】

process_bar.h代码:

process_bar.c代码:

主函数main.c 代码:

效果图:

颜色设置:

三、【不一样的进度条】

1、【进度条动态增长的本质】

2、【背景色实现动态增长】

process_bar.hpp代码:

process_bar.c代码:

主函数main.c 代码:

效果图:

总结


前言

本篇博客介绍了,如何在Linux下运行一个进度条小程序,以及该如何对一个进度条小程序进行美化,还包括行缓冲区的概念,以及 ” \r “ 的用法,请耐心观看! 


一、【行缓冲区的引入】

1、【问题提出】

首先,我们来感受一下行缓冲区的存在,在Linux当中以下代码的运行结果是什么样的?

#include<stdio.h>
#include<unistd.h>
int main()
{printf("this is a test file...\n");sleep(3);return 0;
}

对于此代码,大家应该都没问题,当然是先输出对应字符串然后休眠3秒之后结束运行。

那么对于以下代码呢?

#include<stdio.h>
#include<unistd.h>
int main()
{printf("this is a test file...");sleep(3);return 0;
}

可以看到代码中仅仅删除了字符串后面的’\n’,那么代码的运行结果还与之前相同吗?

答案否定的,该代码的运行结果是:

先休眠3秒,然后打印字符串hello world之后结束运行。该现象就证明了行缓冲区的存在。

原因如下:

显示器对应的是行刷新,即当缓冲区当中遇到’\n’或是缓冲区被写满才会被打印出来,而在第二份代码当中并没有’\n’,所以字符串先被写到缓冲区当中去了,然后休眠3秒后,直到程序运行结束时才将字符串打印到显示器当中。

这里我们可能会有点懵,不知道缓冲区是什么,目前我们仅需知道缓冲区实际上就是一块内存空间,对于上面的字符串,就是被保存在这样的一块内存空间里,当程序运行结束时,会自动的刷新缓冲区里的内容到显示器上,所以无‘\n’才能看到上述的现象。

也就是说我们需要刷新缓冲区,才能将内容进行显示,那么我们该如何刷新缓冲区呢?

一般有下面3种方法:

①加上‘\n’,立即刷新

②等待缓冲区满或者程序结束,自动刷新

③强制刷新

实际上我们需要进行强制刷新,而我们强制刷新的方式就是使用C语言提供的一个的函数———fflush,这个函数就是用于强制刷新缓冲区的。

这里的文件流后面的文章有详细讲解,现在我们只需要知道程序在运行时默认会打开三种流

  • 标准输入,stdin
  • 标准输出,stdout
  • 标准错误,stderr

我们的显示器就是标准输出文件,Linux下一切皆文件!所以可以采用该函数强制刷新到对应的文件流上。

我们现在强制刷新看一下:

#include<stdio.h>
#include<unistd.h>
int main()
{printf("this is a test file...");fflush(stdout);sleep(3);return 0;
}

可以看到,即使程序没有‘\n’,也能直接显示字符串内容!

2、【\r和\n】

\r: 回车,使光标回到本行行首。
\n: 换行,使光标下移一格

回车和换行对于我们而言就是一个简单的动作,可是对于机器来说这是两个独立的动作。

而我们键盘上的Enter键实际上就等价于\n + \r :

既然是\r是使光标回到本行行首,那么如果我们向显示器上写了一个数之后再让光标回到本行行首,然后再写一个数,不就相当于将前面一个数字覆盖了吗?

但这里有一个问题:不使用’\n’进行换行怎么将缓冲区当中数据打印出来?

我们就可以使用前面介绍的fflush函数,该函数可以刷新缓冲区,即将缓冲区当中的数据刷新当显示器当中。对此我们可以编写一个倒计时的程序。

3、【简易倒计时程序】

原理:实际就是对同一个位置进行覆盖进而实现一个动态的效果。

每当显示一个数字之后就将光标挪动至最前面(回车),休眠,然后再显示下一个数字。回车配休眠需要强制刷新缓冲区。

#include<stdio.h>
#include<unistd.h>
int main()
{int cnt = 10;while (cnt >= 0){printf("倒计时:%2d\r", cnt);fflush(stdout);//强制刷新到屏幕sleep(1);cnt--;}return 0;
}

注意:为什么用%2d,是因为数字显示在屏幕上时,实际上是字符串的形式,相当于10是两个字符的字符串,需要覆盖两个字符。当然,大家感兴趣把2去掉看看! 

知道了\r这个概念我们就可以实现一个简单的进度条了。

二、【简易进度条的实现】

这里我们使用Makefile来实现,可以方便我们管理文件。

进度条不可能单独存在,一般常见的场景就是下载这一场景,会根据网络带宽,文件大小等其他的要素来决定下载进度,这里我们不清楚具体的下载逻辑,因此我们仅仅是模拟一下下载过程。

代码:

process_bar.h代码:

#pragma once
#include<stdio.h>                                                                 
#include <unistd.h>                                                                                       
#include <string.h>
#define SIZE 101 //定义数组大小为101,反映数组的长度
#define MAX_RATE 100//最大为百分比为100
#define STYLE '='//进度条填充物
#define STYLE_HEADER '>'//进度条尾部填充物
#define STIME 100000//休眠时间
#define TARGET_SIZE 1024*1024 // 1MB
#define DSIZE 1024*10//单次下载速度
typedef void (*callback_t)(double);//函数指针,方便实现回调函数
void process_bar(double);//函数声明

process_bar.c代码:

#include "process_bar.h"
const char* str = "|/-\\";//旋转光标的数组
void process_bar(double rate)
{static char bar[SIZE] = { 0 };static int cnt = 0;//旋转光标数组下标,用来变化旋转光标int num = strlen(str);//旋转光标长度if (rate <= MAX_RATE && rate >= 0)//判断比率是否合法{cnt++;cnt = (cnt >= num ? 0 : cnt); //cnt %= num;//防止越界//[\033[30;47m\033[0m]是为其设置颜色,30是字体颜色,47是背景颜色 printf("[\033[30;47m % -100s\033[0m][% .1f % %][% c]\r",bar, rate, str[cnt]);fflush(stdout);if (rate < MAX_RATE){bar[(int)rate] = STYLE; //'='bar[(int)rate + 1] = STYLE_HEADER; //'>'}else{bar[(int)rate] = STYLE;}}
}

代码解释:

①进度条实际上是放在一个数组的中,所以定义一个长度为101的字符数组。数组按字节全部初始化为'\0'!

②因为要带来动态的效果所以运用上面提到的倒计时程序类似。回车('\r')配延时,强制刷新缓冲区

③%-100s:表示左对齐

④%.1f%%:最后的双百分号,是为了取字面值%,以实现100%的效果。因为单独一个%在C语言中有着特殊含义,并且一般进度条都带小数,所以采用double。

⑤const char* str:字符串,同样最后的”\\“,也是为了取字面值\,防止转义,单独的\在C语言有着特殊含义。

主函数main.c 代码:

#include "process_bar.h"
void download(callback_t cb)
{int testcnt = 100;int target = TARGET_SIZE;int total = 0;while (total <= target){usleep(STIME); // 用简单的休眠时间,模拟本轮下载花费的时间total += DSIZE;double rate = total * 100.0/ target;//模拟卡顿/*  if (rate > 50.0 && testcnt) {total = target / 2;testcnt--;}*/cb(rate); // 回调函数}cb(MAX_RATE); // 回调函数printf("\n");
}int main()
{download(process_bar);//模拟下载函数return 0;
}

注意:这里声明了一个callback_t函数指针类型目的就是实现函数回调。因为未来可能还有更多版本的进度条功能代码, 采用函数指针的方式,只需要传入对应的函数名,就会去调用对应的功能代码。

效果图:

颜色设置:

printf("\033[0m \033[属性代码1;属性代码2;属性代码3...m字符串,\033[0m",占位符填充变量);

三、【不一样的进度条】

当我们向上面那样实现进度条会发现与我们日常见到的进度条不太一样,我们日常见到的大多是,类似于一整个填充满的矩形块,而不是上面那样用字符等号,箭头来填充。一般像下面这样:

我们来实现一个类似于这种矩形的进度条,由于能力有限,还是实现一个简易的没有图片中这么炫酷。

1、【进度条动态增长的本质】

我们先来分析一下,我们上面版本的进度条是如何实现进度条的动态增长的,我们先来看单纯打印进度条的代码:

void process_bar(int rate)
{int rate = 0;char crr[101];memset(crr, '\0', sizeof(crr));const char* str = "\\|/—";//char str[4] = {'\\','|','/','--'};while (rate <= 100){printf("[%-100s][%d%% %c]\r", crr, rate, str[rate % 4]);crr[rate] = '=';Sleep(50);rate++;}printf("\n");
}

我们主要打印的是crr数组里所有的内容,并且随着rate的自增,渐渐的crr数组会被‘ = ’,填满,由于我们每次在打印之前都会使用/r来清除上次打印留下的结果,所以随着crr中的 ‘ = ’随着rate的增长越来越多,就实现了动态增长的效果。

那么是不是意味着,我们可以通过将字符等号替换为某个矩形块来实现我们的目的呢?

比如下面的这些矩形块:

static const char* ponit[] = { "\x20\x20", "\xA8\x87", "\xA8\x86", "\xA8\x84", "\xA8\x83", "\xA8\x80" }; //  ▏▎▍▊█

用这些字符实际上是不能达到目的的,本质原因是因为它们不是字符而是字符串,我们是通过·%s来打印crr这个字符数组的所有内容,本质上crr是一个会自动变化的字符串,它其中的 ‘  =  ’字符越来越多,而%s只有遇到 ‘ \0 ’才会停止打印,而又因为那些矩形块本质上都是字符串,所以无论crr里存放再多的字符串,%s也只会打印一个,因为字符串是以 ‘ \0 ’结尾的。

所以我们应该重新实现一个打印的逻辑。这里还是采用简单的办法。 

2、【背景色实现动态增长】

背景色是打印对象的背景,也就是说只要打印对象是自增的,那么背景也会跟着自增,但是要注意,要将背景只对应填充物,这就意味着我们不能在使用100那个占位符(”%-100s“)了,如果提前占100个位置,背景也会一直是100个长度,恒定不变,所以我们不进行占位,至于填充物选择空格就好。

那么我们就可以像下面这样书写(vs2019下运行):

process_bar.hpp代码:

#pragma once #include <stdio.h>
#include <string.h>
#include <windows.h>
#define Length 101 //进度条长度
#define Style ' ' //进度条样式
typedef void(*Call_back)(double, double);//定义函数指针类型,为了方便回调                                                                                                    
void processbar(double total, double current);

注意:这里声明了一个Call_back函数指针类型目的就是实现函数回调。因为未来可能还有更多版本的进度条功能代码, 采用函数指针的方式,只需要传入对应的函数名,就会去调用对应的功能代码。

process_bar.c代码:

#include "process_bar.hpp"
const char* lab = "|/-\\";//加载旋转标志
void processbar(double total, double current)
{char bar[Length];memset(bar, '\0', sizeof(bar));//初始化为\0int len = strlen(lab);int cnt = 0;double rate = (current * 100.0) / total;//计算比率int load_top = (int)rate;while (cnt < load_top){bar[cnt++] = Style;//字符追加至数组中}printf("\033[0m \033[42;1m[%c]%s%.1lf%% \r\033[0m", lab[cnt % len], bar, rate);fflush(stdout); //强制刷新
}

解释一下:

①因为主函数的下载功能是一个循环,会不断的调用该功能函数,每次的下载进度都不一样,因此需要计算每次的比率,再根据比率去打印进度条。。

②这里是先将字符一次性放进数组中,最后在一起刷新出来。如果不这样的话每次都会显示从0开始打印,并不是我们想看到的结果。

主函数main.c 代码:

double bandwith = 1024 * 1024 * 1.0;//下载速度1MB/s                         void download(double filesize, Call_back cb)//函数指针做参数                         
{double cnt = 0.0;printf("download begin....,bandwith is:%.1lf\n", bandwith);while (cnt <= filesize){cb(filesize, cnt);Sleep(50);cnt += bandwith;}printf("\ndownload finishi......,filesize is:%.1lf\n", filesize);printf("\n");
}
int main()
{download(50.0 * 1024 * 1024, processbar);//50MB                         download(10.0 * 1024 * 1024, processbar);//10MB                        download(100.0 * 1024 * 1024, processbar);//100MB                                                                                                                         return 0;
}

我们在主函数中分别模拟下载了3个不同大小的文件,还有下载功能存在一个函数指针类型的参数,只需传入函数名(函数地址),就能找到对应函数的功能实现。

效果图:

可以看到,进度条会根据文件大小的不同,下载的速度也不一样,同一带宽情况下,文件越大,那必然越慢,如上图的10MB比100MB快多了。

总结

本篇博客到这里就结束了,感谢观看!

...................................................................................................花开当折直须摘,青春最可爱

                                                                                                                  ————《望春风》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/462667.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【已解决】cra 配置路径别名 @ 后,出现 ts 报错:找不到模块“@/App”或其相应的类型声明。ts(2307)

cra 配置路径别名 后&#xff0c;出现 ts 报错&#xff1a;找不到模块“/App”或其相应的类型声明。ts(2307) 然后可以在 tsconfig.json 中配置 baseUrl 和 paths &#xff1a; {"compilerOptions": {"target": "es5","lib": [&quo…

es拼音分词器(仅供自己参考)

github地址&#xff1a;https://github.com/infinilabs/analysis-pinyin&#xff08;各种版本&#xff0c;对接es版本&#xff09; 拼音分词器存在的问题&#xff1a; 1、是直接将每个字的拼音返回和一段话的拼音首字母返回&#xff0c;不能很好的分词。 2、不会保留中文&am…

为什么大家都在学数字孪生呢?

随着物联网&#xff0c;大数据、人工智能等技术的发展&#xff0c;新一代信息技术与制造业正在深度融合&#xff0c;人们与物理世界的交互方式正在发生转折性的变化。数字化转型正在成为企业的重要战略&#xff0c;而数字孪生则成为全新的焦点。 当下&#xff0c;在数字技术和…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-11

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

LLM Observability: Azure OpenAI (一)

作者&#xff1a;来自 Elastic Vinay Chandrasekhar•Andres Rodriguez 我们很高兴地宣布 Azure OpenAI 集成现已全面上市&#xff0c;它提供了对 Azure OpenAI 服务性能和使用的全面可观察性&#xff01;另请参阅本博客的第 2 部分 虽然我们已经提供了对 LLM 环境的可视性一段…

HTML 基础标签——表格标签<table>

文章目录 1. `<table>` 标签:定义表格2. `<tr>` 标签:定义表格行3. `<th>` 标签:定义表头单元格4. `<td>` 标签:定义表格单元格5. `<caption>` 标签:为表格添加标题6. `<thead>` 标签:定义表格头部7. `<tbody>` 标签:定义表格…

第7章 内容共享

第 7 章 内容共享 bilibili学习地址 github代码地址 本章介绍Android不同应用之间共享内容的具体方式&#xff0c;主要包括&#xff1a;如何利用内容组件在应用之间共享数据&#xff0c;如何使用内容组件获取系统的通讯信息&#xff0c;如何借助文件提供器在应用之间共享文件…

基于 Python 的 Django 框架开发的电影推荐系统

项目简介&#xff1a;本项目是基于 Python 的 Django 框架开发的电影推荐系统&#xff0c;主要功能包括&#xff1a; 电影信息爬取&#xff1a;获取并更新电影数据。数据展示&#xff1a;提供电影数据的列表展示。推荐系统&#xff1a;基于协同过滤算法实现个性化推荐。用户系…

【高等数学】3-2多元函数积分学

1. 二重积分 可以想象你有一块不规则的平面薄板,它在一个平面区域上。二重积分就是用来求这个薄板的质量(假设薄板的面密度函数是)。 把区域划分成许多非常小的小方块(类似于把一块地划分成很多小格子),在每个小方块上,密度近似看成是一个常数,然后把每个小方块的质量加…

喜欢央卫 5.5.5 | 老年人专用电视直播APP

喜欢央卫是一款专门为老年人设计的电视直播APP。这款APP的名字非常简单直白&#xff0c;内容也符合老年人的口味。它提供了常用的央卫频道&#xff0c;还有V4和V6的不同线路&#xff0c;同时支持超多地方频道。界面简洁易用&#xff0c;非常适合教老人如何看电视。 大小&#…

DAY17|二叉树Part03|LeetCode: 654.最大二叉树 、617.合并二叉树 、700.二叉搜索树中的搜索、98.验证二叉搜索树

目录 LeetCode: 654.最大二叉树 基本思路 C代码 LeetCode: 617.合并二叉树 基本思路 C代码 LeetCode: 700.二叉搜索树中的搜索 基本思路 C代码 LeetCode: 98.验证二叉搜索树 中序遍历判断递增 基本思路 C代码 递归法 C代码 LeetCode: 654.最大二叉树 力扣…

《数字图像处理基础》学习05-数字图像的灰度直方图

目录 一&#xff0c;数字图像的数值描述 &#xff11;&#xff0c;二值图像 &#xff12;&#xff0c;灰度图像 3&#xff0c;彩色图像 二&#xff0c;数字图像的灰度直方图 一&#xff0c;数字图像的数值描述 在之前的学习中&#xff0c;我知道了图像都是二维信息&…

golang的多表联合orm

项目截图 1.数据库连接配置 DbConfigUtil.go package configimport ( "fmt" _ "github.com/go-sql-driver/mysql" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" )var Go…

Chromium 中chrome.topSites扩展接口定义c++

一、chrome.topSites 使用 chrome.topSites API 访问新标签页上显示的热门网站&#xff08;即最常访问的网站&#xff09;。不包括用户自定义的快捷方式。 权限 topSites 您必须声明“topSites”扩展程序清单中授予使用此 API 的权限。 {"name": "My exten…

在Zetero中调用腾讯云API的输入密钥的问题

也是使用了Translate插件了&#xff0c;但是需要调用腾讯云翻译&#xff0c;一直没成功。 第一步就是&#xff0c;按照这上面方法做&#xff1a;百度、阿里、腾讯、有道各平台翻译API申请教程 之后就是&#xff1a;Zotero PDF translat翻译&#xff1a;申请腾讯翻译接口 主要是…

2-137 基于matlab的sigmoid函数的变步长自适应语音信号增强

基于matlab的sigmoid函数的变步长自适应语音信号增强&#xff0c;与传统LMS相对比&#xff0c;比较不同的变步长函数去噪效果&#xff0c;并基于较好的去噪算法分析不同变步长中参数变化对降噪的影响。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a;2-13…

DNS服务部署

第一步&#xff1a;两个服务器恢复快照&#xff0c;预处理、安装软件、设置网卡信息 第二步&#xff1a;主服务端操作&#xff0c;编辑主配置文件设置监听IP。主服务端操作打开区域配置文件&#xff0c;添加如下内容&#xff0c;重点为&#xff1a;允许从服务器的同步请求&…

云效+mqtt实现本地构建和远程自动发版

之前写过一篇jenkinsmqtt实现本地构建和远程自动发版_jenkins远程调用和本地调用-CSDN博客 由于本地搭建jenkins实在太费机器了&#xff0c;这次改用云效搭建。不过云效并没有直接发送mqtt的方法&#xff0c;需要编写中转接口。 中转接口采用go-gin框架实现&#xff0c;代码如…

存储器与寄存器

​​​​​​存储器 存储器&#xff08;Memory&#xff09;是计算机中用于存储数据和程序的硬件设备。有了存储器计算机就具有记忆功能。 RAM 随机存取存储器&#xff08;RAM, Random Access Memory&#xff09; 是计算机系统中一种重要的内存类型&#xff0c;主要用于临时存储…

T10打卡—数据增强

​​​​​​​ &#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 1.导入及查看数据 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]False import…