Linux的小程序——进度条

发布于:2025-08-01 ⋅ 阅读:(13) ⋅ 点赞:(0)

为了写出这个小程序我们先来了解几个知识点


(一)回车和换行

先以写作文为例子了解一下,当在一行中写了一半,由此处位置往下一行的操作叫做换行,回到该行的开头位置为回车。

而在c语言中\n帮我们完成了换行和回车两个动作,那单纯回车是——\r


(二)缓冲区概念

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

int main()
{
    printf("hello world\n");
    sleep(3);
    return 0;
}

在LInux中如何查看sleep,man sleep即可

有什么现象:

即先打印hello world在休眠三秒

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

int main()
{
    printf("hello world");
    sleep(3);
    return 0;
}

现象如下:

看到的是他是先休眠了,在打印的。那他的顺序真的是这样的吗?
不对,在 C 语言中,printf("hello world") 先执行,但输出内容会被暂存在标准输出缓冲区(stdout 缓冲区) 中,并未立即显示到屏幕。随后执行 sleep(3) 进行休眠,3 秒后程序结束时,缓冲区会被自动刷新,内容才显示出来。这就是为什么看起来像是 “先休眠再打印” 的原因。

为什么需要缓冲区?

标准输出(stdout)默认是行缓冲模式

  • 当输出内容包含换行符 \n 时,会自动刷新缓冲区
  • 若没有换行符,会积累到一定大小(通常是 4096 字节)或程序结束时才刷新

代码中 printf 没有换行符,因此内容暂存在缓冲区,直到程序结束才输出,中间被 sleep(3) 打断,造成了执行顺序的 “错觉”。

那如果我要强制刷新怎么办了?

先了解C语言程序中默认会打开3个输入输出流,即标准输入,标准输出,标准错误。标准输出为显示器(stdout),其中Liunx下一切皆文件,那显示器也是文件。
可以用 fflush 函数手动刷新指定流,立即输出缓冲区内容:

#include <stdio.h>
#include <unistd.h>  // 包含sleep函数的头文件

int main()
{
    printf("hello world");
    fflush(stdout);  // 强制刷新标准输出缓冲区
    sleep(3);        
    return 0;
}

此时现象:

 为了看起来更顺眼我们可以在后面加一个换行。

又有上面的知识点我们可以写一个倒计时

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

int main()
{
    int cut=10;
    while(cut>=0)
    {
        printf("%-2d\r",cut);
        fflush(stdout);
        cut--;
        sleep(1);
    }
    printf("\n");
    return 0;
}

运行结果:

细节解释:

printf("%-2d\r",cut)为什么是%-2d

因为输入10,显示器会将它显示成1 0

但是我们要的就是10,所以要写成%2d,%2d是指定整数输出时至少占用 2 个字符宽度,但是c语言进行输出显示,默认是右对齐,所以要用%-2d,-符合是强制左对齐。

 \r的作用

回车符,光标回到行首

总结:

  • 每次输出时,%-2d 确保无论数字是一位还是两位,都占用固定的 2 个字符位置。
  • 左对齐的补空格方式,能保证光标回到行首后,新数字会完全覆盖上一次的输出(不会留下残留字符)。

(三)进度条的实现 

(1) 准备工作

依旧使用多文件,建立一个processbar.h,processbar.c ,test.c

其中processbar.c ,test.c包含processbar.h

(2) 实现

1. processbar.h代码:

#pragma once
#include <stdio.h>

#define NUM 102
#define STYLE '#'

extern void processbar();

小细节:

1.1 extern void processbar();

这里的extern声明表示processbar()函数在其他文件中定义,当前文件仅作声明。在单个模块(单文件)中确实可以省略,但在多文件项目中,使用extern可以清晰地表明这是一个外部函数,增强代码的可读性和规范性

1.2. #define NUM 102

保证每次输出的字符比上次多,定义一个数组,其中NUM为数据开辟的空间,使用宏定义方便统一修改,且可以避免在代码中硬编码数值,提高可维护性。

1.3. #define STYLE '#'

