Linux小程序(1)—— 简单进度条

本文记录在Linux中实现一个简单进度条程序。要完成此程序,需要用到前置知识;需要掌握vim的基础操作和Linux自动化构建工具makefile的使用。详细请查看文章:Linux(4)------ 基础开发工具

项目框架

进度条程序我们很熟悉,在手机电脑上玩游戏、下载安装软件等我们都能看到下方会有一个安装后加载的进度条。在开始该程序项目之前,我们需要理清楚设计的框架。

首先,要有main函数文件,即main.c文件。

然后,要有一个头文件,我们创建一个名为processBar.h的头文件。此外,程序的主要实现代码也可以单独放在一个文件里。我们再创建一个processBar.c的文件用于存放程序主要逻辑代码。

最后,运用我们前面学习到的Linux自动化项目构建工具 makefile(Makefile),我们需要再创建一个makefile文件。

综上所述,我们一共需要创建四个文件,自己选择一个适合的文件夹,创建上面的四个文件:\

bash 复制代码
touch main.c processBar.h processBar.c Makefile

创建好之后,我们打开头文件加入库:

cpp 复制代码
#pragma once

#include <stdio.h>

之后我们再打开Makefile进行配置:

bash 复制代码
processbar:pricessBar.c main.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f processbar

这样我们输入make指令就会生成processbar的程序,需要清除项目使用make clean即可。

我们来看一下Makefile里面的代码。

可以看到我们编译和清理一个名为processbar的程序。第一行为依赖关系;文件proceeBar.c和main.c两个文件时目标文件的依赖项,即processbar依赖于这两个源文件。

第二行 gcc -o $@ $ 是构建目标的命令。其中 $@是Makefile自动变量,代表目标文件名(即processbar)。而$^代表所有依赖文件,即(processBar.c 和 main.c源文件)

所以第二行的依赖方法等效于下面的代码:

bash 复制代码
gcc -o processbar processBar.c main.c

最后,.PHONY:clean :表示声明 clean 是一个伪目标:

伪目标不对应实际的文件,总是执行其命令防止目录中存在名为 clean 的文件时规则不执行。

最后的删除代码我们在Linux(4)------ 基础开发工具 中有过演示,这里不再说明。

倒计时

在正式开始进度条程序之前,我们先来尝试设计部一个倒计时程序。这样方便我们理解构建项目。如果不想完成这部分,可以直接点击目录跳转到进度条项目。

下面我们创建一个 test.c 文件进行编写。

cpp 复制代码
#include "processBar.h"

int main()
{
    return 0;
}

1、打印数字

要看到倒计时,首先我们肯定要能看的到倒计时的数字。我们假设一共需要倒计时9秒钟。

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 9;
    while(cnt>=0)
    {
        printf("%d",cnt);
        cnt--;
    }
    return 0;
}

编译后运行看一下,发现成功打印:

2、模拟时间

但是我们发现这是一下子全部打印出来了,所以我们需要模拟一下时间,让程序一秒钟打印一个数字;这里我们使用sleep。在Linux中,sleep的参数单位是秒,这和windows系统不同。

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 9;
    while(cnt>=0)
    {
        printf("%d",cnt);
        fflush(stdut);//刷新打印
        cnt--;
        sleep(1);//间隔一秒
    }
    return 0;
}

我们再次运行就能成功看到每隔一秒打印一个数字了。

3、覆盖

不过倒计时并不会全部把数字显示出来;我们如何实现只看到一个数字?这就需要将前面已经打印的内容进行覆盖。前面我们在打印的时候是顺着后面打印的,因为光标出现在打印的数字后面。要想实现覆盖,就需要实现打印时的光标出现在打印的数字左边(前面)。我们这里使用\r,表示将光标移动到当前行的开头。这样我们就能成功实现覆盖操作。

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 9;
    while(cnt>=0)
    {
        printf("%d\r",cnt);//使用/r将光标移动到当前行开头实现覆盖
        fflush(stdut);//刷新打印
        cnt--;
        sleep(1);//间隔一秒
    }
    return 0;
}

我们也可以加上在最后打印一个换行符(看着更舒服):

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 9;
    while(cnt>=0)
    {
        printf("%d\r",cnt);
        fflush(stdut);//刷新打印
        cnt--;
        sleep(1);//间隔一秒
    }
    printf("\n");
    return 0;
}

4、打印两位数

如果我们需要倒计时的时间为两位数,我们将cnt的值改为10,再次运行看一下:

这个时候问题出现了,10的第二位0会一直保持在屏幕上。要解决这个问题,只需要做出如下改动:

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 10;
    while(cnt>=0)
    {
        printf("%2d\r",cnt);
        fflush(stdut);//刷新打印
        cnt--;
        sleep(1);//间隔一秒
    }
    printf("\n");
    return 0;
}

我们将%d改为了%2d,这样表示按至少2个字符宽度输出整数。因为10开始是表示两个字符,它不是我们看到的10 ,而是一个1和一个0。所以我们使用%2d就可以打印两个字符,实现二位数的倒计时。以此类推,三位数,甚至四位数倒计时也可以实现。不过一般情况不会显示那么大的数字。

不过,还可以改进一点。我们发现按照上面的运行之后,打印的数字会停留在第二个字符位置。所以我们再改成 %-2d,让打印的数字进行左对齐,运行后发现没有问题。

cpp 复制代码
#include "processBar.h"

int main()
{
    int cnt = 10;
    while(cnt>=0)
    {
        printf("%-2d\r",cnt);
        fflush(stdut);//刷新打印
        cnt--;
        sleep(1);//间隔一秒
    }
    printf("\n");
    return 0;
}

这样我们就成功实现了倒计时。

