《Linux系统编程》Linux基础开发工具 (三):从零实现动态进度条(附回车、换行与缓冲区详解)

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

[一、两个储备知识:回车换行 / 缓冲区](#一、两个储备知识:回车换行 / 缓冲区)

[1.1 回车(\r)与换行(\n)的本质区别](#1.1 回车(\r)与换行(\n)的本质区别)

[1.2 深入理解行缓冲区运行机制](#1.2 深入理解行缓冲区运行机制)

[1.3 进度条的核心构成元素详解](#1.3 进度条的核心构成元素详解)

二、实战开发:打造动态彩色进度条

[2.1 进度条测试版本(演示原理)](#2.1 进度条测试版本(演示原理))

[2.2 基础版进度条模拟实现(无拓展功能实现)](#2.2 基础版进度条模拟实现(无拓展功能实现))

[2.3 自动化构建流程:Makefile编写](#2.3 自动化构建流程:Makefile编写)

[2.4 头文件设计(process.h):接口函数声明](#2.4 头文件设计(process.h):接口函数声明)

[2.5 核心实现文件(process.c)](#2.5 核心实现文件(process.c))

[2.6 主函数模块(main.c):应用场景测试](#2.6 主函数模块(main.c):应用场景测试)

三、操作实践与效果展示

结束语


一、两个储备知识:回车换行 / 缓冲区

在动手写代码前,必须先理清 2 个关键概念,否则容易出现 "进度条不刷新""换行错乱" 等问题。

1.1 回车(\r)与换行(\n)的本质区别

  • 换行(\n :光标移动到下一行的行首 ,但不会回到当前行开头 ;我们日常使用它的时候其实是 回车+换行 的作用(=/r/n )
  • 回车(\r :光标回到当前行的行首 ,但不会移动到下一行
  • 进度条的核心 是 "在同一行反复覆盖刷新",因此必须用 \r让光标回到行首,再重新打印新的进度信息。

1.2 深入理解行缓冲区运行机制

C语言的 printf 函数默认是**"行缓冲"** ------只有遇到**\n** 、缓冲区满 或**手动刷新(fflush(stdout))**时,才会把缓冲区的内容输出到终端。

示例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() 
{
    printf("hello world!");
    sleep(1);
    return 0;
}
  • 如果只写printf 而不加\n或fflush,内容会一直存在缓冲区,终端看不到任何输出,这就是很多人写进度条 "没反应" 的原因。

注意:虽然没显示出来,但是我们的C语言默认是顺序结构的,一定是先执行 printf 再执行 sleep的。

示例修正

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() 
{
    printf("hello world!");
    fflush(stdout);
    sleep(1);
    return 0;
}

练练手:光标快速回退,完成倒计时功能

cpp 复制代码
#include <stdio.h>      
#include <unistd.h>      
      
int main()      
{      
    int i = 10;      
    while(i >= 0)      
    {      
        printf("%-2d\r", i);      
        fflush(stdout);      
        i--;      
        sleep(1);     
    }    
    printf("\n");    
}      

1.3 进度条的核心构成元素详解

一般一个完整的动态进度条通常包含以下部分:

  • 进度条主体: 用=等字符填充,直观显示完成比例;
  • 百分比: 显示完成进度(0%~100%);
  • 动态光标: 用 | / - \ 循环切换,提示程序正在运行。
  • 附加信息: 如当前进度 / 总进度、传输速度,提升实用性。

二、实战开发:打造动态彩色进度条

基于基础框架,我们先实现一个基础功能的简单进度条,后续慢慢优化实现出 "彩色区分 + 速度显示 + 多场景适配" 的进度条,核心分为头文件、实现文件、主函数三部分。

2.1 进度条测试版本(演示原理)

这个测试版本的其他文件我就不写了,就展示一个proces.c:

cpp 复制代码
void process_v1()    
{     
    char buffer[NUM];    
    memset(buffer, 0, sizeof(buffer));    
    const char* lable = "|/-\\";    
    int len = strlen(lable);    
    
    int cnt = 0;    
    while(cnt <= 100)    
    {    
       // printf("[%s]\r", buffer);    
        printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt % len]);    
        fflush(stdout);    
        buffer[cnt++] = STYLE;      
       usleep(50000);    
    }    
    printf("\n");    
}    

2.2 基础版进度条模拟实现(无拓展功能实现)

cpp 复制代码
//process.h
#pragma once                                                                                                                                                                                 
#include <stdio.h>    
       
void FlushProcess(double current, double total);  

//process.c
#include "process.h"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define NUM 101    
#define STYLE '#'    
    
void FlushProcess(double current, double total)    
{    
    char buffer[NUM];    
    memset(buffer, 0, sizeof(buffer));    
    const char* lable = "|/-\\";    
    int len = strlen(lable);    
    
    int cnt = (int)(current * 100 / total);    
    static int count = 0;    
    int i = 0;    
    for(i = 0; i < cnt; i++)    
    {    
        buffer[i] = STYLE;    
    }    
    
    printf("[%-100s][%.1f%%][%c]\r", buffer, current * 100 / total, lable[count % len]);    
    fflush(stdout);                                                                                                                                                                          
    
    count++;    
}    

//main.c
#include "process.h"                                                                                                                                                                         
#include <stdio.h>
#include <unistd.h>
       
double total = 1024.0;         
double speed = 1.0;              

//使用回调函数               
typedef void (*callback_t)(double current, double total); //函数指针类型 取别名->callback_t
         
void Download(callback_t cb)   
{                     
    double current = 0.0;               
    while(current <= total)  
    {    
        cb(current, total);
        //下载代码                               
        usleep(3000); //充当下载数据
        current += speed;
    }            
    printf("\n");
    printf("download %.2lfMB Done\n", total);
}                              

int main()
{
    Download(FlushProcess);
    return 0;
}         

2.3 自动化构建流程:Makefile编写

为了方便编译和清理,编写 Makefile 实现自动化构建,只需一条命令即可生成可执行文件:我们在之前讲过 Makefile 的编写策略,这里就不多说了直接展示

bash 复制代码
BIN=process.exe                                                                                                                                                                              
SRC=$(shell ls *.c)    
OBJ=$(SRC:.c=.o)    
    
$(BIN):$(OBJ)    
    gcc $^ -o $@    
    
%.o:%.c    
    gcc -c $<    
    
.PHONY:    
clean:    
    rm -f $(OBJ) $(BIN)  

2.4 头文件设计(process.h):接口函数声明

定义进度条回调函数类型和核心接口,方便后续扩展和复用:

cpp 复制代码
#pragma once     
#include <stdio.h>    
    
//void process_v1();    
//void FlushProcess(double current, double total);    
    
// 定义进度条回调函数类型:适配不同场景的进度刷新逻辑    
typedef void (*flush_t)(double total,double current,double speed,const char* userinfo);    
    
// 彩色动态进度条核心接口    
// total:总进度(如文件总大小)    
// current:当前进度(如已下载大小)    
// speed:当前速度(如MB/s)    
// userinfo:附加信息(如单位)    
void Process_version(double total,double current,double speed,const char*userinfo);  

2.5 核心实现文件(process.c)

集成颜色控制、进度计算、动态刷新,是进度条的核心:

cpp 复制代码
#include "process.h"
#include <string.h>
                           
// 进度条配置参数                        
#define NUM 103     // 进度条缓冲区大小(适配100%+额外字符)
#define STYLE '#'    // 进度填充字符                        
                                                            
#define COLOR_GREEN  "\033[32m"   // 绿色                   
#define COLOR_GRAY   "\033[90m"   // 灰色     
#define COLOR_CYAN   "\033[36m"   // 青色     
#define COLOR_RESET  "\033[0m"    // 重置颜色 
#define COLOR_RED     "\033[31m"   // 红色    
#define COLOR_YELLOW  "\033[33m"   // 黄色   
#define COLOR_BLUE    "\033[34m"   // 蓝色   
#define COLOR_MAGENTA "\033[35m"   // 品红
#define COLOR_WHITE   "\033[97m"   // 白色                                               
#define COLOR_BLACK   "\033[30m"   // 黑色

// 彩色动态进度条实现
void Process_version(double total, double current, double speed, const char*userinfo)
{
    // 边界处理:当前进度超过总进度时直接返回
    if(current > total)
    {
        return;
    }
    
    // 1. 计算比率
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));
    double rate = current * 100.0 / total;

    // 2. 填充进度字符
    int cnt = (int)(current * 100 / total);
    int i = 0;
    for(i = 0; i < cnt; i++)
    {
        buffer[i] = STYLE;
    }

    // 动态光标:循环切换,提示程序运行中
    const char* lable = "|/-\\";
    static int count = 0;
    int len = strlen(lable);

    // 3. 彩色打印进度条(分颜色区分不同部分,视觉更清晰)
    //printf("[%-100s][%.1f%%][%c]\r", buffer, current * 100 / total, lable[count % len]);
    printf(COLOR_RED"[%-100s]", buffer);    // 进度主体(红色)
    printf(COLOR_BLUE"[%5.1lf%%]",rate);    // 百分比(蓝色)
    printf(COLOR_CYAN"[%c]",lable[count % len]);  // 动态光标(青色)
    printf(COLOR_YELLOW"| %.1lf/%.1lf,speed: %.1lf%s\r",current,total,speed,userinfo);  // 附加信息(黄色)
    printf(COLOR_RESET);  // 重置颜色,避免污染后续输出

    // 手动刷新缓冲区,确保内容实时显示
    fflush(stdout);
    count++;                                                                                                                                                                                 
}

2.6 主函数模块(main.c):应用场景测试

模拟多场景下载任务,测试进度条的适配性和稳定性:

cpp 复制代码
#include "process.h"                                                                                                                                                                         
#include <stdio.h>
#include <unistd.h>            
#include <stdlib.h>              
#include <time.h>
                                 
// 全局配置(可根据实际场景修改)
double total = 1024.0;                    
double speed = 1.0;            

void Download(double total, flush_t cb)
{
    double current = 0.0;

    // 模拟不同网络速度(模拟实际场景中速度波动)
    double level[] = { 0.05, 0.50, 1.00, 10.0, 1.00, 16.0, 20.0, 12.0, 24.0, 1.00, 26.0, 38.0, 1.00, 41.0, 50.0, 1.00, 65.0 };
    int num = sizeof(level)/sizeof(level[0]);
    while(1)
    {
        // 模拟下载耗时(0.5秒刷新一次)
        usleep(500000);

        // 随机选择当前速度(模拟网络波动)
        speed = level[rand() % num];
        current += speed;

        // 边界处理:进度达到100%时终止
        if(current >= total)
        {
            current = total;  // 确保进度不超过100%
            cb(total,current,speed,"MB/s");
            break;
        }
        else{
            // 进度条未满,刷新进度条
            cb(total,current,speed,"MB/s");
        }
    }
    printf("\n");
}

int main()
{
    // 初始化随机数种子,模拟速度波动
    srand(time(NULL));
    
    // 测试4个不同大小的下载任务,验证进度条适配性
    printf("download: \n");
    Download(total, Process_version);

    printf("download: \n");
    Download(200.0, Process_version);

    printf("download: \n");
    Download(500.0, Process_version);

    printf("download: \n");
    Download(900.0, Process_version);
    return 0;
}                                

三、操作实践与效果展示

实际操作过程

cpp 复制代码
[admin@iZbp12ear9ufvimc78fddkZ processbar]$ ll
total 20
-rw-rw-r-- 1 admin admin    1 May  2 10:10 code.c
-rw-rw-r-- 1 admin admin 2763 May 20 00:17 main.c
-rw-rw-r-- 1 admin admin  139 May  1 15:03 makefile
-rw-rw-r-- 1 admin admin 3123 May 20 00:14 process.c
-rw-rw-r-- 1 admin admin  570 May 20 00:01 process.h
[admin@iZbp12ear9ufvimc78fddkZ processbar]$ make
gcc -c code.c
gcc -c main.c
gcc -c process.c
gcc code.o main.o process.o -o process.exe
[admin@iZbp12ear9ufvimc78fddkZ processbar]$ ./process.exe 
download: 
[####################################################################################################][100.0%][-]| 1024.0/1024.0,speed: 16.0MB/s
download: 
[####################################################################################################][100.0%][|]| 200.0/200.0,speed: 24.0MB/s
download: 
[####################################################################################################][100.0%][/]| 500.0/500.0,speed: 20.0MB/s
download: 
[####################################################################################################][100.0%][\]| 900.0/900.0,speed: 50.0MB/s

执行 ./process_bar 后,终端会输出 4 个下载任务的进度条,每个部分颜色区分清晰:

  • 红色:进度主体(# 填充部分);
  • 蓝色:百分比(如50.0%);
  • 青色:动态光标( | / - \ 循环切换);
  • 黄色:附加信息(当前进度 / 总进度、传输速度);

进度完成后自动换行,后续任务不重叠,整体流畅无错乱。

注意 :其中动态光标 只与调用的这个函数有关不管进度条动不动,他都是得转动的。其它的优化大家也可以自己想想尝试一下。

效果演示

进度条

结束语

一个高质量的进度条,不仅是功能的补充,更是用户体验的提升。本文从回车换行、缓冲区两大底层知识点切入,清晰拆解了 Linux 终端输出的核心原理,在此基础上完整实现了彩色动态进度条项目。通过本案例,既能吃透行缓冲、字符刷新等 IO 底层细节,也能掌握小型项目的模块化开发思路。Linux 终端开发的魅力就在于此 ------ 看似简单的功能,背后藏着扎实的底层逻辑,吃透这些细节,才能写出更稳定、更优雅的代码。

相关推荐
cui_ruicheng2 小时前
Linux网络编程(四):UDP Socket基础编程
linux·服务器·网络·udp
用户2367829801682 小时前
Linux more 命令详解:从基础分页到高级文本查看技巧
linux
相思难忘成疾2 小时前
SELinux 强制访问控制安全策略验证
linux·运维·服务器·网络·memcached
j7~2 小时前
【Linux操作系统】基础IO文件系统(理解硬件,理解文件系统,Inode,软硬链接)
linux·运维·服务器·磁盘·文件系统·inode·软硬件链接
Donk_672 小时前
Shell 数组实践
linux·算法·bash
XMAIPC_Robot2 小时前
电力设备RK3568/RK3576+FPGA,多系统混合部署Linux+RTOS RT-THREAD,强实时性
linux·运维·fpga开发
aashuii2 小时前
linux测试lsquic
linux·运维·服务器
认真的薛薛2 小时前
Linux运维:Jenkins部署
linux·运维·jenkins