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

news2025/6/1 12:02:51

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

  • 进度条小程序
    • GitHub地址
  • 前言
  • 前置知识
    • 回车换行(CR/LF)的深度解析
      • 历史渊源与技术规范
      • 在进度条/倒计时中的应用
    • 缓冲区机制的全面剖析
      • 缓冲区引入
      • 缓冲类型对比
      • 进度条开发中的关键控制
  • 进度条实现
    • 以小见大——倒计时
      • 倒计时最终效果演示
      • 错误演示
        • 位宽不够带来的影响
        • 设置位宽后不反转带来的影响
        • 不使用\r回车带来的影响
      • 总结回顾倒计时
    • 进度条架构设计
      • 组件关系图
      • 核心数据结构
    • 版本迭代解析
      • v1版本悟原理
        • progressBar.h头文件
        • progressBar.c源文件
        • main.c调用
      • V2版本求拓展
        • progressBar.h头文件
        • progressBar.c源文件
        • main.c模拟多任务调度实现
        • 最终效果演示
    • Makefile配置要点
  • 结语

进度条小程序

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单独完成换行

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

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

进度条开发中的关键控制

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

进度条实现

以小见大——倒计时

倒计时最终效果演示

在这里插入图片描述

  • 代码如下:
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数组操作

核心数据结构

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

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

版本迭代解析

v1版本悟原理

progressBar.h头文件
#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源文件
#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调用
#include <unistd.h>
#include "progressBar.h"

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

效果如下
在这里插入图片描述

V2版本求拓展

  • 进度条最常见的适用场景就是在下载任务中,具体的运行方式应该是:
    • 下载任务向进度条函数传递下载任务已完成的进度百分比,进度条函数根据比率动态显示
    • 因此下载任务内一定要反复调用进度条程序。
  • 我们可以传入进度条函数的地址进行实现,也就是函数指针的回调函数
progressBar.h头文件
#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源文件
#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模拟多任务调度实现
#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配置要点

# 依赖关系
	# 依赖方法
progressBar:*.c  
	@gcc $^ -o $@

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

结语

关键知识点回顾

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

扩展应用场景

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从Homebrew找到openssl.cnf文件并拷贝到Go项目下使用

安装OpenSSL 在 macOS 上下载和安装 OpenSSL 最常见和推荐的方式是使用 Homebrew&#xff0c;这是一个 macOS 缺失的包管理器。 如果您还没有安装 Homebrew&#xff0c;请先安装它。安装 Homebrew 后&#xff0c;安装 OpenSSL 只需要一条命令。 步骤 1&#xff1a;安装 Home…

微信小程序一次性订阅封装

封装代码如下&#xff1a; export async function subscribeMessage(tmplIds: string[]): Promise<ISubscribeMessagePromise> {// 模板ID// 1、获取设置状态const settings (await wx.getSetting({ withSubscriptions: true })).subscriptionsSetting || {}console.log…

安全帽检测算法AI智能分析网关V4守护工地/矿山/工厂等多场景作业安全

一、方案概述​ 在工业生产与建筑施工场景中&#xff0c;安全帽是保障人员安全的重要装备。但传统人工巡检效率低、易疏漏&#xff0c;难以满足现代安全管理需求。AI智能分析网关V4安全帽检测方案&#xff0c;借助人工智能与计算机视觉技术&#xff0c;实现作业现场安全帽佩戴…

Python自动化之selenium语句——打开、关闭浏览器和网页

目录 一、打开谷歌浏览器 1.双击桌面的Pycharm工具 2.新建Python文件&#xff0c;输入文件名 3.新建的Python文件如下 4.安装selenium库 5.导入包 二、打开网页、关闭网页、关闭浏览器 1.导入增加一个时间包 2.使用函数打包之前写的浏览器的配置 3.调用 4.打开百度网…

【数据结构】--二叉树--堆(上)

一、树的概念和结构 概念&#xff1a; 树是一种非线性的数据结构&#xff0c;他是由n(n>0)个有限结点组成一个具有层次关系的集合。其叫做树&#xff0c;是因为他倒过来看就和一棵树差不多&#xff0c;其实际上是根在上&#xff0c;树枝在下的。 树的特点&#xff1a; 1…

多线程(5)——单例模式,阻塞队列

目录 单例模式饿汉模式懒汉模式—单线程版懒汉模式—多线程版&#xff08;经典面试题&#xff09;懒汉模式—多线程版&#xff08;改进&#xff09; 阻塞队列阻塞队列是什么生产者消费者模型标准库中的阻塞队列-BlockingQueue阻塞队列实现 单例模式 单例模式是一种设计模式&am…

视频监控汇聚平台EasyCVR工业与安全监控:防爆摄像机的安全应用与注意事项

石油、化工、煤矿等行业存在易燃易爆气体、粉尘&#xff0c;普通监控设备易因电火花、高温引发爆炸火灾。随着工业规模扩大&#xff0c;安全生产监控需求激增&#xff0c;防爆摄像机成为保障安全的关键。加之国家法规与行业标准对危险环境监控设备要求严格&#xff0c;规范其应…

