【Linux我做主】进度条小程序深度解析

Linux下C语言进度条程序深度解析

进度条小程序

GitHub地址

有梦想的电信狗

前言

​ 在Linux系统编程中,控制台交互的视觉反馈是提升用户体验的重要环节。进度条作为经典的人机交互组件,在软件安装、文件传输、数据处理等场景中具有广泛应用价值。本文将以Linux环境下C语言实现的进度条程序为切入点,深入探讨控制台输出控制、缓冲区机制、函数指针应用等核心技术。通过三个版本迭代的代码解析(基础版/V1、模拟多任务版/V2),读者将掌握从原理到实践的完整知识链路。


前置知识

回车换行(CR/LF)的深度解析

在C语言中,我们使用\n来表示换行,这其实是C语言帮我们做了处理。实际上,回车和换行其实是两个动作。

C语言中用\n来表示回车和换行。

以上图片深入阐述了回车和换行概念以及和区别。

  • \r:回车,光标回到当前行的最开始 。C语言中用\r来表示仅回车。
  • \n:换行,光标垂直向下移动一行,叫做换行。

历史渊源与技术规范

  • ASCII规范定义:CR(Carriage Return,\rASCII 13)将光标移动到行首
  • LF(Line Feed,\nASCII 10)使光标下移一行
  • Windows系统采用CRLF组合实现新行操作
  • Linux/Unix系统使用LF单独完成换行

在进度条/倒计时中的应用

c 复制代码
printf("%-3d\r", cnt);  // 关键代码示例

此代码实现:

  1. 使用%-3d保证3字符宽度左对齐
    • 3表示该值位宽为3,C语言默认为右对齐,用-来表示左对齐。
  2. \r使每次输出回到行首
  3. 配合fflush(stdout)强制刷新缓冲区
  4. 实现原地更新的数字倒计时效果

缓冲区机制的全面剖析

缓冲区引入

先看如下两个例子:

  • 有换行符\n时显示器直接刷新
  • 无换行符时,像是先执行了sleep再执行printf

    经分析得知:
  • C语言中一定是按顺序执行代码的,因此一定是printf先执行,再执行sleep
  • 那么,在sleep期间,printf函数一定已经执行完了。
  • 那么,sleep期间,hello wolrd在哪里?

综上,hello wolrd一定是被保存起来了!!!

保存hello wolrd,必然 需要一块内存空间,这块内存空间被称为缓冲区

  • 缓冲区就是由C语言维护的一段内存。

C程序运行时,默认会帮助我们打开三个输入输出流

  • stdin:标准输入
  • stdout:标准输出(默认是显示器)
  • stderr:标准错误

C语言的默认行为是在程序退出时,再刷新缓冲区。

printf打印消息,是向stdout输入,消息暂存在了stdout中,当我们不想让消息暂存在缓冲区中,而是想直接刷新stdout的内容到显示器时,可以使用fflush刷新,默认stdout在程序结束时刷新,使用fflush可以强制进行刷新输入输出流

  • 以下:
  • 此时printf("hello world")没有\n
  • 使用fflush(stdout)强制将缓冲区中的数据刷新到显示器上

通过以上两个例子,我们已经对缓冲区有了一个大概的理解了。

缓冲类型对比

缓冲类型 特征 典型应用场景
全缓冲 缓冲区满时刷新 文件操作
行缓冲 遇到换行符或缓冲区满时刷新 终端输出(默认)
无缓冲 立即输出 标准错误流stderr

进度条开发中的关键控制

  • 手动刷新机制:
c 复制代码
fflush(stdout);  // 强制立即输出缓冲区内容

进度条实现

以小见大------倒计时

倒计时最终效果演示

  • 代码如下:
