当前位置: 首页 > news >正文

Linux—进度条实现

一、回车换行概念

二、缓冲区问题

三、倒计时程序

四、进度条-版本1

五、进度条-版本2


本篇文章将要在Linux中实现一个简易的进度条,在实现进度条之前需要有两个知识的补充,进度条也会实现两个版本,那下面我们一起来看看吧

一、回车换行概念

回车换行是不是一个概念呢?在写C语言的时候我们在printf的最后加上\n,我们说是换行,但其实它们并不是一个概念,换行指的是仅仅换到下一行继续写,从当前的位置换到下一行写,不是从下一行的开头写。回车指的是回到当前行的头,如果写入内容就覆盖掉原来的内容。

但是在写代码的时候,我们写了\n,仅仅是代表换行,那应该是换到下一行写啊,为什么换到下一行的头开始写了呢?这是因为在语言的角度,只是换到下一行写非常怪,所以\n就代表回车换行两个动作,而单独的回车用\r表示,所以\n和\r\n是一个意思。

上面的例子给代码增加了一个sleep函数,意思是等待2秒再执行下一步我们可以,两个代码的执行结果是一样的,都是先把hello world打印出来,光标紧接着去到下一行的开头,等待两秒之后打印出命令行。


二、缓冲区问题

但是当把\n或者\r\n去掉之后再运行我们会发现,居然是先等待了两秒之后才把字符串打印出来的,那难道是因为先执行的sleep,再执行的printf吗?但是我们的程序没有判断,循环,肯定是顺序执行的,那就一定是先执行的printf再sleep,现在的问题是,我们先看到的是sleep,根据语法执行的规则这时肯定已经执行了printf,但是我们没有看到,那这个字符串在哪里呢?其实是在缓冲区中,缓冲区我们就理解为一块内存空间就可以,把字符串写到了缓冲区中,那什么时候才会刷新到显示器上呢?

刷新的策略采用的是行刷新

1、在\n或者\r\n之前的内容,会被刷新到显示器上

2、程序退出的时候,历史数据也会被刷新

那如果现在就想强制看到呢?就要强制刷新缓冲区,可以用fflush函数

通过man手册查到头文件是stdio.h,参数需要一个流,在我们的程序启动的时候,有三个流默认会打开,分别是stdin, stdout, stderr,stdout是标准输出流,我们只需要让stdout做fflush的参数即可

 

 


三、倒计时程序

下面我们写一个简易的倒计时程序,方便我们更好理解待会的进度条

当我们写下printf("%d", 123)的时候,在显示器上打印的是整数123还是3个字符?其实打印的是字符,3.14打印的也并不是浮点数,而是打印了4个字符,所以显示器也叫做字符设备。

当我们了解了这个细节之后,我们再来看看倒计时的程序,当我们输出了9之后,光标会像后走,但是我们的要求是应该把9覆盖掉,输出8,那就需要\r回车到最开始去覆盖,而单独的\r不会打印到显示器上,而是会写入到缓冲区,就需要配合fflush强制刷新缓冲区。

当整个程序运行结束时,最后的1并没有显示在显示器上,这是因为打印完1光标也是回到这一行的开始,那再打印命令行的时候自然就把1覆盖掉了,所以我们要在循环结束后打印一个\n,让光标去到下一行的最开始打印命令行就可以了。

现在让 i 从10开始倒计时,会发现出现问题了,10是好好的,但是接下来变成了90,80,70,...,这是因为回车之后光标回到最开始字符1的位置,下面要输出9,就只覆盖这1个字符,后面的0不变,才变成我们看到的这样子,解决方法就是控制格式,使用%2d,每次输出2个字符,但是这是右对齐,我们的倒计时左边空了一个空格,在后面进行的打印输出,想要左对齐就是%-2d,自此我们的倒计时程序就写好了。


四、进度条-版本1

有了上面的这些储备知识以后,就可以来编写进度条了,我们以分文件的形式来写,process.h是声明,process.c是定义,test.c是测试,而我们要写的进度条是这个样子的

用#来填充进度,一共是100个,每个#代表一个百分比,加载进度在第二个,最后一个是旋转光标,那这个旋转光标是干什么用的呢?当有时候用户的网络卡顿时,加载进度可能并没有增长,但是旋转光标一直在动表示在告诉用户,现在在加载,进度条没有出问题,是你的网络太卡了。

进度条的本质就是每一次都回车回到最前面覆盖掉上一次的结果,然后每一次都多打印一个#,加载进度也跟着走,一共需要100个#,还有一个\0,那我们就定义一个长度为101的数组,并且全部初始化为0

usleep的单位是微秒,为了不让进度条一下就打印完,我们每次都让它等待20000微秒再打印下一次,而且在printf语句内我们要输出的是3%,需要在%d后面带两个%转义,并且需要在循环出来后立即打印\n换行,否则命令行就会覆盖掉进度条。