基于 Redis 实现分布式锁:原理及注意事项

文章目录 基于 Redis 实现分布式锁&#xff1a;原理及注意事项基于 Redis 实现分布式锁的原理Redis 分布式锁的过期时间和锁续期机制如何防止锁被其他 goroutine 删除&#xff1f;Redis 分布式锁存在的单点故障问题&#xff1a;基于 RedLock 的解决方案高并发场景中 Redis 分布…

手机设备多?怎样设置IP保证不关联

在移动互联网时代&#xff0c;多设备运营&#xff08;如电商、游戏工作室、社交媒体矩阵&#xff09;常面临IP关联风险&#xff0c;轻则账号受限&#xff0c;重则封禁。以下提供6种高效设置独立IP的方法&#xff0c;结合技术原理与实操建议&#xff0c;助您打造稳定合规的运营环…

哈尔滨工业大学计算机系统大作业程序人生-Hello’s P2P

摘 要 文章以C语言程序设计经典案例hello.c为研究对象&#xff0c;系统解析程序在计算机系统中的完整生命周期。剖析源代码通过预处理、编译、汇编、链接四阶段演化为可执行目标程序的编译系统工作机制&#xff0c;继而从进程视角揭示程序运行时计算机体系结构的协同运作&…

Linux系统管理与编程24:基础条件准备-混搭“本地+阿里云”yum源

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 1.添加宿主机共享文件夹 Linux虚拟机可以和宿主机共享文件夹&#xff0c;这样有利于工具文件的共享。具体操作如下&#xff1a; 1&#xff09;vmware workstation共享文件夹 虚拟机…

如何在 Windows 10 PC 上获取 iPhone短信

您可以轻松地将媒体数据从 iPhone 传输到 Windows 计算机&#xff0c;并直接访问计算机上的数据。但是&#xff0c;您可以在 Windows 10 PC 上接收 iPhone 短信吗&#xff1f;有什么功能或工具支持它吗&#xff1f;如果您发现在 Windows 10 PC 上接收 iPhone 消息很困难&#x…

Linux 系统中的软链接与硬链接

目录 一、什么是软链接&#xff1f; 1. 创建软链接 2. 软链接的特性 3. 软链接的用途 二、什么是硬链接&#xff1f; 1. 创建硬链接 2. 硬链接的特性 3. 硬链接的用途 4. 目录硬链接的特殊性 ​编辑 三、软链接与硬链接的区别 1. inode 编号 2. 路径依赖 3. 删除行…

Python爬虫第22节- 结合Selenium识别滑动验证码实战

目录 一、引言 二、滑动验证码原理与反爬机制 2.1 验证码原理 2.2 反爬机制 三、工程实战&#xff1a;滑动验证码识别全流程 3.1 工程准备 3.1.1 环境依赖 3.1.2 目标网站与验证码识别案例 3.2 核心破解流程 3.2.1 自动化打开网页与登录 3.2.2 获取验证码图片&#…

Escrcpy(安卓手机投屏软件) v1.29.6 中文绿色版

在数字设备日益普及的今天&#xff0c;用户对于设备的控制和管理需求也在不断增加。对于Android设备用户来说&#xff0c;Escrcpy这款强大的工具无疑是一个福音。它不仅提供了直观的图形化界面&#xff0c;让用户能够轻松显示和控制自己的Android设备&#xff0c;还以完全免费开…

Linux:深入理解网络层

网络层在复杂的网络环境中确定一个合适的路径.传输到指定的网络中 一、网络层的理解 问题1&#xff1a;为什么要有网络层的概念呢&#xff1f;&#xff1f; ——>我们先来讲一个故事&#xff1a; 假设我在学校里被誉为数学大神&#xff0c;是因为我的数学有考满分的能力&…

Linux_编辑器Vim基本使用

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;LInux_st 小伞的主页&#xff1a;xiaosan_blog 制作不易&#xff01;点个赞吧&#xff01;&#xff01;谢谢喵&#xff01;&a…

vue展示修改前后对比,并显示修改标注diff

动态父组件 <template><el-buttontype"primary"size"small"plainclick"showDiffDialog(subItem)">查看修改内容</el-button><TextDiffDialogv-model:visible"diffDialogVisible":before"currentDiffItem?.…

LiveWallpaperMacOS:让你的 Mac 桌面动起来

随着桌面美化需求的不断提升,用户对于桌面壁纸的要求已经不再局限于静态图片。越来越多的 Mac 用户希望桌面能像 Windows 一样,拥有动态壁纸,展现个性、提升体验。LiveWallpaperMacOS 正是这样一款让你的 Mac 桌面焕发活力的开源项目。 本文将详细介绍 LiveWallpaperMacOS …

[预训练]Encoder-only架构的预训练任务核心机制

原创文章1FFN前馈网络与激活函数技术解析&#xff1a;Transformer模型中的关键模块2Transformer掩码技术全解析&#xff1a;分类、原理与应用场景3【大模型技术】Attention注意力机制详解一4Transformer核心技术解析LCPO方法&#xff1a;精准控制推理长度的新突破5Transformer模…