定义进度条的显示字符,因为风格多变,当需要改变进度条样式时,只需修改这一处定义即可
增强了代码的灵活性

 2. test.c代码:

调用这个小程序

#include "processbar.h"
#include <unistd.h>

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

3. processbar.c程序:

#include "processbar.h"
#include<string.h>
#include<unistd.h>

const char* lable="|/-\\-";//这个加载的那个圈圈

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

    int cnt=0;
    while(cnt<=100)
    {
        printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%5]);
        fflush(stdout);//强制刷新输出缓冲区,确保进度条实时显示
        bar[cnt++]=STYLE;
        usleep(100000);
    }
    printf("\n");
}

运行结果: 

小细节:

3.1. const char* b="|/-\\-";

定义了一个字符数组,包含几个特殊字符,用于实现进度条前的 "旋转加载" 效果(类似转圈动画)。这里的\\是转义字符,表示一个反斜杠。

3.2. usleep

Linux 系统中用于暂停程序执行的函数,声明在<unistd.h>头文件中。

作用:

让当前进程暂停指定的微秒数(1 微秒 = 1/1000000 秒)

3.3. "[%-100s][%d%%][%c]\r"

  • [%-100s]:左对齐的 100 字符宽度区域,显示当前进度
  • [%d%%]:显示当前百分比,%%是转义字符,表示%
  • [%c]:显示旋转动画字符
  • \r:回车符,使光标回到行首,实现覆盖输出效果

4. makefile文件:

processbar:processbar.c test.c
	@gcc -o $@ $^
.PHONY:	clean
clean:
	rm -f processbar

小细节:

 4.1processbar:processbar.c test.c

  • 这是一个规则,processbar 是目标(可执行文件),processbar.c 和 test.c 是依赖文件
  • 意思是:要生成 processbar 这个可执行文件,需要先有 processbar.c 和 test.c 这两个源文件
  • 多依赖文件之间用空格隔开

4.2@gcc -o $@ $^

  • @表示执行命令时不在终端显示该命令本身
  • $@ → 目标文件名(要生成的结果)
  • $^ → 所有依赖的源文件(用来生成目标的原材料)

4.3.PHONY:    clean

  • 声明 clean 是一个伪目标(不是实际的文件)
  • 作用是避免当前目录下有同名文件 clean 时,导致该规则失效

(3)进度条的调式和优化

上述进度条存在一定缺陷:

1.功能封装不合理,复用性差

  • 进度条逻辑与控制强耦合:用户无法根据实际场景(如下载、解压等不同任务)控制进度条的更新时机和速度,只能被动执行函数内置的固定节奏。
  • 无法适配实际业务场景:实际开发中,进度条的更新应与任务进度(如下载字节数、处理数据量)绑定,而初始代码的进度完全由函数内部的 cnt 自增控制,与真实任务进度脱节,用户无法通过外部参数传递实际进度。

2. 扩展性差,难以二次使用

  • 单次使用限制:初始代码中,processbar() 函数的进度条状态(数组 bar)是局部变量,每次调用函数都会重新初始化并执行一次完整的进度条动画。这导致它只能正确运行一次,若用户需要在程序中多次显示进度条(如多个任务依次执行),必须重新调用函数,但函数内部逻辑固定,无法重置或复用状态。

3. 用户交互设计不合理

  • 参数缺失,用户体验差:用户使用时,只能调用 processbar() 函数执行一个固定的进度动画,无法传递实际任务的总进度(如 “总大小 1000MB,当前下载 500MB”),导致进度条与真实任务无关,仅为一个 “虚假动画”。
  • 状态不透明:用户无法知道进度条当前的实际进度(如百分比),只能被动等待动画结束,缺乏对任务进度的感知。

4. 细节实现的局限性

  • 局部变量导致状态无法延续:进度条的字符数组 bar是 processbar() 函数的局部变量,每次函数调用都会重新初始化,因此无法在多次调用中保留进度状态,导致无法实现 “分阶段更新进度”(如每次调用更新 10%)。

