基于Linux环境的进度条实现

文章目录


前言

在Linux环境下,C语言的输入输出控制有其独特的魅力和实际应用场景。本文将从回车换行和缓冲区的基础知识讲起,带领大家探索如何在Linux环境中实现一个动态倒计时功能,并进一步完成一个具有交互感的进度条。通过这些内容,你不仅可以理解C语言在Linux中的输出行为,还能掌握如何通过代码提升程序的可视化表现。无论是Linux开发初学者,还是想深入了解C语言底层实现的同学,这篇文章都将为你带来新的启发。


📚一、预备知识

📖1.1 回车换行

真正意义上,回车换行其实是两个动作,在C语言中\n却同时完成了回车+换行的两步动作。

  • 回车:将光标移到当前行的最左侧
  • 换行:将光标移到当前行对应位置的下一行

在C语言中可以使用转义字符\n来实现单独的回车行为。

如图展示以下以前的老式键盘:

这种电脑键盘上的ENTER按键就是同时实现了回车和换行的功能,按下ENTER键,光标会去到下一行的最左侧的位置。

📖1.2 缓冲区

先看一段代码

c 复制代码
#include <unistd.h>
int main() {
     printf("hello world\n");                                               
     sleep(2);
     return 0;
}

这段代码很简单,现在屏幕上打印出hello world,接着调用sleep函数让程序休眠两秒,

间隔两秒后。

接下来,我们对上面的代码稍作修改,去掉\n再来试试。

c 复制代码
#include <unistd.h>
int main() {
     printf("hello world");                                  
     sleep(2);
     return 0;
}

在去掉/n后对代码编译运行,先是休眠了两秒,

接着才在屏幕上打印出hello world,并且因为没有\n,所以打印完后没有换行,导致命令行提示符就紧跟在打印结果的后面。

情景分析

  • 那么问题来了,这段代码是先执行sleep,还是先执行printf打印呢?

很多人会根据上面的现象猜测,这段代码先执行了sleep休眠,再去执行printf打印,这样的猜测是错误的!因为任何一个C语言程序,都是严格按照代码的编写顺序去执行的。

  • 那在休眠的两秒期间,printf的打印结果存在哪里了呢?

hello world其实是保存在了缓冲区 中,缓冲区是用于临时存储数据的内存空间,默认当程序结束的时候才会将缓冲区中的内容刷新出来

如何强制刷新缓冲区

任何一个C语言程序运行的时候都会默认帮我们打开以下三个流:

  • stdin - - - - 标准输入流(键盘)
  • stdout - - - - 标准输出流(显示器)
  • stderr - - - - 标准错误(显示器)

Linux下一切皆文件,这三个流都是FILE*的指针,所以任何一个C语言程序运行的时候,操作系统会帮我们打开以上三个文件。今天我们只需要关心stdout标准输出流即可。我们可以通过fflush函数来刷新缓冲区。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main(){
    printf("hello world");
    fflush(stdout);                                                                                          
    sleep(2);

    return 0;
}

等待两秒后...

通过上面的分析我们可以得出,刷新缓冲区主要有以下几种方法:

  • \n可以刷新缓冲区。
  • 程序结束也会刷新缓冲区。
  • fflush(stdout)可以手动刷新缓冲区。

📚二、倒计时

学习了上面的东西,我们可以先来实现一个简单的倒计时练练手

📖2.1 源代码

cpp 复制代码
#include "processBar.h"
#include <unistd.h>

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

📖2.2 效果展示

从 10 开始计数

直到变成 0 为止。

📖2.3 注意事项:

  • 每打印一个数字后紧跟着打印一个\r回车,让光标回到这一行最开始的位置,这样新打印的数字就会去覆盖掉老的数字。但是\r不会去刷新缓冲区,因此在每打印完一个数字后,需要调用fflush(stdout)来刷新缓冲区。

  • 这里我们需要知道,往显示器上打印整型10,本质上是打印了字符1和字符0,由于这两个字符是挨在一起的,我们看起来就像是整型10。因此打印10,会占用两个字符,而打印0~9只需要一个字符,所以\r回车之后去覆盖写,只会覆盖一个字符,对第二个字符0始终没有影响,因此我们需要用%-2d来控制,每次打印两个位宽的字符,-表示将这两个字符左对齐。如果不进行格式化控制,打印出来的结果将是下面这样:

