【Linux】第一个小程序--进度条

这篇博客要综合利用以前的知识,来实现一个进度条程序~

目录

换行&回车

缓冲区

实现简单的倒计时

实现进度条

version1

version2


在开始写这个小程序之前,我们先学习一些预备知识:

换行&回车

缓冲区

在我们运行这个程序时,并没有直接打印出"hello bit,hello world...",而是当程序运行结束后才显示出来,但是这并不代表这句打印没有执行,而是没有显示出来而已。

那么,在我sleep期间,字符串在哪里?

答案就是被保存在叫做缓冲区的地方,就是一块内存空间,当程序结束时,一般会自动刷新缓冲区到字符设备(显示器),另外,如果程序遇到'\n'的时候,也会刷新缓冲区。如果想要强制刷新,可以使用fflush命令。

那么,为什么要有缓冲区呢,为什么按行刷新?为了提高效率,方便人类阅读,人类读信息都是按行读的,是一种尊重用户的表现

实现简单的倒计时

在实现倒计时中,我们想要达到的效果是依次显示数字,并且下一个覆盖前一个数字,创建test.c文件:

复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   int cnt=10;
  7   while(cnt >=0)
  8   {
  9     printf("倒计时:%2d\r",cnt);                                                                                                              
 10     fflush(stdout);
 11     cnt--;
 12     sleep(1);
 13   }
 14   printf("\n");
 15 
 16   return 0 ;
 17 }

我们实现的效果是10,9,...1,0的倒计时效果,其中,

复制代码
printf("倒计时:%2d\r",cnt);

这句代码中\r是回车,在一次打印完成后,光标回到最前面,继续打印下一个值,覆盖掉上一次的值,用%2d的格式打印的原因是,每次打印两个字符,以防某个字符一直在屏幕上显示。使用fflush(stdout)强制刷新到显示器上。

此外,我们还写了makefile自动化构建工具:

复制代码
 mycode:test.c
  2   gcc -o $@ $^
  3 .PHONY:clean
  4 clean:
  5   rm -f mycode

至此完成了简单的倒计时效果。

实现进度条

version1

在实现进度条中,我们要创建三个文件:Processbar.h,Processbar.c,Main.c,Main.c负责将Processbar.h中的方法进行调用,最终想达到的效果如下图:

在Processbar.h中,我们定义了Process函数声明;

复制代码
#pragma once                                                                                                                                 
#include <stdio.h>
extern void Process();

在Processbar.c中实现了Process:

复制代码
 #include "Processbar.h"
 #include <string.h>
 #include <unistd.h>

 #define Length 101
 #define Style '#'
 const char * label = "|/-\\";
 // version1
  void Process()
  {
     char bar[Length];
     memset(bar,'\0',sizeof(bar));
     int cnt = 0;
     int len =strlen(label);
     while(cnt <= 100)
     {
        printf("[%-100s][%3d%%][%c]\r",bar,cnt,label[cnt%len]);
        fflush(stdout);   
        bar[cnt++]=Style;
        usleep(20000);
     }
       printf("\n");
 }

在上面这段程序中,我们用bar数组来实现进度条,大小Length设为101(包括最后的'\0'),进度条符号为'#'。%-100s表示字符串靠左对齐,保证了'#'从左边往右增长。通过循环遍历label所指向的数组来实现旋转光标的效果。

需要注意的是,因为每次使用'\r'来实现覆盖的效果,但是'\r'不能让每次结果刷新到显示器上,需要用fflush(stdout)来刷新。usleep()函数的单位是微秒(包含unistd.h头文件)。

在Main.c中,调用Process.h:

复制代码
#include "Processbar.h"                                                                                                                                                                      
int main()                                                                                  {                                                                                                                                                 
   Process();                                                                                                                                 
   return 0;                                                                                               
}    

最终的实现效果:

version2

上面我们单独写了一个进度条程序,但是,进度条会单独出现吗?肯定不会!它要和具体的场景结合。

假设我们要完成一个下载的场景:下载指定大小的文件,

在Main.c中,我们写出download()这样一个函数:

复制代码
   double bandwidth = 1024*1024*1.0;                                                                                                                        
   //download
   void download(double filesize,callback_t cb)
   {
     //double filesize = 100*1024*1024*1.0;
     double current = 0.0;
   
     printf("download begin,current:%lf\n",current);
      while(current <= filesize)
     {
        cb(filesize,current);
       //从网络中获取
       current += bandwidth;
       usleep(10000);
      }
      printf("\ndownload done,filesize:%lf\n",filesize);
    }

其中,bandwidth是我们假设的带宽(可以理解为下载速度),download函数的filesize参数是我们要下载的文件大小,第二个参数是进度条打印函数,callback_t是函数指针,其定义在Processbar.h中,

复制代码
typedef void(*callback_t)(double,double); 

在while循环中,根据filesize和current的大小,打印出当前下载进度的进度条,打印完成后,继续下载,current继续增加,然后再打印下一次的进度条并覆盖之前的进度条。

在Processbar.h中,有如下声明:

复制代码
#pragma once 
#include <stdio.h> 
typedef void(*callback_t)(double,double);                                                                                                                
void Process(double total,double current);

在Processbar.c中,定义了Process函数:

复制代码
//version2
void Process(double total,double current)
{
   char bar[Length];
   memset(bar,'\0',sizeof(bar));
   int cnt = 0;
   int len =strlen(label);
 
   double rate = current*100.0/total;
   int loop_count = (int)rate;                                                                                                                            
   while(cnt <= loop_count)
   {
     bar[cnt++]=Style;
   }
   printf("[%-100s][%.1lf%%][%c]\r",bar,rate,label[cnt%len]);
   fflush(stdout);   
 }

根据总文件大小total和当前已下载大小current,打印出当前的进度条,例如,当total=100、current=36时,会打印出如下内容:

在Main.c中,将Process函数作为实参传给download()函数,完成下载。

复制代码
int main()
{
   download(100*1024*1024,Process);
   download(50*1024*1024,Process);                                                                                                                        
   download(80*1024*1024,Process);
   download(1*1024*1024,Process);
   download(18*1024*1024,Process);
   return 0;
}

模拟下载了多个文件,其效果如下:

相关推荐
kk”1 天前
Linux的组和权限管理
linux·运维·服务器
智购科技智能售货柜1 天前
2026自动售货机整机可靠性测试:从高低温交变到EMC电磁兼容的认证工程实践~YH
运维·服务器·数据库·人工智能·物联网
mohesashou1 天前
红帽linux的K8S部署
linux·kubernetes·ansible
xiaoye-duck1 天前
《Linux网络编程》UDP Socket 编程实战:从 Echo 回显、通用字典服务到套接字封装全流程
linux·网络·udp
酒神dnspup1 天前
CDN 回源检测怎么做?用 DNSPup 验证节点命中、源站暴露与多地可用性
运维·服务器·网络·网络协议·tcp/ip
Rain的Java大神之路1 天前
短信接口被狂刷怎么处理
java·运维·后端·web安全·面试·架构·xss
paopaokaka_luck1 天前
停车小程序(车牌OCR智能识别、地图API、Echarts图形化分析、智能车位预约、自动计费与订单处理)
小程序·ocr·echarts
江南风月1 天前
3分钟快速上手日志审计系统 WGLOG v2.2 更新了什么
运维·服务器·网络
孙克旭_1 天前
Docker 镜像构建|Vue+SpringBoot+Go 多语言项目 Dockerfile 案例
linux·运维·vue.js·spring boot·docker·dockerfile