为了解决这个问题我将代码进行了完善,我想要解决上面问题和形成如下进度条:

processbar.h

#pragma once
#include <stdio.h>

#define NUM 102
#define TOP 100
#define STYLE '='
#define end '>'

extern void processbar(int rate);
extern void Initbar();

processbar.c

#include "processbar.h"
#include<string.h>
#include<unistd.h>

const char* lable="|/-\\-";
char bar[NUM];

#define GREEN "\033[0;32;32m"
#define END "\033[m"

void processbar(int rate)
{
    if(rate<0||rate>100)
        return;

    int len=strlen(lable);

    printf(GREEN"[%-100s]"END"[%d%%][%c]\r",bar,rate,lable[rate%len]);
    fflush(stdout);
    bar[rate++]=STYLE;
    if(rate<100)
        bar[rate]=end;
}

void Initbar()
{
    //清空为'\0',避免首次使用时的随机乱码
    memset(bar,'\0',sizeof(bar));
}

小细节:

1.void Initbar()函数

用来解决扩展性差,难以二次使用问题,在调用进度条之前先调用这个函数。此时在processbar.h中也得进行声明。

2.void processbar(int rate)
接收 rate 参数(0~100 的百分比),进度由外部传入,调用者可根据实际场景(如下载了 30%)灵活控制显示内容。

3.全局数组 char bar[NUM]

配合 Initbar() 初始化函数。数组生命周期与程序一致,进度状态可跨多次 processbar() 调用累积,支持多次任务(例如连续下载多个文件时,每次调用 Initbar() 重置即可)。

4.#define GREEN "\033[0;32;32m"和#define END "\033[m"

ANSI 颜色控制宏:设置绿色文本,END用于恢复默认颜色

这些是博主搜的颜色替换,大家可以自己选然后改变

ANSI 颜色控制码 - 前景色
#define FG_BLACK   "\033[30m"   // 黑色
#define FG_RED     "\033[31m"   // 红色
#define FG_GREEN   "\033[32m"   // 绿色
#define FG_YELLOW  "\033[33m"   // 黄色
#define FG_BLUE    "\033[34m"   // 蓝色
#define FG_MAGENTA "\033[35m"   // 洋红色
#define FG_CYAN    "\033[36m"   // 青色
#define FG_WHITE   "\033[37m"   // 白色

ANSI 颜色控制码 - 背景色
#define BG_BLACK   "\033[40m"   // 黑色背景
#define BG_RED     "\033[41m"   // 红色背景
#define BG_GREEN   "\033[42m"   // 绿色背景
#define BG_YELLOW  "\033[43m"   // 黄色背景
#define BG_BLUE    "\033[44m"   // 蓝色背景
#define BG_MAGENTA "\033[45m"   // 洋红色背景
#define BG_CYAN    "\033[46m"   // 青色背景
#define BG_WHITE   "\033[47m"   // 白色背景

test.c

#include "processbar.h"
#include <unistd.h>

typedef void (*callback_t)(int);

void downLoad(callback_t cb)
{
    int total=1000;
    int curr=0;
    while(curr<=total)
    {
        usleep(100000);
        int rate=curr*100/total;

        cb(rate);

        curr+=10;
    }
    printf("\n");
}

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

小细节:

1. typedef void (*callback_t)(int);

是 C 语言中定义函数指针类型的语法

作用:给一个 “参数为 int、返回值为 void 的函数指针” 起一个别名 callback_t,简化函数指针的使用

具体解释:

void (*)(int):这是一个函数指针的 “原型”,表示:

  • 指向的函数返回值为 void(无返回值)。
  • 指向的函数接收一个 int 类型的参数。

callback_t:这是我们给这个函数指针类型起的别名。

运行结果:


以上就是进度条的知识点了,后续的完善工作我们将留待日后进行。希望这些知识能为你带来帮助!如果觉得内容实用,欢迎点赞支持~ 若发现任何问题或有改进建议,也请随时与我交流。感谢你的阅读!     


网站公告

今日签到

点亮在社区的每一天
去签到