
◆ 博主名称: 晓此方-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 的内容被存在了行缓冲区中。行缓冲区的刷新策略通常是:
- 遇到
\n。- 缓冲区满。
- 程序正常退出。
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 核心设计思路
一个基本的进度条包含以下要素:
- 主体展示 :使用
[和]包裹,中间用#或=填充。 - 百分比 :实时显示当前进度(如
53%)。 - 旋转光标 :显示程序正在运行(如
| / - \循环)。 - 原位刷新 :利用
\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设计步骤
- 定义接口 :
progress函数接收当前进度和总目标。 - 占比映射 :计算
(cur * 100 / load)得到百分比,并将其作为数组下标。 - 按需填充 :只有当百分比增加时,才向数组中填入
#。 - 原位刷新 :利用
\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!