Linux小程序 —— 进度条

前言:经过这么多天的学习,想必大家学到了很多Linux知识,今天我们来用Linux来实现我们的第一个小程序 — — 进度条



本篇主要内容将会实现三个版本的进度条:

  • 简单原理版本
  • 实际工程实践版本
  • 拓展版本

在实现进度条之前,我们先了解一些之前没提到过的知识,以便我们理解


进度条

  • 1. 缓冲区的概念
  • 2. \r&&\n
  • 3. 进度条
    • 3.1 版本一
    • 3.2 版本二
    • 3.3 版本三
    • 4. 总结拓展

      1. 缓冲区的概念

      我们先来分析下面几段代码感受一下行缓冲区的存在:

      在Linux当中以下代码的运行结果是什么样的?

      #include #include  int main()
      { printf("你能看见我嘛\n");
          sleep(2);
          return 0;
      }
      

      这段代码运行结果显而易见:printf函数直接打印内容,然后休眠2秒。

      对于大家可能没有什么难度。那如果我们修改一下代码。

      #include #include  int main()
      { printf("你能看见我嘛");
          sleep(2);
          return 0;
      }
      

      缓冲区的概念

      通过视频我们发现,我仅仅是将\n删除了,但是却带来了完全不一样的运行结果:先休眠2秒,然后才是printf函数打印内容

      那么为什么会出现这种情况呢? C语言执行代码的逻辑不应该是从上至下执行吗?

      • 按照 C语言执行代码的逻辑printf确实已经运行了,只不过内容没有被显示出来!
      • 内容所在的区域则是在输出缓冲区中!

      C/C++语言,会针对标准输出,给我们提供默认的缓冲区

      fflush函数可以刷新缓冲区,如果我们想立马显现可以用函数刷新fflush(),而\n是一种刷新的策略——行刷新,所以\n也能立马显现!


      2. \r&&\n

      概念:

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

      光说可能大家不太理解,我们来实操看看:

      #include #include int main()
      {int cnt = 10;
      	while(cnt)
      	{printf("%d\r", cnt--);
      		fflush(stdout);
      		sleep(1);
      	}
      	return 0;
      }
      

      回车 \r

      我们可以看到在输出下一个数之前都让光标先回到本行首格。

      但是为什么输出结果和我预想的完全不一样?

      printf("%-2d\r", cnt--);

      我们以两位字符进行输出,-则是表示靠左对齐,就可以正常输出了!


      3. 进度条

      在进行上面的铺垫之后,我们开始编写我们的第一个小程序。我们将用两个源文件和一个头文件,一个申明,一个调用,一个实现

      test.c:实现

      test.h:申明

      main.c:调用

      // Makefile:
      mytest:test.c main.c
           gcc -o $@ $^
      .PHONY:clean
      clean:
           rm -rf mytest 

      3.1 版本一

      在版本一中,我们只要简单实现一下基本的功能,得到一个基本框架就足够了。

      // process_v1
      //test.h:申明
      pragma once
      #include #include #include  // 定义进度条的总长度,因为有'\0'的存在所以设为101
      #define SIZE 101 
      // 定义进度条的当前进度
      #define MAX_RATE 100
      // 进度条的符号
      #define STYLE '#'
      // 进度条的休眠时间
      #define STIME 1000*15
      void process_v1();
      .............
      //test.c:实现
      #include "test.h"
       
      // 旋转光标
      const char *str = "|/-\\";
      // \\:才能表示一个'\'
      void process_v1()
      { int rate = 0;
           //初始化进度条全为'\0'
           char bar[SIZE] = {0};
       	 //循环打印
       	 int num = strlen(str);
           while(rate <= MAX_RATE)
           { // 进度条的打印格式
           // -100:先取好[]的范围,然后靠左打印。
               printf("[%-100s][%d%%][%c]\r", bar, rate, str[rate%num]);      
           // 刷新缓冲区                                     
               fflush(stdout);
           // 休眠时长
               usleep(STIME);
           // 填充符号
               bar[rate++] = STYLE;
           }
           // 刷新
           printf("\n");
       }
      .............
      //main.c:调用
      #include "test.h>int main()
      {process();
      	return 0;
      }
      

      进度条:版本一

      我们的第一代进度条也就完成了,实现了基本的结构框架!而我们的进度条,肯定不能干自己的,一定是和某种任务关联起来的!


      3.2 版本二

      我们将循环改成内部维护一个简单的静态缓冲区,每次往缓冲区里面增加内容然后刷新缓冲区内容就可以

      不能一次将进度条打印完毕,否则不能与场景更好的结合

      // process_v2
      //test.h:申明
      #pragma once
       
      #include #include #include  #define SIZE 101
      #define MAX_RATE 100
      #define STYLE '#'
      #define STIME 1000*15
       
      typedef void(*callback_t)(int); // 回调函数        
      // 这里我们也可以使用函数指针                                       
      void process_v2(int);                                 
      ~                            
      .............
      //main.c:调用
      #include "test.h"
        
      #define TARGET_SIZE 1024*1024 // 模拟下载软件的大小
      #define DSIZE 1024*10 // 模拟下载的速度
        
      //void download()
      //{//    int target = TARGET_SIZE; // 软件总体积
      //    int total = 0; // 当前下载的大小
      // 
      //    while(total < target)
      //    {//        usleep(STIME); // 用休眠时间,模拟下载时间
      //        total += DSIZE;
      //       process_v2(total*100/target);     
      //    }
      //    printf("\n");
      //}
      // 回调函数
      void download(callback_t cb)
      { int target = TARGET_SIZE; // 软件总体积
          int total = 0; // 当前下载的大小
       
          while(total < target)
          { usleep(STIME); // 用休眠时间,模拟下载时间
              total += DSIZE;
              int rate = total*100/target;   
              cb(rate);  
          }
          printf("\n");
       }
        
      int main()
      { download(process_v2);
          return 0;
      }
      .............
      //test.c:实现
      #include "test.h"
        
      const char *str = "|/-\\";
      void process_v2(int rate)
      { static char bar[SIZE] = {0};
          int num = strlen(str);
          
          if(rate <= MAX_RATE && rate >= 0)     
          { printf("[%-100s][%d%%][%c]\r", bar, rate, str[rate%num]);
              fflush(stdout);
              bar[rate] = STYLE;
          }
          if(rate == MAX_RATE)
          { // 重新刷新为0
              memset(bar, '\0', sizeof(bar));
          }
      }
      

      进度条:版本二

      我们也能完成进度条的实现,最后我们在优化一下,变成我们的版本三!。


      3.3 版本三

      因为版本二已经能将进度条完美的呈现了,我们版本三,只是在二的基础上,美化一下,所以只是简单修改一点代码!

      // test.h
      #define STYLE_DEADER '>'
      #define STYLE_BODY '='
      typedef void (*callback_t)(double);
      .............
      // main.c
      // 我们在下载时,模拟下载中断的状态
      void download(callback_t cb)
      { int target = TARGET_SIZE; // 软件总体积
          int total = 0; // 当前下载的大小
       
          while(total <= target)
          { usleep(STIME); // 用休眠时间,模拟下载时间
              total += DSIZE;
              double rate = total*100/target; 
              // 我们让下载进度永远维持在50%左右
              if(rate > 50.0)
              { total = target/2;
              }  
              cb(rate);  
          }
          printf("\n");
       }
       
      .............
      // test.c
       if(rate <= MAX_RATE && rate >= 0)
       { // 设置cnt是为了在下载终止时,光标依然能变化
          cnt++;
          cnt = cnt > num ? 0 : cnt;
          // \033[1;47;30m ... \033[0m 则是更改输出时字体和背景颜色
          printf("加载中...\033[1;47;30m%-100s\033[0m][%.1lf%%][%c]\r", bar, rate, str[cnt]);        
          fflush(stdout);
          if(rate < MAX_RATE)
          { bar[(int)rate] = STYLE_BODY;
              bar[(int)rate+1] = STYLE_HEADER;
          }
          else{ bar[(int)rate] = STYLE_BODY;
          }
       }
      

      进度条:版本三

      我们可以发现,我们修改了字体颜色和背景,设置测试了可能遇到的中断情况,光标依旧会变化。当然了进度条还有很多情景,等待着各位开发!


      4. 总结拓展

      拓展:

      关于print带颜格式化输出,我在这里推荐一篇博客,有兴趣的可以去了解一下

      print带颜格式化输出


      总结:

      本篇我们简单了解了一下缓冲区,以及换行'\n'与回车'\r'的基本概念,然后由浅入深的介绍了三个版本的进度条,当然了美化方式各位都不一样,都是可以的,我们的Linux第一个小程序就讲到这里

      谢谢大家支持本篇到这里就结束了