c 复制代码
void test() {
    //实现一个倒计时
    int cnt = 100;
    while (cnt >= 0) {
        // printf("%-2d\r", cnt);//使用\r回车会导致三位数只刷新了两位数
        printf("%-3d\r", cnt);  // %3d\r 可以实现在行的开头更新数字  -相当于反转  确保是左对齐
        fflush(stdout);
        --cnt;
        sleep(1);
    }
    printf("\n");
}
int main() {
    test();
    return 0;
}
  • 当前设置倒计时从100开始
  • 关于printf中的参数%-3d\r的解释
    • %d的作用:表示输出数字cnt,用于显示倒计时
    • 3的作用:用于控制输出显示位宽为3,倒计时的数字是几位,位宽就设置为几。
    • -的作用:设置位宽后,C程序默认为右对齐 ,我们想让数字在当前行的最左侧显示,要用-实现左对齐。
    • \r的作用:用于实现仅回车,回到当前行的开始,覆盖打印实现倒计时的效果。
  • 使用fflush(stdout)将缓冲区内的数据立即刷新出来
  • sleep(1)每隔一秒循环一次

错误演示

位宽不够带来的影响
  • 位宽小于数字的位数时,会出现数字残余的情况。
  • 由于显示器是字符设备,只会一个一个打印字符,123实际上是1 2 3三个字符连在一起表示的
  • 以下是从10开始计数的错误例子
  • 正确设置位宽即可解决
设置位宽后不反转带来的影响
  • 不用-进行反转会导致数字不靠左显示
    • 对于倒计时来说影响甚微,但靠右显示的话,会导致进度条从右向左加载!
不使用\r回车带来的影响
  • 不使用\r会导致数字接连不断的出现,不符合倒计时的效果。

总结回顾倒计时

printf中的格式化控制参数缺一不可

  • 关于printf中的参数%-3d\r的理解
    • %d表示输出数字cnt,用于显示倒计时
    • 3用于控制输出显示位宽为3,倒计时的数字是几位,位宽就设置为几。
    • 设置位宽后,C程序默认为右对齐 ,我们想让数字在当前行的最左侧显示,要用-实现左对齐。
    • \r用于实现仅回车,回到当前行的开始,覆盖打印实现倒计时的效果。
  • 使用fflush(stdout)将缓冲区内的数据立即刷新出来

进度条架构设计

组件关系图

main函数 模拟downLoad progressbar回调 initBar初始化 bar数组操作

核心数据结构

c 复制代码
#define NUM 102       // 缓冲区长度(含终止符)
#define BODY '='      // 进度条主体字符
#define HEAD '>'      // 进度头部指示符
#define TOP 100       // 进度最大值

typedef void (*callback_t)(int);  // 标准化回调接口 
  • 利用函数指针实现回调。

版本迭代解析

v1版本悟原理

progressBar.h头文件
cpp 复制代码
#pragma once

#include <stdio.h>

//缓冲区长度(含终止符)
#define NUM 102  	  // 102 表示字符数组的长度 0-100 101个字符  末尾是\0, 因此大小是 102
#define BODY '='      // 进度条主体字符
#define HEAD '>'      // 进度头部指示符
#define TOP 100       // 进度最大值

extern void progressbar(int speed);  // extern 声明外部变量时必须加上  函数声明可加可不加
  • #pragma once:防止头文件重复包含
  • #define NUM 102102 表示字符数组的长度,0-100,共101个字符 ,字符串末尾是\0, 因此数组长度是102
  • #define BODY '=':定义进度条的形体为=
  • #define HEAD '>':定义进度条的头部为>
  • #define TOP 100:定义进度条的区间长度,暂定为100

通过宏的方式定义,可以方便的实现进度条样式的修改!

progressBar.c源文件
c 复制代码
#include "progressBar.h"
#include <string.h>
#include <unistd.h>

char bar[NUM] = {0};
const char* label = "|/-\\";

