当前位置: 首页 > news >正文

Linux 第一个系统程序 - 进度条

目录

一、回车与换行概念

1、基本概念

2、历史背景

3、现代系统中的区别

二、行缓冲区实验引入

实验1:带换行符的输出

实验2:不带换行符的输出

三、标准I/O缓冲区机制

1、显示器刷新策略

2、相关概念

3、强制刷新缓冲区

fflush函数

实验3:手动刷新缓冲区

原理说明

四、倒计时程序

1. %-2d 格式化输出

2. \r 回车符

3. 为什么需要 %-2d?

五、进度条实现

文件结构

process.h(头文件)

process.c(进度条实现)

main.c(测试程序)

Makefile(构建文件)

功能说明


一、回车与换行概念

1、基本概念

  • 回车(Carriage Return): 将光标移动到当前行的行首,对应转义字符 \r

  • 换行(Line Feed): 将光标移动到下一行,对应转义字符 \n

2、历史背景

在老式打字机中:回车:将打印头移回行首        换行:将纸张向上移动一行

在老式键盘中的Enter键形象地表示了:

光标先换到下一行再回到行首位置,实际上就等价于\n+\r。

3、现代系统中的区别

  • Windows系统:使用 \r\n 表示换行

  • Unix/Linux系统:使用 \n 表示换行

  • Mac OS(早期):使用 \r 表示换行


二、行缓冲区实验引入

实验1:带换行符的输出

#include <stdio.h>
int main()
{printf("hello bite!\n");sleep(3);return 0;
}

现象:立即输出"hello bite!",然后等待3秒后程序结束

实验2:不带换行符的输出

#include <stdio.h>
int main()
{printf("hello bite!");sleep(3);return 0;
}

现象:先等待3秒,然后输出"hello bite!"并立即结束程序

        这段代码仅删除了字符串末尾的'\n',但运行结果与之前有所不同:程序会先休眠3秒,然后才打印"hello world"并结束运行。这一现象证实了行缓冲区的存在。

        显示器采用行刷新机制,只有当缓冲区遇到'\n'或被写满时才会输出内容。在修改后的代码中,由于缺少'\n',"hello world"首先被写入缓冲区。经过3秒休眠后,直到程序结束时才将该字符串输出到显示器上。

所以这里存在一个问题:我们如何在不使用'\n'的情况下将缓冲区中的数据输出?


三、标准I/O缓冲区机制

1、显示器刷新策略

  • 行缓冲(Line Buffering):标准输出(stdout)默认使用行缓冲模式

    • 遇到换行符\n时自动刷新

    • 缓冲区(在内存中)满时自动刷新

    • 程序正常结束时自动刷新

2、相关概念

  1. 标准流

    • stdin:标准输入(通常为键盘)

    • stdout:标准输出(通常为显示器)

    • stderr:标准错误(无缓冲,立即输出)

  2. 缓冲区类型

    • 全缓冲:文件I/O通常使用

    • 行缓冲:终端I/O通常使用

    • 无缓冲:如stderr

3、强制刷新缓冲区

fflush函数

功能:强制刷新指定流的输出缓冲区

        然后回到上面的问题中,我们想到的解决方法是通过调用fflush函数强制刷新缓冲区,将数据立即显示在屏幕上:

实验3:手动刷新缓冲区

#include <stdio.h>
int main()
{printf("hello bite!");fflush(stdout);  // 手动刷新输出缓冲区sleep(3);return 0;
}

现象:立即输出"hello bite!",然后等待3秒后程序结束

原理说明

  • 标准输出通常是行缓冲的,遇到\n或缓冲区满时会自动刷新

  • 使用fflush(stdout)可以强制刷新输出缓冲区


四、倒计时程序

