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

Linux的小程序——进度条

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


(一)回车和换行

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

而在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:这是我们给这个函数指针类型起的别名。

运行结果:


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

http://www.dtcms.com/a/305809.html

相关文章:

  • 相亲小程序匹配与推荐系统模块搭建
  • 元码智能“大眼睛”机器人首发,智启生活新纪元!
  • Netty的Http解码器源码分析
  • Tdesign-React 模板面包屑如何放到 Header头部
  • 深度学习:预训练和warm up的区别
  • React 开发中遇见的低级错误
  • 线性代数常见的解题方法
  • JS中defineProperty/Proxy 数据劫持 vue3/vue2双向绑定实现原理,react 实现原理
  • 在 React + Ant Design 项目中实现文字渐变色
  • 技术速递|GitHub Copilot 的 Agent 模式现已全面上线 JetBrains、Eclipse 和 Xcode!
  • 国产化再进一步,杰和科技推出搭载国产芯片的主板
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(5)
  • JavaScript数据类型
  • 高密度客流特征识别误差↓76%!陌讯多模态轻量化算法实战解析
  • Linux初始及常见指令使用
  • Redis学习------缓存雪崩
  • 解决Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required报错问题
  • 视频生成模型蒸馏的方法
  • Orange的运维学习日记--19.Linux文件归档和备份
  • 15.10 单机8卡到千卡集群!DeepSpeed实战调参手册:A100训练效率翻倍,百万成本优化实录
  • 南水北调东线工程图件 shp数据
  • 三目云台全景监控画面实现三个画面联动
  • 【图像处理】直方图均衡化c++实现
  • python基础语法2,程序控制语句(简单易上手的python语法教学)(课后练习题)
  • Python3与MySQL的PyMySQL连接与应用
  • 【Spring Boot 快速入门】四、MyBatis
  • Nestjs框架: 关于 OOP / FP / FRP 编程
  • 关于神经网络CNN的搭建过程以及图像卷积的实现过程学习
  • OSS-服务端签名Web端直传+STS获取临时凭证+POST签名v4版本开发过程中的细节
  • 修改Windows鼠标滚轮方向