void progressbar(int speed) {
    memset(bar, '\0', sizeof(bar));  //整体将字符串设为\0,可以方便的输出
    int len = strlen(label);

    int cnt = 0;
    while (cnt <= TOP) {
        //没有\n 就没有立即刷新,因为显示器默认是行刷新
        printf("[%-100s][%d%%][%c]\r", bar, cnt, label[cnt % len]);
        //预留出100空间,100s 默认是右对齐,进度条是反的, 用-100s解决
        //用变长的字符串,循环覆盖输出,实现进度条光标移动的效果
        //给进度条跑  %%显示百分号
        fflush(stdout);
        bar[cnt++] = BODY;  //更改进度条的风格
        if (cnt < TOP)
            bar[cnt] = '>';
        // sleep(1);
        usleep(speed);  // 100000微秒,用usleep实现更快的跑完
    }
    printf("\n");  //防止命令行提示符影响效果
}
  • char bar[NUM] = {0}:进度条主体使用长度不断改变的字符串来实现
    • 初始化为{0},这样就不用手动设置\0终止符了,用%s输出变化的字符串
    • 0-100,恰好是char bar[NUM] = {0}字符数组中每个字符的下标
    • 每次循环内
      • cnt会++
      • 利用cnt0-100每个位置的字符都设置为进度条主体=
      • 随着cnt++,数组内字符串的长度也在增长,再通过%s\r回车数组字符串,从而实现进度条的动态增长
  • const char* label = "|/-\\"
    • 通过0-4五个字符的顺序循环输出,实现光标闪动的效果。
  • printf("[%-100s][%d%%][%c]\r", bar, cnt, label[cnt % len])
    • [%-100s]
      • -:确保进度条不反方向增长
      • 100s:预留出100长度,供进度条字符串填充
    • [%d%%]
      • %d:输出进度数字cnt
      • %%:控制输出字符%
    • [%c]:控制循环顺序输出|/-\\中的每个字符,实现光标闪动的效果
      • cnt % len实现0-4的循环
    • \r:实现每次从行首开始输出,实现进度条的动态增长!
  • fflush(stdout):每次printf过后,刷新缓冲区
  • usleep(speed)usleep结合函数参数speed,实现进度条时长的控制
main.c调用
c 复制代码
#include <unistd.h>
#include "progressBar.h"

int main(){
    progressbar(20000);
	return 0;
}

效果如下

V2版本求拓展

  • 进度条最常见的适用场景就是在下载任务中,具体的运行方式应该是:
    • 下载任务向进度条函数传递下载任务已完成的进度百分比,进度条函数根据比率动态显示
    • 因此下载任务内一定要反复调用进度条程序。
  • 我们可以传入进度条函数的地址进行实现,也就是函数指针的回调函数。
progressBar.h头文件
c 复制代码
#pragma once

#include <stdio.h>

//缓冲区长度(含终止符)
#define NUM 102  	  // 102 表示字符数组的长度 0-100 101个字符  末尾是\0, 因此大小是 102
#define BODY '='      // 进度条主体字符
#define HEAD '>'      // 进度头部指示符
#define TOP 100       // 进度最大值

typedef void (*callback_t)(int);  //函数指针类型

extern void progressbar(int rate);  // extern 声明外部变量时必须加上  函数声明可加可不加
extern void downLoad(callback_t cb);
extern void initBar();
  • typedef void (*callback_t)(int)callback_t是函数指针类型,我们可以拆解帮助理解:
  • void (*)(int):其中:
    • * :表示这是一个指针,且必须用括号包裹。否则会被解析为函数返回指针(如 void *func(int) 是返回 void* 的函数)
    • (*):表示这是一个函数指针
      • void:表示该函数的返回值为void,
      • (int):表示该函数的参数类型为一个int
  • typedef void (*callback_t)(int)可以理解为typedef void (*)(int) callback_t
    • typedef:在typedef中,参数名会被省略,只保留类型。因此可以拆解为:
    • typedef void (*)(int) callback_t:将void (*)(int)类型的指针定义为类型别名 callback_t
progressBar.c源文件
c 复制代码
#include "progressBar.h"
#include <string.h>
#include <unistd.h>
// v2 应用
char bar[NUM] = {0};
const char* label = "|/-\\";

void initBar() {
    memset(bar, '\0', sizeof(bar));
}
void progressbar(int rate) {
    if (rate < 0 || rate > 100)
        return;
    int len = strlen(label);
    //用单个字符循环覆盖输出实现光标闪动
    printf("[%-100s][%d%%][%c]\r", bar, rate, label[rate % len]);
    fflush(stdout);
    bar[rate++] = BODY;  //更改进度条的风格
    if (rate < TOP)
        bar[rate] = HEAD;
}

优化亮点

  • 全局状态保存实现多任务支持

  • initBar()提供重置进度条的能力

  • 动态头部指示符(>)增强视觉效果

  • 增加非法进度的判断

    • if (rate < 0 || rate > 100) return
