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

【Linux系统】—— 简易进度条的实现

【Linux系统】—— 简易进度条的实现

  • 1 回车和换行
  • 2 缓冲区
  • 3 进度条的准备代码
  • 4 第一版进度条
  • 5 第二版进度条

1 回车和换行

  先问大家一个问题:回车换行是什么,或者说回车和换行是同一个概念吗?
  可能大家对回车换行有一定的误解,本文在这里讲解一下:

假设我们是在写小作文

  • 换行:将笔尖换到下一格,即移动到下一行。
  • 回车:将笔尖移动到本行的开头。
      
    在这里插入图片描述

  在计算机中,换行符为「\n」,回车符为 「\r」,我们往往用「\r\n」来整体表示回车换行。
  但之前写 C语言 时,我们只用「\n」也能同时达到回车和换行的效果,这是在语言层面上,将 \n 解析成 \r\n
  
  

2 缓冲区

  下面我们非常粗略的了解一下缓冲区的概念:

先来段测试代码

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

: sleep() 函数的头文件为:<unistd.h>
  

在这里插入图片描述

  一切正常
  我们将 printf 中的「\n」去掉试试

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

在这里插入图片描述

  结果好像和我们想的有点不太一样。

  解释上面原因前,先问大家一个问题:上述代码是先执行 printf还是先执行sleep
  按结果来看,应该是先执行 sleep,再执行printf
  但事实恰恰相反,是先执行的 printf。在初学C语言时,我们知道一个程序有几种控制流:循环、判断、顺序。我们对应的程序在执行时永远都是从前往后执行的
  
  当程序执行到 sleep 语句时,它一定是把 printf 执行完了。可显示器上并没有显示,那在休眠的 3 秒期间,字符串 “Hello Linux” 在哪呢?在缓冲区里!
  简单理解一下:在内存中有一块内存块叫缓冲区,先将字符串临时存放在缓冲区里。缓冲区向显示器输出是行刷新,也就是说它遇到 \n 自动将缓冲区的内容刷新出去;如果没遇到就一直在缓冲区中呆着,直到程序退出时再自动刷新缓冲区,我们才能看到打印出的字符串。

  如果想让不带 ‘/n’ 的字符串立即刷新,可以用 fflush 函数

#include <stdio.h>
#include <unistd.h>int main()
{printf("Hello Linux!");fflush(stdout);sleep(3);return 0;
}

在这里插入图片描述

  
  

3 进度条的准备代码

  我们先不急着写进度条,先写几段测试代码

  先来实现一个倒计时

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

在这里插入图片描述

  
  现在它是换行进行打印,但我们想让它在同一个位置打印。这时我们可以运用前面学习到的回车符 「\r」

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

在这里插入图片描述

  为什么它一直不显示呢?而且最后程序结束了,命令行覆盖了,什么都没有
  这是因为数据一直在缓冲区没有刷新出来,而最后程序运行结束刷新缓冲区了,因为回车符,命令行从头开始写将数据覆盖了。

  我们手动刷新缓冲区,并且为了不让命令行覆盖数据,我们单独进行换行

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

在这里插入图片描述

  现在,我们写的代码就可以进行倒计时了……了吗?

  还没有,如果我们改成从 10 开始倒计时会怎样

在这里插入图片描述

  又出问题了。

  讲个小知识点:显示
  当我们向显示器中输出 12345 这个数时,显示器上本质上是输出 12345 这个数字还是 ‘1’ ‘2’ ‘3’ ‘4’ ‘5’ 这 5 个字符呢?
  答案是后者显示器是字符设备,它只认字符
  这也解释了为什么我们 printf 要格式化输出。比如我们输出一个 int a,printf 内部将其由整数转成字符串,再用类似 putc 的接口一个一个字符输出到显示器上

  怎么解决上述问题呢?很简单,将输出的显示的位宽改为 2 即可

int main()
{int i = 10; while(i >= 0){   printf("%-2d\r", i); fflush(stdout);--i;sleep(1);}   printf("\n");return 0;
}

  

4 第一版进度条

  我们想写一个怎么样的进度条:
  一对 [ ] 括起100个空字符;每加载 1% 就有一个 ‘#’ 替换一空字符;并在后面显示下载进度和转圈圈表示软件一直在下载

在这里插入图片描述

  
  首先创建多文件

在这里插入图片描述

  

  再写 makefile(对 makefile 有困惑的小伙伴可移步【Linux系统】—— make/makefile)

在这里插入图片描述

  
  基本框架如下:

在这里插入图片描述

  
  process.c 代码如下

在这里插入图片描述

  如果大家觉得休眠 1 秒时间太长,这里给大家再介绍一个新的休眠函数:usleep
  usleep 函数的休眠时间是以微妙为单位的,头文件同样是<unistd.h>

  效果如下:

在这里插入图片描述

  现在我们还需要加上百分比旋转光标。旋转光标是用来告诉用户该程序一直在下载中。
  百分比的实现很简单,这里就不单独介绍了,需要注意的是打印 ‘%’,要输入 ‘%%’ 表示取其字面值
  
  我们简单介绍一下简易光标如何实现
  其实旋转光标很简单,只需要 '|' '/' '-' '\' 不断循环打印即可(因为 '\' 是特殊字符,我们输入 '\\' 表示取字面值)
  
  至此,我们就完成了第一版进度条,代码如下

#include "process.h"
#include <string.h>
#include <unistd.h>#define NUM 101
#define STYLE '#'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][%2d%%][%c]\r", buffer, cnt, lable[cnt % len]);fflush(stdout);buffer[cnt] = STYLE;++cnt;usleep(100000);}   printf("\n");
}

效果展示:

在这里插入图片描述

  
  

5 第二版进度条

  第一版本的进度条看起来像模像样的,其实它根本无法使用

  因为第一版本的进度条和下载的软件是各跑各的,可能软件值下载了 1% 但我们的进度条已经跑完了,而真实的进度条是要反应真实下载进度的。

  一个进度条,一定要结合具体的场景,边下载边更新进度条。

  我们定义变量 total 来表示要下载的总大小;speed 为下载的速度,当然实际的下载速度是浮动的,但这里为方便我们将其固定下来;current 表示当前已下载量。当然,真正的下载是要从网络中获取数据的,这点我们还没学,就用休眠时间来替代

// main.c
#include "process.h"
#include <unistd.h>
#include <stdio.h>double total = 1024;
double speed = 1.0;void DownLoad()
{double current = 0;while(current < total){   //下载代码usleep(3000);//充当下载数据current += speed;}   printf("download %lfMB Done\n", current);
}int main()
{DownLoad();return 0;
}

  现在的情况是我们不知道他正在下载,因此我们需要引入进度条。
  我们重新定义一个 FlushProcess()函数,FlushProcess()函数 的作用是根据当前的下载进度来打印进度条

部分代码如下

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);int i = 0;for(; i < num; i++){   buffer[i] = STYLE;}   double rate = current / total;cnt %= len;printf("[%-100s][%.lf%%][%c]\r", buffer, rate * 100, lable[cnt]);++cnt;fflush(stdout);
}void DownLoad()
{double current = 0;while(current <= total){   //调用FlushProcess函数不断打印进度条FlushProcess(total, current);//下载代码usleep(3000);//充当下载数据current += speed;}   printf("\ndownload %.2lfMB Done\n", current);
}int main()
{DownLoad();return 0;
}

效果演示

在这里插入图片描述

  
  但是上述代码还有一点小问题:现在是下载需要进度条,如果以后是上传呢?上传也需要有对应的进度条。但此时的进度条函数 FlushProcess() 是硬加载在下载函数 DownLoad() 中的;如果是上传,需要的函数是 UpLoad(),那是不是在 UpLoad()函数中也要加载一份进度条 FlushProcess() 函数呢?这样做是不是太麻烦了?

  为了解决这个问题,我们可以使用函数指针

完整代码:

//process.h
#pragma one
#include <stdio.h>void FlushProcess(double total, double current);//process.c
#include "process.h"
#include <string.h>
#include <unistd.h>#define NUM 101
#define STYLE '#'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);int i = 0;for(; i < num; i++){   buffer[i] = STYLE;}   double rate = current / total;cnt %= len;printf("[%-100s][%.lf%%][%c]\r", buffer, rate * 100, lable[cnt]);++cnt;fflush(stdout);
}//main.c
include "process.h"
#include <unistd.h>
#include <stdio.h>typedef void(*callback_t)(double total, double current);double total = 1024;
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);
}int main()
{DownLoad(FlushProcess);return 0;
}

  
  
  
  


  好啦,本期关于 进度条 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 Linux 的学习路上一起进步!

相关文章:

【Linux系统】—— 简易进度条的实现

【Linux系统】—— 简易进度条的实现 1 回车和换行2 缓冲区3 进度条的准备代码4 第一版进度条5 第二版进度条 1 回车和换行 先问大家一个问题&#xff1a;回车换行是什么&#xff0c;或者说回车和换行是同一个概念吗&#xff1f;   可能大家对回车换行有一定的误解&#xff0…...

一文学会:用DeepSeek R1/V3 + AnythingLLM + Ollama 打造本地化部署的个人/企业知识库,无须担心数据上传云端的泄露问题

文章目录 前言一、AnythingLLM 简介&基础应用1.主要特性2.下载与安装3.配置 LLM 提供商4.AnythingLLM 工作区&对话 二、AnythingLLM 进阶应用&#xff1a;知识增强使用三、AnythingLLM 的 API 访问四、小结1.聊天模式2.本地存储&向量数据库 前言 如果你不知道Olla…...