📚三、进度条

📖3.1 源代码

📓processBar.h
c 复制代码
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='                                                                                                  
#define TOP 100
#define BODY '$'
extern void processbar();
📓processBar.c
c 复制代码
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";//旋转提示
void processbar(){
    char bar[NUM];
    memset(bar, '\0', sizeof(bar));
	int len = strlen(lable);

	int cnt = 0;
	while(cnt <= TOP){
    	printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%len]);
   		fflush(stdout);
    	bar[cnt++] = STYLE;
    	if(cnt < 100) {
        bar[cnt] = BODY;                                                                                           
    	}
    	usleep(100000);//以微秒为单位进行休眠,想让进度条10秒跑完,因为一共会循环101次,所以每次循环大概就是休眠0.1秒,100毫秒,10000微秒
	}
	printf("\n");
}
📓效果演示

📖3.2 代码分析

进度条往右走的实现原理

  1. 进度条的可视化:
    • bar表示进度条的当前状态,用字符填充进度条并逐步延长。
    • cnt代表当前进度百分比(从0到100)。
  2. 动态旋转提示:
    • lable是旋转提示符,依次显示|, /, -, \,用来模拟动态效果。
  3. 每次刷新屏幕:
    • 使用\r回到行首并覆盖之前的内容,fflush(stdout)刷新输出缓冲区,确保显示即时更新。
    • 通过usleep(100000)控制刷新间隔(每0.1秒更新一次)。

while循环逻辑分析:

c 复制代码
while(cnt <= TOP) {
    printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt % len]);
    fflush(stdout);  // 强制刷新输出缓冲
    bar[cnt++] = STYLE;  // 填充进度条中的下一个字符
    if(cnt < 100) {
        bar[cnt] = BODY;  // 设置进度条下一位置的占位符(非满状态)
    }
    usleep(100000);  // 延迟0.1秒
}

分析逐步展开:

  1. 初始状态:
    • cnt0开始,bar数组全为空字符,进度条未显示任何填充内容。
    • 动态提示符从lable的第一个字符开始(|)。
  2. 每次循环中:
    • 动态更新输出:
      • 使用printf打印格式化输出:
        • [%-100s]:打印一个左对齐的进度条,长度为100字符。
        • [cnt%%]:打印当前百分比。
        • [lable[cnt % len]]:显示旋转提示符,cnt % len保证提示符循环显示。
    • 刷新进度条:
      • bar[cnt++] = STYLE:在bar数组的第cnt位置填充进度条样式字符STYLE
      • 如果cnt < 100,在下一个位置设置占位符BODY(非满状态时)。
    • 延迟:
      • usleep(100000)延迟0.1秒,控制进度条更新的速度。
    • 覆盖上一行:
      • 使用\r回到行首,使当前输出覆盖上一行,达到刷新效果。
  3. 终止条件:
    • cnt > TOP时退出循环,表示进度条已完成。
  4. 完成状态:
    • 输出"\n"换行符,表示进度条结束。

📖3.3 实际使用场景

上面的processBar.c中为了演示进度条的原理,在里面写了一个while循环来模拟,但实际上的进度条并不是这样用的。以下载东西为例,作为一个进度条,它本身并不知道下载了多少,它只会提供一个接口,在下载东西的时候,调用这个接口,然后将已经下载好的比率作为参数传给进度条模块,它会根据比率打印出对应的进度条样式。

版本一

c 复制代码
//processBar.h
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='
#define TOP 100
#define BODY '>'
extern void processbar(int ret);
c 复制代码
//processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
const char* lable = "|/-\\";
//V2版本
char bar[NUM] = {'\0'};//定义在全局避免每一次函数调用都会重现创建                         
void processbar(int ret){
	if(ret <0 || ret > 100){ 
		return;
	}
	if(ret == 0){ //当比率为0的时候将数组全置为'\0'
		memset(bar, '\0', sizeof(bar));
	}
	int len = strlen(lable);
	printf("[%-100s][%d%%][%c]\r", bar, ret, lable[ret%len]);
	fflush(stdout);
	bar[ret++] = STYLE;
	if(ret < 100){
		bar[ret] = BODY;
	}
}
c 复制代码
//main.c
int main(){                                                 
    int total = 1000;//假设总共要下载1000个G  
    int cur = 0;//当前下载的  
    while(cur <= total) {                                                    
        processbar(cur * 100 / total);                   
        usleep(50000);//模拟下载花费时间                 
        cur += 10;//循环下载了一部分,更新进度           
    }                                                    
   return 0;   
}

