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

文章目录
- 概要&序論
- 一、 回车与换行的概念区分
-
- 1.1 历史渊源
- 1.2 现代计算机中的演变
- 二、 缓冲区问题与刷新机制
-
- 2.1 现象观察
- 2.2 强制刷新:fflush
- 三、 必备知识储备
-
- 3.1 休眠函数:usleep
- 3.2 进阶技巧:printf 彩色输出
- 四、 进度条原理与代码实现
-
- 4.1 核心设计思路
- 4.2 版本 1:原理验证版
- 4.2 升级版:解耦设计
-
- 4.2.1设计意图
- 4.2.2设计步骤
- 4.2.3代码实现
概要&序論
Hello 大家好,我是此方。 本篇利用 \\r(回车不换行)控制光标原位刷新,配合 fflush(stdout) 强制冲刷缓冲区,实现一个带百分比和动态旋转光标的 Linux 进度条小程序。 Well——正式开始。
一、 回车与换行的概念区分
在实现进度条之前,首先要理清两个在底层逻辑中完全不同的概念:回车与换行。
1.1 历史渊源
这两个概念起源于老式打字机:
- 回车(\\r):将打印头(或光标)移回到当前行的起始位置,但并不向下移动行。
- 换行(\\n):将打印头向下移动一行,但并不回到行首。

1.2 现代计算机中的演变
- 在 Linux/Unix 系统中,\\n 通常被解释为“回车+换行”,即跳到下一行的开头。
- 在进度条开发中,我们核心利用的是 \\r(回车)。它能让光标回到当前行的开头,从而通过后续覆盖打印实现动态刷新效果。
二、 缓冲区问题与刷新机制
在编写 C 语言程序时,输出内容并不会直接显示在屏幕上,而是先进入缓冲区。
2.1 现象观察
参考图片中的代码示例:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("hello bit!"); // 注意:这里没有加 \\n
sleep(3); // 程序休眠 3 秒
return 0;
}
现象:程序运行后,屏幕并不会立即打印 hello bit!,而是等待 3 秒后,随着程序的退出才显示出来。 原因:printf 的内容被存在了行缓冲区中。行缓冲区的刷新策略通常是:
2.2 强制刷新:fflush
如果我们想在不使用 \\n(因为换行会导致无法在同一行刷新)的情况下立即显示内容,需要手动调用刷新函数:
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 重置属性,否则后续的所有终端输出都会受影响。
#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 核心设计思路
一个基本的进度条包含以下要素:
4.2 版本 1:原理验证版
下面是根据图片逻辑实现的第一个进度条系统程序:
#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设计步骤
4.2.3代码实现
ProgressBar.h
#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 (渲染逻辑)
#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 (业务逻辑)
#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!