main.c模拟多任务调度实现
c 复制代码
#include <unistd.h>
#include "progressBar.h"
// 模拟下载任务调用进度条
void downLoad(callback_t cb) {		
    int total = 1000;
    int curr = 0;  //目前curr需要从0开始
    while (curr <= total) {
        //进行某种下载任务,模拟时使用手动控制速度
        usleep(50000);
        int rate = curr * 100 / total;
        cb(rate);  	 // 传入参数,回调展示进度
        curr += 10;
    }
    printf("\n");  //防止命令行提示符影响效果
}

int main() {
    printf("downLoan 1:\n");
    downLoad(progressbar);
    initBar();
    printf("downLoan 2:\n");
    downLoad(progressbar);
    initBar();
    printf("downLoan 3:\n");
    downLoad(progressbar);
    initBar();
    return 0;
}
  • 通过任务调用进度条,外部程序通过回调,调用进度条
  • void (callback_t)(int)callback_t是函数指针类型。
    • 相当于typedef void (*)(int) callback_t,把callback_t变成函数指针类型的别名

关键技术

  1. 函数指针实现回调机制
  2. 速率换算算法(curr * 100 / total
  3. 时间控制(usleep微秒级延时)
  4. char bar[NUM]为全局数组,每次模拟download后,需initbar()函数重置保证独立性
  5. 模块化设计,确保了可维护性。
最终效果演示

Makefile配置要点

makefile 复制代码
# 依赖关系
	# 依赖方法
progressBar:*.c  
	@gcc $^ -o $@

# clean和上面是独立的
.PHONY:clean   
clean:
	@rm -f progressBar

结语

关键知识点回顾

  1. 控制台输出控制 :回车符与换行符的灵活运用
  2. 缓冲区机制:行缓冲特性与强制刷新策略
  3. 可视化设计:进度条元素(主体、头部、百分比)的协同
  4. 软件工程实践 :模块化设计、回调机制、多任务支持

扩展应用场景

  1. 大数据处理进度监控
  2. 嵌入式系统固件更新
  3. 自动化测试进度反馈
  4. 游戏加载界面优化

​ 通过本文对Linux下C语言进度条程序 的深度解析,我们系统性地掌握了控制台交互的核心技术。从回车换行符的底层原理缓冲区刷新机制 ,从进度条动态显示模块化设计 ,每一步都揭示了控制台可视化反馈的实现精髓。通过函数指针与回调机制的精妙配合,我们实现了多任务场景下的独立进度管理,展现了C语言在系统编程中的强大灵活性。

​ 本项目的核心价值在于:不仅实现了基础的进度展示功能,更通过版本迭代演进,示范了软件开发的渐进式优化思路。在性能层面,未来可结合纳秒级延时控制多线程安全机制 提升精度;在交互层面,可考虑引入ANSI色彩代码或动态图标可进一步增强用户体验。

分享到此结束啦
一键三连,好运连连!

相关推荐
虾球xz1 小时前
CppCon 2014 学习:Making C++ Code Beautiful
c++·学习
文牧之3 小时前
PostgreSQL 临时表空间
运维·数据库·postgresql
李天琦3 小时前
git查看commit属于那个tag
linux·git·云计算
liulilittle3 小时前
关于DDOS
linux·运维·服务器·网络·ddos·通信
LetsonH4 小时前
Ubuntu 22.04 系统下 Docker 安装与配置全指南
linux·ubuntu·docker
pianmian15 小时前
3D Tiles高级样式设置与条件渲染(3)
linux·服务器·前端
maxruan5 小时前
docker环境添加安装包持久性更新
运维·docker·容器
清晨朝暮6 小时前
【Linux 学习计划】-- 命令行参数 | 环境变量
linux·运维·学习
聂 可 以6 小时前
Nginx基础篇(Nginx目录结构分析、Nginx的启用方式和停止方式、Nginx配置文件nginx.conf文件的结构、Nginx基础配置实战)
linux·运维·nginx
Clownseven6 小时前
用Git管理你的服务器配置文件与自动化脚本:版本控制、变更追溯、团队协作与安全回滚的运维之道
运维·服务器·git