版本二

c 复制代码
//processBar.h
#pragma once
#include <stdio.h>
#define NUM 102
#define STYLE '='
#define TOP 100
#define BODY '>'
extern void processbar(int ret);
c 复制代码
//processBar.c
#include "processBar.h"
#include <string.h>
#include <unistd.h>
#define NONE "\033[m"
#define RED "\033[0;32;31M"
#define GREEN "\033[0;32;32m"
#define LIGHT_BLUE "\033[1;34m"
#define LIGHT_PURPLE "\033[1;35m"
const char* lable = "|/-\\";
//V2版本
char bar[NUM] = {'\0'};
void processbar(int ret){
	if(ret <0 || ret > 100)//合理性判断{
		return;
	}
	if(ret == 0)//当比率为0的时候将数组全置为'\0'{
		memset(bar, '\0', sizeof(bar));
	}
	int len = strlen(lable);
	printf("["LIGHT_BLUE"%-100s"NONE"]""[%d%%][%c]\r", bar, ret, lable[ret%len]); 
	fflush(stdout);                                                        
	bar[ret++] = STYLE;
	if(ret < 100){
		bar[ret] = BODY;
	}
}
c 复制代码
//main.c
#include "processBar.h"                                                     
#include <unistd.h>                                                         
typedef void (*callback_t) (int);                                             
//模拟一种安装或者下载                                                           
void Downbload(callback_t ct) {                                           
	int total = 1000;//假设总共要下载1000个MB                                           
	int cur = 0;//当前下载的                                                  
	while(cur <= total) {                                                      
		int rate = cur*100/total;
		ct(rate);       
		usleep(50000);//模拟下载花费时间                            
		cur += 10;//循环下载了一部分,更新进度 
	}                  
	printf("\n");                               
}                                                                                
int main(){                                     
	printf("Downbload 1:\n");                                 
	Downbload(processbar);                                                                                               
	printf("Downbload 2:\n");                     
	Downbload(processbar);                                                                                     
	printf("Downbload 3:\n");                                       
	Downbload(processbar);                                                                                                     
	printf("Downbload 4:\n"); 
	Downbload(processbar);
	return 0;
}

效果展示


结语

在Linux环境中,掌握C语言的缓冲区管理和动态输出功能是一项非常实用的技能。从回车换行的基础概念到炫酷的进度条展示,我们一步步地感受到了C语言的强大控制力以及其在终端交互中的无限潜力。希望本文能帮助你更好地理解Linux环境下C语言的这些核心知识点,同时也为你的编程旅程增添更多的趣味与技巧!期待你在实践中创造更多精彩!

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连 支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

相关推荐
emma羊羊1 分钟前
【SQL注入】延时盲注
数据库·sql·网络安全
币须赢12 分钟前
英伟达Thor芯片套件9月发货 “物理AI”有哪些?
大数据·人工智能
dyxal17 分钟前
linux系统安装wps
linux·运维·wps
盼小辉丶18 分钟前
Transformer实战(18)——微调Transformer语言模型进行回归分析
深度学习·语言模型·回归·transformer
格林威21 分钟前
机器视觉检测如何使用360 度全景成像镜头进行AI 瑕疵检测
人工智能·深度学习·数码相机·机器学习·计算机视觉·视觉检测·相机
啟明起鸣26 分钟前
【网络编程】从与 TCP 服务器的对比中探讨出 UDP 协议服务器的并发方案(C 语言)
服务器·c语言·开发语言·网络·tcp/ip·udp
一叶飘零_sweeeet27 分钟前
从 MySQL 到 TiDB:分布式数据库的无缝迁移与实战指南
数据库·mysql·tidb
大家的笔记本33 分钟前
jetson orin super nano(arm linux系统)上读取大恒图像工业相机(型号MER-050-560U3C)教程
linux·arm开发·相机
互联网之声39 分钟前
崔传波教授:以科技与人文之光,点亮近视患者的清晰视界‌
人工智能