此时程序还有两个问题,第一个是字符串后面的右中括号,现在这个括号是随着我们的#数量的增多,括号也在往右走,但是我们想让它一开始就预留出空间,增加的仅仅是中间#的数量,两边的括号不要动,这时就要指定格式,用%100s提前预留出100个空格的空间,但是现在是右对齐,当输出的字符比100少时,前面用空格替代,后面输出#,所以进度条看起来是在反向增长,从右往左,就要用%-100s就是左对齐,每次留出100个空间,前面是#,后面用空格代替。

第二个问题是旋转光标,我们定义一个字符数组,分别是 | / - \,需要注意的是反斜杠\需要转义,两个\\表示输出一个\,用下标分别访问每一个字符然后打印出来,每一次打印都是跟上一次不同的方向,就形成了旋转光标的感觉。

这里的注意点是cnt%size,不取模就会越界,把函数在process.h中声明,test.c中调用一下进度条就跑起来了,这就是一个完整的进度条的实现了,下面我们在再来看另一个版本的。

运行结果


五、进度条-版本2

大家想一下,我们上面写的进度条可以实际应用吗?是不是不可以,在实际中应该是边下载边更新进度条,而我们现在的进度条是直接一下就完事了,假如用户是下载1MB的数据,进度条一下就走完了,用户要下载100MB的数据,进度图也是一下就走完了,这显然是不合理的,我们应该结合用户具体的场景,例如一共要加载多少,当前的下载速度是多少,应该结合具体数据来更新进度条,而不是无论什么情况都是一个速度。

下面举一个具体的样例,假设一共要下载1024M,速度是1M

当前下载的总量只要超过了目标大小就停止,用usleep代表一次下载,现在调用process就不行了,我们需要按照当前的下载量以及总下载量来更新进度条,需要把current_total以及target这两个变量传过去。

现在有了一共的下载量,当前下载量,比如一共要下100M,现在下了5M,两个做除法,再乘100,就得到了当前下载的百分比5%,但是还有可能是小数啊,比如5.3,6.7,我们规定百分比增加1,才打印一个#,5.3是5个#,6.7是6个#,所以我们需要强转一下,得到当前应该打印的#的个数,然后定义一个长度为101的字符数组,刚刚强转完的整数是多少,就往数组里填多少个#,打印以及刷新缓冲区这些是一样的。

有两个地方需要处理,第一个是旋转光标问题,定义的数组和第一版是一样的,还要定义一个下标,但是下标需要注意,因为FlushProcess是DownLoad函数调用的,第一次调用下标是0,访问到|,然后下标++,但是出了作用域局部变量就销毁了,等到下次再调用的时候,又要重新初始化,又从0开始,打印的是|,那就每一次都打印|,达不到我们想要的效果,所以应该把下标创建成静态的,才可以一直++,不用每次都初始化。第二个问题是打印百分比的时候,小数点太多了,我们只保留一位。

下面我们看test.c文件中的DownLoad函数,当current_total==target时,也会进入循环,但是这种情况下,我们只想打印进度条,current_total += speed以及usleep这两步不想做,因此要增加一个判断。并且在循环外,当current_total >= target时,需要打印\n换行,否则现在的进度条会被命令行覆盖。


process.h

process.c

 test.c

运行结果 

http://www.xdnf.cn/news/421183.html

相关文章:

  • 开源网络地图可视化第六章学习指南
  • 【unity游戏开发——编辑器扩展】使用MenuItem自定义菜单栏拓展
  • 【ArcGIS】根据shp范围生成系列等距点:范围外等距点+渔网点(Python全代码)
  • Android之横向滑动列表
  • ETL背景介绍_1:数据孤岛仓库的介绍
  • HTTP和HTTPS模块
  • 【计算机视觉】OpenCV实战项目:基于Tesseract与OpenCV的字符识别系统深度解析
  • Flask+HTML+Jquery 文件上传下载
  • 数据库优化
  • 第一章:HTML基石·现实的骨架
  • 《Effective Python》第2章 字符串和切片操作——深入理解Python 中的字符数据类型(bytes 与 str)的差异
  • ValueError: Caught ValueError in DataLoader worker process 0.
  • Postman启动时检测到版本不匹配错误
  • stm32之PWR、WDG
  • UE5通过C++实现TcpSocket连接
  • 在SQL Server中调整查询超时j解决方案
  • STM32 启动文件分析
  • 为何选择MCP?自建流程与Anthropic MCP的对比分析
  • docker大镜像优化实战
  • Landsat 5介绍
  • 【HCIA】浮动路由
  • 代码随想录算法训练营第六十三天| 图论9—卡码网47. 参加科学大会,94. 城市间货物运输 I
  • Springboot之类路径扫描
  • AI+可视化:数据呈现的未来形态
  • 小程序的内置组件
  • Docker与PostgreSQL
  • 自旋锁和CLH锁和AQS
  • 首个窗口级无人机配送VLN系统!中科院LogisticsVLN:基于MLLM实现精准投递
  • Codis集群搭建和集成使用的详细步骤示例
  • Flask Docker Demo 项目指南