开源身份和访问管理方案之keycloak(一)快速入门

文章目录 什么是IAM什么是keycloakKeycloak 的功能 核心概念client管理 OpenID Connect 客户端 Client Scoperealm roleAssigning role mappings分配角色映射Using default roles使用默认角色Role scope mappings角色范围映射 UsersGroupssessionsEventsKeycloak Policy创建策略…...

C++STL(六)——list模拟

目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…...

HTML5--网页前端编程(下)

HTML5–网页前端编程(下) 9.常用标签下 (1)表格标签 用来展示数据,显示数据,规整条理,可读性好 基本语法 <table><tr> <td>单元格内的文字</td> <td>单元格内的文字</td>… </tr> <tr> <td>单元格内的文字&l…...

Spring 的 ResponseEntity 包装器使用详解

简介 在 Spring 中&#xff0c;ResponseEntity 是 HTTP 响应的包装器。它允许自定义响应的各个方面&#xff1a; HTTP 状态码 响应主体 HTTP 请求头 使用 ResponseEntity 允许完全控制 HTTP 响应&#xff0c;并且它通常用于 RESTful Web 服务中从控制器方法返回响应。 基…...

Git 分布式版本控制工具使用教程

1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具&#xff0c;由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目&#xff0c;速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰&#xff0c;并且在…...

linux部署ollama+deepseek+dify

Ollama 下载源码 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz启动 export OLLAMA_HOST0.0.0.0:11434 ollama serve访问ip:11434看到即成功 Ollama is running 手动安装deepseek…...

torch_bmm验算及代码测试

文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘&#xff0c;比如&#xff0c; mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…...

Vue3 特点

不强制要求组件有根节点 // vue2 <template><div><h1>标题</h1><p>内容</p></div> </template>// vue3 <template><h1>标题</h1><p>内容</p> </template> 注意事项 虽然 Vue 3 不再强制…...

mysql8 C++源码中创建表函数,表字段最大数量限制,表行最大存储限制

在 MySQL 8 的 C 源码中&#xff0c;表的最大字段数量限制体现在 MAX_FIELDS 宏定义中。这个宏定义了表中可以拥有的最大字段数量。 代码中的体现 在 mysql_prepare_create_table 函数中&#xff0c;有以下代码段检查表的字段数量是否超过最大限制&#xff1a; cpp if (alt…...

CTFHub-RCE系列wp

目录标题 引言什么是RCE漏洞 eval执行文件包含文件包含php://input读取源代码远程包含 命令注入无过滤过滤cat过滤空格过滤目录分隔符过滤运算符综合过滤练习 引言 题目共有如下类型 什么是RCE漏洞 RCE漏洞&#xff0c;全称是Remote Code Execution漏洞&#xff0c;翻译成中文…...

【OneAPI】通过网页预渲染让搜索引擎收录网页

API简介 网页预渲染&#xff0c;适用于动态网页以及单页面的SEO&#xff0c;支持网页缓存。 您无须更改代码即可让搜索引擎收录您的网页。只要将需要预渲染的页面转发的本接口即可。 如果您使用Nginx作为网页服务器&#xff0c;推荐使用以下配置&#xff1a; #您的网站locat…...

从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型&#xff0c;以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1&#xff0c;而且在推理模型的成本优化上实现了突破…...

【学习笔记】企业数字化转型顶层设计与企业架构TOGAF9.2-第0章 导论

数据要素资产化迈入关键发展期 围绕发挥数据要素乘数作用&#xff0c;研究实施“数据要素x”行动:从供需两端发力&#xff0c;在智能制造、商贸流通、交通物流、金融服务、医疗健康等若干重点领域&#xff0c;加强场景需求牵引&#xff0c;打通流通障碍、提升供给质量&#xf…...

Vue3 Ref全家桶深度解析:掌握响应式编程精髓

Vue3 Ref全家桶深度解析&#xff1a;掌握响应式编程精髓 一、Ref核心概念 1.1 响应式数据容器 const count ref(0) // 相当于创建了一个响应式容器&#xff1a; {value: 0,__v_isRef: true,// 其他响应式系统属性 }1.2 全家桶全景图 #mermaid-svg-VkHPjjlo16rOyItj {font-f…...

如何避免大语言模型中涉及丢番图方程的问题

