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

news2025/1/13 21:17:22

进度条---命令行版本

回车换行

其实本质上回车和换行是不同概念,我们用一张图来简单的理解一下:

在计算机语言当中:

换行符:\n

回车符:\r

\r\n:回车换行

这时候有人可能会有疑问:我在学习C/C++语言的时候,单纯的\n就起到了回车加换行的行为呀?!

答:因为在语言层面\n给我们编译成了\r\n 

缓冲区问题

在我们目前的阶段,我们将缓冲区看作是一段内存块,我们可以先实现一个测试代码:

#touch test.c

#可以直接
vim test.c

测试代码

技巧补充:我们可以在底行模式使用man手册查看sleep(3)函数所需的头文件:
:!man 3 sleep

#include<stdio.h>
#include<unistd.h>

int main()
{
//printf("hello Linux!\n");
    printf("hello Linux!");
    sleep(3);
    return 0;
}

 我们在Linux编译后./test执行时,当只有上被注释的代码被执行,和只有第二句printf的代码,我们发现前者内容打印出来,程序3秒后结束,后者是3秒后程序结束才打印内容,我们可以明显感受到程序是先执行sleep(3)的,但是我们之前学习C/C++知道,我们定义的程序逻辑都是从上往下执行的,可是我们为什么没有看到字符串呢?

我们应该知道,对于后者,printf在sleep之前一定执行完了,但是显示器上没有显示,那么在我们休眠3秒期间,字符串" hello Linux! "在哪里?其实该字符串在缓冲区里面,可以理解为内存有一小内存块,将该字符串临时保存在该处了。我们带有\n的字符串可以在显示屏上打印,我们称为行刷新,也就是遇到\n,printf执行完就默认将其字符串直接显示到显示器了(行刷新),可以理解为没有\n就不给行刷新;

那后者为什么最后也可以刷新出来呢?

是因为程序退出了,会自动刷新缓冲区!


我们如何可以让不带\n的字符串能够立马刷新呢?

我们如果想让不带\n的字符串立马刷新,可以使用fflush:

fflush 是 C 语言标准库函数之一,用于刷新流的缓冲区。其原型定义在 <stdio.h> 头文件中

我们可以进行man手册查看。

printf("hello Linux!");
fflush(stdout);