#include <stdio.h>
#include <unistd.h>int main()
{int i = 10;while(i >= 0){printf("%-2d\r", i);  // 使用\r回车不换行fflush(stdout);       // 刷新输出缓冲区i--;sleep(1);             // 暂停1秒}printf("\n");            // 最后换行return 0;
}

对此上面的结论,如上,我们可以根据其内容编写一个倒计时的程序。

功能:实现从10到0的倒计时显示,在输出下一个数之前都让光标先回到本行行首,数字在同一位置更新,就得到了倒计时的效果。

1. %-2d 格式化输出

  • %d:以十进制形式输出整数 i

  • %2d至少占用 2 字符宽度,如果数字不足 2 位,左侧补空格(右对齐)。

    • 例如:

      • 10 → "10"(刚好 2 位,不变)

      • 5 → " 5"(左侧补 1 空格)

  • %-2d左对齐,不足 2 位时右侧补空格。

    • 例如:

      • 10 → "10"

      • 5 → "5 "(右侧补 1 空格)

2. \r 回车符

  • \r(Carriage Return):将光标移动到当前行的行首但不换行

  • 效果:下一次输出会覆盖当前行的内容(实现倒计时的动态更新)。

3. 为什么需要 %-2d

  • 如果直接用 %d\r

    • 当数字从 10 变为 9 时,"10" 会被 "9" 覆盖,但第二个字符 0 可能残留,显示为 "90"(错误)。

  • 使用 %-2d

    • 强制每个数字占 2 位,右侧补空格,确保完全覆盖前一次输出。

    • 例如:

      • 10 → "10"

      • 9 → "9 "(覆盖 "10",不会残留 0


五、进度条实现

文件结构

process.h(头文件)

#pragma once
#include <stdio.h>// 简单进度条版本
void process_v1();// 可配置进度条版本
void FlushProcess(double total, double current);

process.c(进度条实现)

#include "process.h"
#include <string.h>
#include <unistd.h>#define NUM 101     // 进度条长度+1
#define STYLE '='    // 进度条填充字符// 版本1: 简单进度条
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("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]);fflush(stdout);buffer[cnt] = STYLE;cnt++;usleep(50000);  // 50ms延迟}printf("\n");
}// 版本2: 可配置进度条
void FlushProcess(double total, double current)
{char buffer[NUM];memset(buffer, 0, sizeof(buffer));const char *lable = "|/-\\";int len = strlen(lable);static int cnt = 0;// 计算填充比例int num = (int)(current*100/total);for(int i = 0; i < num; i++){buffer[i] = STYLE;}double rate = current/total;cnt %= len;printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]);cnt++;fflush(stdout);
}

当我们想make时,会发现下面的错误: 

如果我们选择把定义放外面的话,for会不美观,但能解决错误:

int i = 0;for(; i < num; i++)
{buffer[i] = STYLE;
}

main.c(测试程序)

#include "process.h"
#include <stdio.h>
#include <unistd.h>double total = 1024.0;  // 模拟总下载量
double speed = 1.0;     // 模拟下载速度void DownLoad()
{double current = 0;while(current <= total){FlushProcess(total, current);// 模拟下载过程usleep(3000);        // 3ms延迟current += speed;    // 增加下载量}printf("\ndownload %.2lfMB Done\n", current);
}int main()
{// 模拟多次下载for(int i = 0; i < 8; i++){DownLoad();}return 0;
}

Makefile(构建文件)

SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
BIN=processbar$(BIN):$(OBJ)gcc -o $@ $^%.o:%.cgcc -c $<.PHONY:
clean:rm -f $(OBJ) $(BIN)

结合上面的错误,我们还可以再Makefile中修改,直接按照报错要求来: 

SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
BIN=processbar$(BIN): $(OBJ)gcc -o $@ $^ -std=c99  # 修正:添加 -std=c99%.o: %.cgcc -c $< -std=c99      # 可选:如果其他 .c 文件也需要 C99.PHONY: clean
clean:rm -f $(OBJ) $(BIN)

        但是我们按照要求来修改后,我们发现还是会出现有warning,但是能够成功构建可执行文件,我们不用管它: 

  1. 在某些系统(如较新的 Linux)中,usleep() 被标记为 废弃(obsolete),默认可能不暴露它的声明。

  2. 现代 Linux 推荐使用 nanosleep()usleep() 只是为了兼容性保留。

  3. usleep() 确实被调用了,但编译器认为它的声明是“隐式”的(即没有找到正式的函数原型)。

  4. 程序能运行,是因为在链接阶段,usleep() 的实现(在 libc 或 librt 中)被正确找到,但编译阶段仍然有警告。

功能说明

  1. process_v1():简单的100步进度条,自带循环

  2. FlushProcess():更灵活的进度条,根据传入的总量和当前值计算进度

  3. DownLoad():模拟下载过程,展示进度条的实际应用

进度条包含以下元素:

  • 进度条本身(用=填充)

  • 完成百分比

  • 旋转指示器(|/-\)表示程序正在运行

http://www.dtcms.com/a/270292.html

相关文章:

  • (C++)list列表相关基础用法(C++教程)(STL库基础教程)
  • 跨越NLP的三重曲线:从词法到叙事的进化之路
  • 使用python的 FastApi框架开发图书管理系统-前后端分离项目分享
  • huggingface笔记:文本生成Text generation
  • EXCEL(带图)转html
  • 基于LiteOS与SLE的多任务无线控制器项目实战
  • 深圳凭物联网软件开发构建智慧‘城市大脑‘
  • 什么是 3D 文件?
  • UE material advance 学习笔记
  • 【时时三省】(C语言基础)怎样引用指针变量
  • 免安装图片修改软件,一键批量处理
  • 16018.UE4+Airsim仿真环境搭建
  • 详细页智能解析算法:洞悉海量页面数据的核心技术
  • 软件系统测试的基本流程
  • 【PyTorch项目实战】VisRAG:基于视觉的多模态文档检索增强生成(文本+图像)
  • Android 事件分发机制深度解析
  • Android 中的多线程编程全面解析
  • YOLO融合[ICLR2025]PolaFormer中的极性感知线性注意力
  • docker proxy
  • C 解压文件
  • Day55 序列预测任务介绍
  • Subject vs Flowable vs Observable 对比
  • 【零基础学AI】第31讲:目标检测 - YOLO算法
  • 每日算法刷题Day44 7.8:leetcode前缀和4道题,用时1h40min
  • JVM 为什么使用元空间(Metaspace)替换了永久代(PermGen)?——深入理解 Java 方法区与类元数据存储的演进
  • 视频能转成gif动图吗?怎么弄?
  • [NOIP][C++]洛谷P1376 [USACO05MAR] Yogurt factory 机器工厂
  • 没合适的组合wheel包,就自行编译flash_attn吧
  • 行业实践案例:金融行业数据治理体系全景解析
  • Java 关键字详解:掌握所有保留关键字的用途与最佳实践