希尔伯特第十问题是一个著名的数学问题,涉及不定方程(又称为丢番图方程)的可解答性。然而在大模型中,我们希望问题都是确定的可解的,或者说要尽可能的想办法避免不确定的不可解问题。由于丢番图方程问题是不可判定问题(即不存在一个有效的算法能够解决该类问题的所有实例…...

SpringCloud - Sentinel服务保护

前言 该博客为Sentinel学习笔记&#xff0c;主要目的是为了帮助后期快速复习使用 学习视频&#xff1a;7小快速通关SpringCloud 辅助文档&#xff1a;SpringCloud快速通关 源码地址&#xff1a;cloud-demo 一、简介 官网&#xff1a;https://sentinelguard.io/zh-cn/index.h…...

Java 使用腾讯翻译 API 实现含 HTML 标签文本,json值,精准翻译工具

注意&#xff1a;需搭配标题二的腾讯翻译工具使用 一-1、翻译标签文本工具 package org.springblade.common.utils;import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern;public class TencentTranslationFor…...

前端导出pdf,所见即所得

一、推荐方案&#xff1a;html2canvas jsPDF&#xff08;图片式PDF&#xff09; javascript import html2canvas from html2canvas; import jsPDF from jspdf;const exportPDF async (elementId, fileName) > {const element document.getElementById(elementId);// 1.…...

单片机上SPI和IIC的区别

SPI&#xff08;Serial Peripheral Interface&#xff09;和IC&#xff08;Inter-Integrated Circuit&#xff09;是两种常用的嵌入式外设通信协议&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的详细对比&#xff1a; — 1. 基本概念 SPI&#xff0…...

03-DevOps-安装并初始化Gitlab

Gitlab可以理解为是自己搭建的GitHub&#xff0c;也就是自己的代码仓库。 开启macvlan 在192.168.1.10服务器上&#xff0c;构建Macvlan网络&#xff0c;这种网络模式可以为每个容器独立分配ip。 docker network create -d macvlan \--subnet192.168.1.0/24 \--ip-range192.16…...

RabbitMQ 从入门到精通:从工作模式到集群部署实战(五)

#作者&#xff1a;闫乾苓 系列前几篇&#xff1a; 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;一&#xff09;》&#xff1a;link 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;二&#xff09;》&#xff1a; lin…...

DFS+回溯+剪枝(深度优先搜索)——搜索算法

DFS也就是深度优先搜索&#xff0c;比如二叉树的前&#xff0c;中&#xff0c;后序遍历都属于DFS。其本质是递归&#xff0c;要学好DFS首先需要掌握递归。接下来咱们就一起来学习DFS涉及的算法。 一、递归 1.什么是递归&#xff1f; 递归可以这样理解把它拆分出来&#xff0…...

使用PyCharm创建项目以及如何注释代码

创建好项目后会出现如下图所示的画面&#xff0c;我们可以通过在项目文件夹上点击鼠标右键&#xff0c;选择“New”菜单下的“Python File”来创建一个 Python 文件&#xff0c;在给文件命名时建议使用英文字母和下划线的组合&#xff0c;创建好的 Python 文件会自动打开&#…...

ArrayList和LinkedList有什么区别?在什么情况下使用ArrayList更高效?

ArrayList和LinkedList在Java中是两种常用的数据结构&#xff0c;分别基于数组和链表实现。它们在性能、内存使用和适用场景上各有特点。 ArrayList与LinkedList的主要区别 数据结构&#xff1a; ArrayList&#xff1a;基于动态数组实现&#xff0c;元素存储在连续的内存空间…...

Spring MVC 拦截器(Interceptor)与过滤器(Filter)的区别?

1、两者概述 拦截器&#xff08;Interceptor&#xff09;&#xff1a; 只会拦截那些被 Controller 或 RestController 标注的类中的方法处理的请求&#xff0c;也就是那些由 Spring MVC 调度的请求。过滤器&#xff08;Filter&#xff09;&#xff1a; 会拦截所有类型的 HTTP …...

elasticsearch实战应用从入门到高效使用java集成es快速上手

Elasticsearch 因其出色的性能、可扩展性和易用性,成为了处理大规模数据和构建搜索引擎的首选工具。本文将通过一个实际案例,详细讲解如何在 Spring Boot 项目中集成 Elasticsearch,进行数据索引、搜索、聚合分析等操作。 一、Elasticsearch 简介 Elasticsearch 是一个基于…...

Spring Boot 整合 JPA 实现数据持久化

目录 前言 一、JPA 核心概念与实体映射 1. 什么是 JPA&#xff1f; 2. JPA 的主要组件 3. 实体映射 4. 常见的字段映射策略 二、Repository 接口与自定义查询 1. 什么是 Repository 接口&#xff1f; 2. 动态查询方法 3. 自定义查询 4. 分页与排序 三、实战案例&…...

如何优化网站结构以促进快速收录?

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/104.html 优化网站结构以促进快速收录&#xff0c;可以从以下几个方面入手&#xff1a; 一、合理规划页面结构 扁平化结构&#xff1a;采用扁平化的网站结构&#xff0c;减少层级&#xf…...