其实我们的程序会默认打开三个输入输出流,分别是:(描述符分别为1,2,3)

  1. 标准输入流(stdin:用于从标准输入设备(通常是键盘)读取数据。

  2. 标准输出流(stdout:用于向标准输出设备(通常是屏幕)输出数据。

  3. 标准错误流(stderr:用于输出错误信息和其他诊断信息,通常也是输出到屏幕。

都是一个指向 FILE 结构体的指针,定义在 <stdio.h> 中。

我们的printf默认是从标准输出流里面打,我们可以看到fprintf其实就是比printf多了一个参数:

在Linux下,一切皆文件,当一个程序在 Linux 下运行时,操作系统会自动为该程序打开三个标准流文件描述符,也就是下面两条代码是等价的,fprintf只是显示的表示出往哪里输出:

    fprintf(stdout,"hello Linux!\n");
    printf("hello Linux!\n");

 我们就可以利用缓冲区来实现一个倒计时:光标显示后回退(回车)

int main()
{
    int i=9;
    while(i>=0)
    {
        printf("%d\r",i);
        i--;
        sleep(1);
    }
    return 0;
}

但是我们发现在显示器上没有显示任何数据,这是为什么呢?

因为我们行刷新是需要\n的,%d后是\r,\r不支持我们行刷新,所以对应的信息并未有显示出来,还在缓冲区里面存着呢,所以需要用到fflush(stdout);

int main()
{
    int i=9;
    while(i>=0)
    {
        printf("%d\r",i);
        fflush(stdout);
        i--;
        sleep(1);
    }
    printf("\n");//跑完之后想要保留0(命令行),不让会覆盖
    return 0;
}

为了使我们对下面进度条程序更好的实现,我们来看看当i取10的时候:

命令行变化:10-90-80-70-60-50-40-30-20-10-00----00

这时候,我们应该好好理解显示:
举个例子,我们往显示器上输入:12345,是输出的是12345数字,还是'1' '2' '3' '4' '5'字符?

显示器只认识字符!显示器是字符设备,所以输出的是后者,这也是为什么我们需要像%d的格式化输出,所以我们可以将上述代码中的%d改成%2d,还可以在前面加一个-即"%-2d"来整体靠左显示,代码我就不写出来了,在原代码上做改进就🆗。

以上我们进度条的预备工作就基本完成了(回车换行,缓冲区问题,格式问题,字符设备的理解问题,输出设备的刷新问题)

我们想写一个怎么样的进度条

框架的搭建

创建文件

首先,在我们自己的工作目录中创建四个文件:

  • main.c

  • Makefile

  • process.c

  • process.h

编写 Makefile

使用 vim 编辑器打开 Makefile,并写入以下内容:

SRC:=$(wildcard *.c)
OBJ:=$(SRC:.c=.o)
BIN=processbar

$(BIN): $(OBJ)
	gcc -o $@ $^

%.o: %.c
	gcc -c $<

.PHONY: clean
clean:
	rm -f $(OBJ) $(BIN)

这个 Makefile 定义了如何编译和链接你的程序。它使用 wildcard 自动查找所有 .c 文件,并生成相应的 .o 文件。最终生成的可执行文件名为 processbar

编写 process.h

使用 vim 编辑器打开 process.h,并写入以下内容:

#pragma once
#include <stdio.h>

void process_v1();

这个头文件声明了一个函数 process_v1,该函数将在 process.c 中实现。

编写 process.c

使用 vim 编辑器打开 process.c,并写入以下内容:

#include "process.h"

void process_v1() {
    printf("hello rose!\n");
}

这个文件实现了 process_v1 函数,目前只是简单地打印一条消息。

编写 main.c

使用 vim 编辑器打开 main.c,并写入以下内容:

#include "process.h"

int main() {
    process_v1();
    return 0;
}

这个文件是程序的入口点,它调用了 process_v1 函数。

编译和运行

完成上述文件编写后,你可以使用以下命令进行编译:

make

这将根据 Makefile 的规则编译并生成 processbar 可执行文件。然后,你可以运行该程序:

./processbar

如果一切顺利,你应该会看到输出 "hello rose!"。

清理

最后,使用以下命令清理生成的文件:

make clean

这将删除所有 .o 文件和 processbar 可执行文件。

接下来,我们就可以在 process.c 中实现进度条的功能。可以根据具体需求设计进度条的更新逻辑和显示方式。

version1原理版本

#include"process.h"
#include<string.h>
#include<unistd.h>

#define NUM 101//因为会有一个\0的存在
#define STYLE '='

void process_v1()
{    
    char buffer[NUM];
    memset(buffer,0,sizeof(buffer));
    const char *lable="|/-\\";
    size_t len=strlen(lable);

    //计数器
    int cnt=0;
    while(cnt<=100)//这个循环会循环101次
    {
        printf("[%-100s][%d%%][%c]\r",buffer,cnt,lable[cnt%len]);
        fflush(stdout);
        buffer[cnt]=STYLE;
        cnt++;
        //sleep(1);
        usleep(10000);
    }
    printf("\n");
    return;
}

  1. 定义常量和变量

    • NUM 定义为 101,因为进度条的长度为 100 个字符,加上一个字符串的终止符 \0
    • STYLE 定义为 =,用于表示进度条的已填充部分。
    • buffer 是一个字符数组,用于存储进度条的当前状态。
    • lable 是一个字符串,包含旋转的符号 |/-\,用于在进度条旁边显示一个旋转的动画效果。
    • len 是 lable 字符串的长度。
    • cnt 是一个计数器,用于控制进度条的更新和旋转符号的切换。
  2. 初始化缓冲区

    • 使用 memset 函数将 buffer 初始化为全 0,即空字符串。
  3. 进度条更新循环

    • while 循环会执行 101 次,因为 cnt 从 0 开始,直到 100 结束。
    • 在每次循环中,使用 printf 函数输出当前的进度条状态:
      • [%-100s] 表示一个宽度为 100 的左对齐字符串,buffer 作为参数传入,表示进度条的当前填充状态。
      • [%d%%] 表示当前的百分比进度,cnt 作为参数传入。
      • [%c] 表示旋转符号,lable[cnt%len] 计算当前应该显示的旋转符号。
    • 使用 \r 作为 printf 的结尾,表示返回到行首,这样下一次输出会覆盖当前行的内容。
    • 调用 fflush(stdout) 确保输出立即显示在屏幕上,而不是被缓冲。
    • 将 buffer[cnt] 设置为 STYLE,即 =,表示进度条的已填充部分向右扩展一个字符。
    • cnt++ 更新计数器。
    • usleep(10000) 暂停 10 毫秒,使进度条的更新速度适中,便于观察。注释掉的 sleep(1) 是暂停 1 秒的另一种方式,但会使进度条更新过慢。
  4. 结束输出

    • 循环结束后,调用 printf("\n") 输出一个换行符,使光标移动到下一行,避免后续输出覆盖进度条。

整体功能:

这个 process_v1 函数实现了一个动态更新的文本进度条,进度条的长度为 100 个字符,旁边有一个旋转的符号动画。通过在循环中逐步填充 buffer 并输出,模拟了进度条的动态增长过程。每次更新后,使用 \r 返回行首并刷新输出,实现了进度条的原地更新效果。

version2真实版本

main.c

#include "process.h"

#include <stdio.h>
#include <unistd.h>

double total = 1024.0; // 总下载量,单位为MB
double speed = 1.0;    // 下载速度,单位为MB/s

void DownLoad() {
    double current = 0; // 当前已下载量
    while (current <= total) {
        // 刷新进度条
        FlushProcess(total, current);
        // 模拟下载数据,每次循环下载speed大小的数据
        usleep(300000); // 暂停300ms,模拟下载延时
        current += speed;
    }
    printf("\ndownload %.2lfMB Done\n", current);
}

int main() {
    DownLoad();
    return 0;
}

version2

#include "process.h"
#include <string.h>
#include <unistd.h>

#define NUM 101 // 因为会有一个\0的存在
#define STYLE '='

// version2
void FlushProcess(double total, double current) {
    char buffer[NUM];
    memset(buffer, 0, sizeof(buffer));
    const char *lable = "|/-\\";
    size_t 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); // 确保进度条立即显示
}

进度条与下载模拟功能梳理:

  • 下载模拟

    • DownLoad 函数模拟了一个下载过程,其中 total 表示总下载量,speed 表示下载速度。
    • 在 while 循环中,每次循环模拟下载 speed 大小的数据,并通过 usleep 函数暂停一段时间来模拟下载延时。
    • 循环过程中,不断调用 FlushProcess 函数来刷新进度条,显示当前的下载进度。
  • 进度条刷新

    • FlushProcess 函数负责根据当前下载量 current 和总下载量 total 计算进度条的填充状态,并输出到屏幕。
    • buffer 数组用于存储进度条的当前状态,num 变量计算出需要填充的 = 字符的数量。
    • 使用 printf 函数输出进度条,其中 [%-100s] 表示一个宽度为 100 的左对齐字符串,用于显示进度条的填充部分;[%.1f%%] 显示当前的下载百分比;[%c] 显示一个旋转的符号,用于增加动画效果。
    • 使用 \r 作为 printf 的结尾,表示返回到行首,使下一次输出覆盖当前行的内容,实现进度条的原地更新效果。
    • fflush(stdout) 确保进度条的输出立即显示在屏幕上,而不是被缓冲。

整体功能

这个程序通过模拟下载过程,并在每次下载数据后刷新进度条,实现了动态显示下载进度的效果。进度条的长度为 100 个字符,旁边有一个旋转的符号动画,使用户可以直观地看到下载进度的变化。当下载完成后,程序会输出一条完成信息,告知用户下载已成功完成。

我们不仅仅可以在下载方面使用进度条,我们也可以在上传方面使用进度条,因此,在main.c源文件当中,我们typedef一个函数指针来使用回调函数来优化代码:

#include"process.h"

#include<stdio.h>
#include<unistd.h>

typedef void (*callback_t) (double total,double current);//函数指针

double total=1024.0;
double speed=1.0;

//回调函数
void DownLoad(callback_t cb)
{
    double current=0;
    while(current<=total)
    {
        //刷新进度
        cb(total,current);
        //下载代码
        usleep(3000);//充当下载数据
        current+=speed;
    }
    printf("\ndownload %.2lfMB Done\n",current);
}

void UpLoad(callback_t cb)
{
    double current=0;
    while(current<=total)
    {
        //刷新进度
        cb(total,current);
        //下载代码
        usleep(3000);//充当下载数据
        current+=speed;
    }
    printf("\nUpload %.2lfMB Done\n",current);
}
int main()
{
    DownLoad(FlushProcess);
    UpLoad(FlushProcess);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2276149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

于交错的路径间:分支结构与逻辑判断的思维协奏

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。* 这一节内容很多&#xff0c;文章字数达到了史无前例的一万一&#xff0c;我们要来学习分支与循环结构中…

【学习笔记】理解深度学习和机器学习的数学基础:数值计算

深度学习作为人工智能领域的一个重要分支&#xff0c;其算法的实现和优化离不开数值计算。数值计算在深度学习中扮演着至关重要的角色&#xff0c;它涉及到如何在计算机上高效、准确地解决数学问题。本文将介绍深度学习中数值计算的一些关键概念和挑战&#xff0c;以及如何应对…

DolphinScheduler自身容错导致的服务器持续崩溃重大问题的排查与解决

01 问题复现 在DolphinScheduler中有如下一个Shell任务&#xff1a; current_timestamp() { date "%Y-%m-%d %H:%M:%S" }TIMESTAMP$(current_timestamp) echo $TIMESTAMP sleep 60 在DolphinScheduler将工作流执行策略设置为并行&#xff1a; 定时周期调度设置…

Python学习(三)基础入门(数据类型、变量、条件判断、模式匹配、循环)

目录 一、第一个 Python 程序1.1 命令行模式、Python 交互模式1.2 Python的执行方式1.3 SyntaxError 语法错误1.4 输入和输出 二、Python 基础2.1 Python 语法2.2 数据类型1&#xff09;Number 数字2&#xff09;String 字符串3&#xff09;List 列表4&#xff09;Tuple 元组5&…

LLM - Llama 3 的 Pre/Post Training 阶段 Loss 以及 logits 和 logps 概念

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/145056912 Llama 3 是 Meta 公司发布的开源大型语言模型&#xff0c;包括具有 80 亿和 700 亿参数的预训练和指令微调的语言模型&#xff0c;支持…

[RabbitMQ] RabbitMQ运维问题

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

MongoDB如何使用

1.简单介绍 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最 像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 MongoDB是一个基于分布式文件存储的数据库由C语…

【2024年华为OD机试】(C卷,100分)- 分割均衡字符串 (Java JS PythonC/C++)

一、问题描述 题目描述 均衡串定义&#xff1a;字符串中只包含两种字符&#xff0c;且这两种字符的个数相同。 给定一个均衡字符串&#xff0c;请给出可分割成新的均衡子串的最大个数。 约定&#xff1a;字符串中只包含大写的 X 和 Y 两种字符。 输入描述 输入一个均衡串…

React Fiber框架中的Commit提交阶段——commitMutationEffect函数

Render阶段 Render阶段可大致归为beginWork&#xff08;递&#xff09;和completeWork&#xff08;归&#xff09;两个阶段 1.beginWork流程&#xff08;递&#xff09; 建立节点的父子以及兄弟节点关联关系 child return sibling属性给fiber节点打上flag标记(当前节点的flag) …

【STM32-学习笔记-6-】DMA

文章目录 DMAⅠ、DMA框图Ⅱ、DMA基本结构Ⅲ、不同外设的DMA请求Ⅳ、DMA函数Ⅴ、DMA_InitTypeDef结构体参数①、DMA_PeripheralBaseAddr②、DMA_PeripheralDataSize③、DMA_PeripheralInc④、DMA_MemoryBaseAddr⑤、DMA_MemoryDataSize⑥、DMA_MemoryInc⑦、DMA_DIR⑧、DMA_Buff…

IoT平台在设备远程运维中的应用

IoT平台是物联网技术的核心组成部分&#xff0c;实现了设备、数据、应用之间的无缝连接与交互。通过提供统一的设备管理、数据处理、安全监控等功能&#xff0c;IoT平台为企业构建了智能化、可扩展的物联网生态系统。在设备远程运维领域&#xff0c;IoT平台发挥着至关重要的作用…

浅谈云计算05 | 云存储等级及其接口工作原理

一、云存储设备 在当今数字化飞速发展的时代&#xff0c;数据已然成为个人、企业乃至整个社会的核心资产。从日常生活中的珍贵照片、视频&#xff0c;到企业运营里的关键业务文档、客户资料&#xff0c;数据量呈爆炸式增长。面对海量的数据&#xff0c;如何安全、高效且便捷地存…

网络传输层TCP协议

传输层TCP协议 1. TCP协议介绍 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同&#xff0c;在于TCP是有连接、可靠、面向字节流的。具体来说&#xff0c;TCP设置了一大…

【Linux系列】`find / -name cacert.pem` 文件搜索

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【论文笔记】Sign Language Video Retrieval with Free-Form Textual Queries

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Sign Language Video Retr…

Observability:将 OpenTelemetry 添加到你的 Flask 应用程序

作者&#xff1a;来自 Elastic jessgarson 待办事项列表可以帮助管理与假期计划相关的所有购物和任务。使用 Flask&#xff0c;你可以轻松创建待办事项列表应用程序&#xff0c;并使用 Elastic 作为遥测后端&#xff0c;通过 OpenTelemetry 对其进行监控。 Flask 是一个轻量级…

网站目录权限加固

说明 在一个入侵链路中&#xff0c;往往是利用某个安全漏洞&#xff0c;向服务器写入或上传一个webshell&#xff08;后门&#xff09;&#xff0c;再通过webshell提权或进行后续渗透入侵行为。 这个过程中&#xff0c;获取webshell是最关键最重要的一个步骤&#xff0c;如能在…

qt QPainter setViewport setWindow viewport window

使用qt版本5.15.2 引入viewport和window目的是用于实现QPainter画出来的内容随着窗体伸缩与不伸缩两种情况&#xff0c;以及让QPainter在widget上指定的区域(viewport)进行绘制/渲染&#xff08;分别对应下方demo1&#xff0c;demo2&#xff0c;demo3&#xff09;。 setViewpo…

一些计算机零碎知识随写(25年1月)-1

我原以为世界上有技术的那批人不会那么闲&#xff0c;我错了&#xff0c;被脚本真实了。 今天正隔着画画呢&#xff0c;手机突然弹出几条安全告警通知。 急忙打开服务器&#xff0c;发现问题不简单&#xff0c;直接关服务器重装系统..... 首先&#xff0c;不要认为小网站&…

分布式锁Redisson详解,Redisson如何解决不可重入,不可重试,超时释放,主从一致问题的分析解决(包括源码简单分析)

目录 1. Redisson解决不可重入锁导致的死锁问题 2. 不可重试问题 Pub/Sub 的优势 锁释放的发布逻辑 3. 超时释放的问题 1. 锁的超时释放机制背景 2. 源码分析 2.1 锁的获取 2.2 看门狗机制 2.3 看门狗续期实现 2.4 手动设置锁的过期时间 总结 4. 主从一致性 问题…