进度条

1、准备工作

下面我们打算在processbar函数里面进行主要功能的代码实现。所以我们需要在processbar.h文件中对该函数进行声明,在process.c文件中写入主要代码;最后要在main.c文件中实现调用。

所以在头文件中:

cpp 复制代码
#pragma once

#include <stdio.h>

extern void processbar();

声明一个processbar函数。

在processBar.c文件中,先写好processbar函数;这里为了方便测试,我们先在函数中写一个打印语句:

cpp 复制代码
#include "processBar.h"

void processbar()
{
    printf("Hello processBar!\n");
}

最后在main函数中进行对processbar函数的调用:

cpp 复制代码
#include "processBar.h"

int main()
{
    processbar();
    return 0;
}

关于makefile文件的配置我们不做修改。

下面我们make一下,看能否正常运行:

一切正常。

下面我们直接实现processbar函数即可。

2、函数实现

我们可以将进度条分成100份,用字符来表示进度,每一个字符表示%1;在后面进度增加的时候,将光标移动到当前字符的左边,用新的字符将前面的覆盖,实现递增效果。

所以我们需要一个数组来存储这些字符内容,并通过循环来实现打印。

我们先定义进度条打印的字符,假设为#,设置数组的大小为102;打开头文件:

cpp 复制代码
#pragma once

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define NUM 102
#define STYLE '#'//进度条字符

extern void processbar();

打开函数文件,我们先使用while循环打印数组:

cpp 复制代码
#include "processBar.h"

void processbar()
{
    char bar[NUM];
    memset(bar,'\0',sizeof(bar))//初始化数组

    int cnt = 0;
    while(cnt <= 100)
    {
        printf("%s\n",bar);//打印字符串
        bar[cnt++] = STYLE;
        sleep(1);//模拟间隔
    }
}

这里我们先用\n换行符查看一下是否实现增加。

make后运行一下,可以看到现在会一行一行地增加字符:

下面我们需要让其实现覆盖。

和前面的倒计时一样,我们需要使用\r来实现光标跳转。此外,还需要进行刷新。

\n为换行,光标跳转到开头并刷新,而\r是只跳转光标。没有\n,就没有立即刷新,因为显示模式是行刷新方式。所以我们还需要使用fflush刷新。

cpp 复制代码
#include "processBar.h"

void processbar()
{
    char bar[NUM];
    memset(bar,'\0',sizeof(bar))//初始化数组

    int cnt = 0;
    while(cnt <= 100)
    {
        printf("%s\r",bar);//打印字符串
        fflush(stdout);//刷新 
        bar[cnt++] = STYLE;
        sleep(1);//模拟间隔
    }
}

我们再次运行就发现可以正常覆盖了。

3、usleep

我们发现一秒钟打印一个字符似乎有点太慢了,要观察程序到结束要耗时一分多钟。因此我们将sleep换成usleep可以让程序运行时间缩短。

usleep和sleep都是进行休眠。只不过usleep的参数单位是微秒。我们设置时间间隔为0.1秒,而进制关系为:1 s = 1000000微秒。

所以我们修改一下代码:

cpp 复制代码
#include "processBar.h"

void processbar()
{
    char bar[NUM];
    memset(bar,'\0',sizeof(bar))//初始化数组

    int cnt = 0;
    while(cnt <= 100)
    {
        printf("%s\r",bar);//打印字符串
        fflush(stdout);//刷新 
        bar[cnt++] = STYLE;
        usleep(100000);//模拟间隔
    }
}

4、预留空白

我们为整个进度条加上[]框:

cpp 复制代码
#include "processBar.h"

void processbar()
{
    char bar[NUM];
    memset(bar,'\0',sizeof(bar));//初始化数组

    int cnt = 0;
    while(cnt<=100)
    {
        printf("[%-100s]\r",bar);
        fflush(stdout);
        bar[cnt++] = STYLE;
        usleep(100000);
    }
    printf("\n");
}

加上-号表示左对齐。

5、显示百分比

除了字符显示进度,我们还需要设置一个百分比的数字显示。

cpp 复制代码
#include "processBar.h"

void processbar()
{
    char bar[NUM];
    memset(bar,'\0',sizeof(bar));//初始化数组

    int cnt = 0;
    while(cnt<=100)
    {
        printf("[%-100s][%d%%]\r",bar,cnt);//%%表示%号
        fflush(stdout);
        bar[cnt++] = STYLE;
        usleep(100000);
    }
    printf("\n");
}

上面的进度条我们使用的是#来做加载字符;我们也可以改变风格,使用其他的字符来展示。例如=号或-号等等。

相关推荐
cccyi72 小时前
Linux 序列化技术、自定义协议实现及守护进程
linux·serialization·daemon
adnyting2 小时前
【Linux日新月异(十)】CentOS 7 文件系统结构深度解剖:从根到叶的完整指南
linux·运维·centos
李玮豪Jimmy2 小时前
Day18:二叉树part8(669.修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树)
java·服务器·算法
大锦终2 小时前
【Linux】高级IO
linux·服务器·网络·c++
xiaoxue..3 小时前
用 Node.js 手动搭建 HTTP 服务器:从零开始的 Web 开发之旅!
服务器·前端·http·node.js
LCG元3 小时前
Linux 下高效开发环境搭建:VSCode Remote + 容器开发
linux
哈里谢顿3 小时前
深入理解 Linux 系统 PATH 目录:从理论到实践
linux
可可苏饼干3 小时前
TOMCAT
java·运维·学习·tomcat
拾忆,想起4 小时前
Dubbo监控中心全解析:构建微服务可观测性的基石
java·服务器·网络·tcp/ip·微服务·架构·dubbo