Re:Linux系统篇(十三)特别篇: 实现Linux第⼀个系统程序−进度条


◆ 博主名称: 晓此方-CSDN博客 大家好,欢迎来到晓此方的博客。
⭐️Linux系列个人专栏: 【主题曲】Linux
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)



文章目录

  • 概要&序論
  • [一、 回车与换行的概念区分](#一、 回车与换行的概念区分)
    • [1.1 历史渊源](#1.1 历史渊源)
    • [1.2 现代计算机中的演变](#1.2 现代计算机中的演变)
  • [二、 缓冲区问题与刷新机制](#二、 缓冲区问题与刷新机制)
    • [2.1 现象观察](#2.1 现象观察)
    • [2.2 强制刷新:fflush](#2.2 强制刷新:fflush)
  • [三、 必备知识储备](#三、 必备知识储备)
    • [3.1 休眠函数:usleep](#3.1 休眠函数:usleep)
    • [3.2 进阶技巧:printf 彩色输出](#3.2 进阶技巧:printf 彩色输出)
  • [四、 进度条原理与代码实现](#四、 进度条原理与代码实现)

概要&序論

Hello 大家好,我是此方。 本篇利用 \r(回车不换行)控制光标原位刷新,配合 fflush(stdout) 强制冲刷缓冲区 ,实现一个带百分比和动态旋转光标的 Linux 进度条小程序。 Well------正式开始。

一、 回车与换行的概念区分

在实现进度条之前,首先要理清两个在底层逻辑中完全不同的概念:回车换行

1.1 历史渊源

这两个概念起源于老式打字机:

  • 回车(\r):将打印头(或光标)移回到当前行的起始位置,但并不向下移动行。
  • 换行(\n) :将打印头向下移动一行,但并不回到行首。

1.2 现代计算机中的演变

  • 在 Linux/Unix 系统中,\n 通常被解释为"回车+换行",即跳到下一行的开头。
  • 在进度条开发中,我们核心利用的是 \r(回车)。它能让光标回到当前行的开头,从而通过后续覆盖打印实现动态刷新效果。

二、 缓冲区问题与刷新机制

在编写 C 语言程序时,输出内容并不会直接显示在屏幕上,而是先进入缓冲区。

2.1 现象观察

参考图片中的代码示例:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
int main() {
    printf("hello bit!"); // 注意:这里没有加 \n
    sleep(3);             // 程序休眠 3 秒
    return 0;
}

现象 :程序运行后,屏幕并不会立即打印 hello bit!,而是等待 3 秒后,随着程序的退出才显示出来。

原因printf 的内容被存在了行缓冲区中。行缓冲区的刷新策略通常是:

  1. 遇到 \n
  2. 缓冲区满。
  3. 程序正常退出。

2.2 强制刷新:fflush

如果我们想在不使用 \n因为换行会导致无法在同一行刷新)的情况下立即显示内容,需要手动调用刷新函数:

cpp 复制代码
fflush(stdout); // 强制刷新标准输出流

三、 必备知识储备

3.1 休眠函数:usleep

在 Linux 系统编程中,除了秒级的 sleep,我们常用更精确的休眠函数来控制进度条的刷新频率。

  • 头文件#include <unistd.h>
  • 函数原型int usleep(useconds_t usec);
  • 特点 :以**微秒(μs)**为单位( 1 秒 = 1 , 000 , 000 微秒 1秒 = 1,000,000微秒 1秒=1,000,000微秒)。在进度条实现中,建议设置为 100,000(即 0.1 秒)左右,以获得丝滑的视觉体验。

3.2 进阶技巧:printf 彩色输出

为了让进度条更具辨识度,我们可以利用 ANSI 转义码让终端输出带颜色的文本。

  • 基本格式\033[属性代码;前景颜色;背景颜色m
  • 常用代码
    • 字体颜色:31(红)、32(绿)、33(黄)、34(蓝)。
    • 常用属性:0(重置)、1(加粗)、5(闪烁)。
  • 注意事项 :设置完颜色后必须使用 \033[0m 重置属性,否则后续的所有终端输出都会受影响。
cpp 复制代码
#include <stdio.h>
int main() {
    // 1. 基础颜色:红色
    printf("\033[31m这是一行红色的字\033[0m\n");
    // 2. 复合属性:绿色 + 加粗 + 黑色背景
    printf("\033[1;32;40m这是加粗绿字黑底\033[0m\n");
    // 3. 进度条常用:黄色闪烁(提示警告)
    printf("\033[5;33m正在下载,请稍后...\033[0m\n");
    return 0;
}

四、 进度条原理与代码实现

4.1 核心设计思路

一个基本的进度条包含以下要素:

  1. 主体展示 :使用 [] 包裹,中间用 #= 填充。
  2. 百分比 :实时显示当前进度(如 53%)。
  3. 旋转光标 :显示程序正在运行(如 | / - \ 循环)。
  4. 原位刷新 :利用 \r 回到行首,配合 fflush 实时刷新。

4.2 版本 1:原理验证版

下面是根据图片逻辑实现的第一个进度条系统程序:

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

#define SIZE 101
#define MAX_REVOLVE 4

int main() {
    char bar[SIZE];
    memset(bar, '\0', sizeof(bar));
    const char* label = "|/-\\"; // 旋转光标
    int i = 0;

    while (i <= 100) {
        // %-100s: 左对齐,预留100个字符空间
        // %%: 打印百分号
        // %c: 打印旋转光标
        printf("[% -100s][%d%%][%c]\r", bar, i, label[i % MAX_REVOLVE]);
        // [%-100s]:预留100字符宽度并左对齐,确保bar数组增长时外框不闪烁。
		// [%d%%]  :打印进度数字,'%%' 转义输出百分号。
		// [%c]    :取模循环打印旋转字符(|/-\),模拟加载动画。
		// \r      :回车符,使光标返回行首,配合后续刷新实现"原位覆盖"动态效果。
        fflush(stdout); // 必须手动刷新,否则看不到动态效果
        
        bar[i++] = '#';
        usleep(50000); // 微秒单位,50ms 刷新一次
    }
    printf("\n进度下载完成!\n");

    return 0;
}

4.2 升级版:解耦设计

在实际工程中,进度条不应"自嗨",而应由外部任务驱动。本版本通过参数传递,实现了下载逻辑与显示逻辑的分离。

4.2.1设计意图

  • 解耦 :进度条只负责渲染,不关心进度如何产生。
  • 同步 :通过外部传入的 current(当前量)和 load(总量)实时计算占比,确保视觉与真实状态同步。

4.2.2设计步骤

  1. 定义接口progress 函数接收当前进度和总目标。
  2. 占比映射 :计算 (cur * 100 / load) 得到百分比,并将其作为数组下标。
  3. 按需填充 :只有当百分比增加时,才向数组中填入 #
  4. 原位刷新 :利用 \r 配合 printf 格式化输出,实现动态刷新。

4.2.3代码实现

ProgressBar.h

cpp 复制代码
#pragma once
#include <iostream>
#include <windows.h>
#include <cstring>

const int NUM = 101;
const char BAR = '#';

// 进度条渲染函数声明
void progress(char* arr, int cur, int load);

Test.cpp (渲染逻辑)

cpp 复制代码
#include "ProgressBar.h"

void progress(char* arr, int cur, int load)
{
    // 1. 计算当前下载占比 (0-100)
    const int cnt = (cur * 100 / load);
    
    // 2. 旋转光标:cnt % 4 确保下标在 0-3 之间循环
    const char* pointer = "|-/\\";
    
    // 3. 渲染:%-100s 预留空间,\r 回到行首覆盖刷新
    printf("[%-100s][%d%%][%c]\r", arr, cnt, pointer[cnt % 4]);
    
    // 4. 实时更新:在计算出的占比位置填入进度字符
    if (cnt < 100) arr[cnt] = BAR;
}

ProgressBar.cpp (业务逻辑)

cpp 复制代码
#include "ProgressBar.h"

int main()
{
    char arr[NUM];
    memset(arr, 0, sizeof(arr));

    const int DownLoad = 1024; // 模拟需要下载的总量
    int current = 0;           // 模拟当前已下载的进度

    while (current <= DownLoad)
    {
        // 将当前下载状态推送到进度条进行渲染
        progress(arr, current, DownLoad);
        current++;
        Sleep(1); // 模拟网络延迟
    }
    
    printf("\nDownLoad Complete!\n");
    return 0;
}

好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye!

相关推荐
tedcloud1236 小时前
UI-TARS-desktop部署教程:构建AI桌面自动化系统
服务器·前端·人工智能·ui·自动化·github
AC赳赳老秦9 小时前
供应链专员提效:OpenClaw自动跟踪物流信息、更新库存数据,异常自动提醒
java·大数据·服务器·数据库·人工智能·自动化·openclaw
夏日听雨眠9 小时前
LInux(逻辑地址与物理地址的区别,文件描述符,lseek函数)
linux·运维·网络
哲霖软件10 小时前
ERP 赋能非标自动化行业:破解物料与库存管理难题
运维·自动化
qq_5425154111 小时前
Ubuntu 22.04.4 LTS安装ToDesk最新版打不开,无响应?旧版本4.7.2_277版本分享
linux·ubuntu·todesk
火车叼位11 小时前
替代 Tiny Win10 的 Linux 方案:Debian XFCE 精简桌面搭建
linux·运维
小麦嵌入式11 小时前
FPGA入门(四):时序逻辑计数器原理与 LED 闪烁实现
linux·驱动开发·stm32·嵌入式硬件·fpga开发·硬件工程·dsp开发
皮卡蛋炒饭.12 小时前
传输层协议UDP
linux·网络协议·udp
大明者省12 小时前
宝塔开了端口,Ubuntu 还得开相应端口才能打通
服务器·数据库·ubuntu