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

简陋的进度条程序

简陋的进度条程序

processbar.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>#define NUM 103
#define Body '='
#define Head '>'typedef void (*callback_t)(double);//version 1
void process();//version 2
void process_flush_a(double rate);//version 3
void process_flush_b(double rate);

processbar.c

#include "processbar.h"
#include <unistd.h>
const char* lable = "|\\-/";// version 1
void process()
{char buffer[NUM] = {'\0'};int count = 0;buffer[0] = Head;while(count<=100){//打印字符数组printf("[%-100s][%3d%%] %c\r",buffer,count,lable[count%4]);//打印执行后要立即刷新屏幕,让打印内容出现在屏幕上fflush(stdout);buffer[count++] = Body;if(count < 100){buffer[count] = Head;}usleep(50000);}printf("\n");
}// version 2static char buffer[NUM] = {'\0'};
static int  count = 0;
void process_flush_a(double rate)
{int temp = (int)rate;if(temp < 1) buffer[0] = Head;printf("[%-100s][%.1f%%] %c\r",buffer,rate,lable[count%4]);fflush(stdout);buffer[temp++] = Body;if(temp < 100){buffer[temp] = Head;}count++;count %= 4;}// version 3void process_flush_b(double rate)
{int temp = (int)rate;// 先打印白色背景的进度条底色printf("\033[48;5;7m");  // 设置背景为白色for (int i = 0; i < 100; i++) {printf(" ");}printf("\033[0m\r");  // 重置颜色并回到行首// 打印紫色进度部分printf("\033[48;5;135m");  // 设置背景为紫色for (int i = 0; i < temp; i++) {printf(" ");}printf("\033[0m");  // 重置颜色// 打印百分比和旋转符号printf("[%.1f%%] %c\r", rate, lable[count % 4]);fflush(stdout);count++;count %= 4;
}

main.c

#include "processbar.h"
#define FILESIZE 1024*1024*1024//模拟场景:下载任务
void download(callback_t cb)
{srand(time(NULL)^1023);int total = FILESIZE;while(total){usleep(10000);int one = rand()%(1024*1024);total -= one;if(total < 0) total = 0;int doned = FILESIZE - total;double rate = ((double)doned / (double)(FILESIZE)) * 100.00;cb(rate);}//这里直接调用函数也是可以的//process_flush_a(rate);printf("\n");
}int main()
{// process();download(process_flush_a);//download(process_flush_b);return 0;
}

Makefile

pb: main.o processbar.ogcc main.o processbar.o -o pbmain.o: main.c processbar.hgcc -c main.c -o main.oprocessbar.o: processbar.c processbar.hgcc -std=c99 -c processbar.c -o processbar.o.PHONY:clean
clean:rm -f *.o pb

这个进度条程序是在Linux系统写的,在Windows下运行可能需要更换一下头文件。

这个程序的运行主要依托下面两个知识点:

回车\r与换行\n

这个故事要从机械打字机时代开始讲起。

1. 历史起源:机械打字机

在计算机诞生之前,电动机械打字机是处理文本的主要工具。它有两个独立的操作:

  • 回车 (Carriage Return, CR)
    • 动作:将打印头( carriage )移回当前行的最左端(起始位置)。
    • 目的:为了在下一行的相同起始位置继续打印,或者在同一行重复打印(如生成下划线)。
  • 换行 (Line Feed, LF)
    • 动作:将卷筒旋转一行,使纸张上移一行。
    • 目的:将打印位置移动到下一行。

所以要开始新的一行,需要依次执行两个动作:换行(移动到下一行)和回车(移动到行首)。这两个动作是分开的,由两个不同的杠杆或按钮控制。

早期的计算机设计者沿用了这个逻辑,因为计算机最早的输出设备就是电传打字机(Teletype)。

这个从我们的键盘上也可以看的出来:

在这里插入图片描述


2. 计算机中的表示:ASCII 码

计算机字符编码标准 ASCII(American Standard Code for Information Interchange)为这两个操作分配了特定的控制字符:

  • 回车符 (Carriage Return):ASCII 码 13 (十六进制 0x0D),在C语言和许多其他语言中用 \r 表示。
  • 换行符 (Line Feed):ASCII 码 10 (十六进制 0x0A),在C语言和许多其他语言中用 \n 表示。

因此,在纯文本层面,\r\n 就是两个特殊的控制字符,它们没有可见的图形表示,而是用来控制光标或文本流的布局。


3. 不同操作系统的分歧

这里就是所有混乱的根源。对于“如何表示一行的结束”,不同的操作系统阵营做出了不同的选择:

  1. Unix / Linux / macOS (现代版本) / Android

    • 标准:只使用 \n (LF) 来表示新行。
    • 哲学\n 的含义就是“新行”(Newline),它同时包含了回车和换行的语义。更简洁、高效。
  2. Windows / DOS

    • 标准:使用 \r\n (CR LF) 两个字符的组合来表示新行。
    • 哲学:严格遵循了打字机的传统,先回车再换行。这是从CP/M系统继承下来的历史遗产。
  3. Classic Mac OS (macOS 9 及之前版本)

    • 标准:只使用 \r (CR) 来表示新行。
    • 哲学:另一个历史分支。苹果的现代系统(基于Unix的macOS)已经转向使用 \n

这种分歧导致的问题
当你把一个在Linux上创建的文本文件用Windows的记事本打开时,所有内容可能会显示在同一行,因为记事本只认识 \r\n 作为换行符,而不认识单独的 \n
反之,一个带有 \r\n 的Windows文本文件在Unix系统上显示时,有时会在行尾多出一个 ^M 符号(这是Vi/Vim等编辑器显示 \r 字符的方式)。

现代的高级文本编辑器(如VSCode, Sublime Text, Notepad++等)都能自动识别并正确处理这两种换行符。


4. C语言中的 \r\n

在C语言中,\r\n转义序列,它们被编译器分别解释为ASCII码13和10。

关键点:C语言标准库中的I/O函数有一个重要的“文本模式”和“二进制模式”的区别,这直接影响了对 \r\n 的处理。

文本模式 ("r", "w") vs 二进制模式 ("rb", "wb")
  • 在文本模式下 ("w", "r")

    • 写入时:当你在程序中使用 printf("Hello\nWorld"); 并向文件写入时,C标准库会根据当前平台进行转换
      • Windows 上,\n 会被转换为 \r\n 再写入文件。
      • Linux 上,\n 保持不变,直接写入文件。
    • 读取时:当从文件读取时,标准库会进行反向转换。
      • 在Windows上,文件中的 \r\n 会被转换回单个的 \n 传入程序。
      • 在Linux上,文件中的 \n 保持不变传入程序。
    • 目的:为了让程序员只需使用 \n,而不用关心底层操作系统的差异,实现代码的跨平台性。\n 在C语言中就是“新行”的抽象表示。
  • 在二进制模式下 ("wb", "rb")

    • 没有任何转换。你写入什么字节,文件里就存储什么字节;你读取到什么字节,程序就看到什么字节。
    • 如果你在Windows上用 fputs("Hello\nWorld", file) 并以二进制模式 "wb" 写入,文件里存储的就是 Hello\nWorld\n 是ASCII 10)。
    • 如果你写入 "Hello\r\nWorld",文件里存储的就是 Hello\r\nWorld
控制台输出行为

当你在C程序中使用 printf("\n"); 向控制台(屏幕)输出时:

  • 在绝大多数系统上,\n 都会同时实现换行和回车的效果,将光标移动到下一行的行首。控制台设备本身已经将 \n 解释为“新行”。
  • 单独使用 \r 会有趣:printf("Loading... 50%\r");。这会将光标移回当前行的行首,然后后续的输出会覆盖当前行的内容。这常被用来制作命令行中的进度提示或动态更新效果,例如:
    #include <stdio.h>
    #include <unistd.h> // for sleep()int main() {for (int i = 0; i <= 100; i += 10) {printf("\rProgress: %3d%%", i); // \r 回到行首,覆盖上次的内容fflush(stdout); // 强制刷新输出缓冲区,立即显示sleep(1);}printf("\nDone!\n");return 0;
    }
    
    输出会原地更新百分比,而不是打印新行。

小点总结

概念C语言转义符ASCII码历史含义现代含义(主要)
回车 (Carriage Return)\r13 (0x0D)将打印头移回行首将光标移回行首(常用于覆盖输出)
换行 (Line Feed)\n10 (0x0A)将纸张上移一行新行(抽象概念,效果是光标移到下一行行首)

核心要点

  1. 历史\r (回车) 和 \n (换行) 源于机械打字机的两个独立动作。
  2. 分歧:Windows用 \r\n 表示行尾,Unix/Linux用 \n 表示行尾。
  3. C语言抽象:在代码中,你应始终使用 \n 来表示换行,以保证可移植性。
  4. C语言I/O模式
    • 文本模式:隐藏了操作系统的差异,帮你自动转换 \n\r\n
    • 二进制模式:直接进行字节操作,不做任何转换。
  5. 特殊用途\r 可用于命令行下的动态输出效果。

输出缓冲区

一、什么是输出缓冲区?

想象一下你要寄很多明信片给朋友。

  • 没有缓冲区:每写完一张明信片,你就立刻跑到邮筒去投递一张。如果你的朋友遍布全球,你这整天就光在路上跑了,效率极低。
  • 有缓冲区:你先把所有明信片都写完,堆在桌子上(这个“桌子”就是缓冲区)。等攒够一定数量(比如10张),或者你决定今天不再写了,你再把这一整叠明信片一次性拿去投递。

输出缓冲区(Output Buffer) 就是计算机世界中这样一个“桌子”。它是一块临时的内存区域,用于暂存程序准备输出的数据。程序不直接与输出设备(屏幕、磁盘、网络)打交道,而是先把数据写到这块内存里,之后再由特定的机制(刷新缓冲区)将数据一次性发送到目标设备。


二、为什么要有输出缓冲区?它的作用是什么?

设立缓冲区的主要目的可以归结为两个字:效率

1. 极大减少系统调用次数,提升性能

这是最核心的原因。与硬件设备(如磁盘、显示器、网络接口)进行I/O操作(write系统调用)是非常耗时的。它需要从用户模式切换到内核模式,让操作系统来协调硬件,这个过程比在内存中操作数据要慢成千上万倍。

  • 无缓冲:如果每次 printf(‘a’) 都直接让系统向屏幕写一个字符,程序大部分时间都在等待这个缓慢的I/O操作完成。
  • 有缓冲:程序可以飞快地把 ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘\n’ 这几个字符依次放入内存中的缓冲区。仅当缓冲区满了,或者遇到换行符 ‘\n’ 等特殊条件时,才发起一次系统调用,将所有数据一次性写入。这将成千上万次的慢速操作减少为几次,性能提升是巨大的。
2. 协调不同设备之间的速度差异

CPU和内存的处理速度(纳秒级)与磁盘、网络I/O的速度(毫秒级)有着天壤之别。缓冲区就像一个“水库”,在洪水季(CPU高速产生数据)时蓄水,然后在枯水季(I/O设备空闲时)平稳放水,避免了洪水直接冲击下游(I/O设备忙不过来)。


三、缓冲区在C语言中的具体体现

C标准库(stdio.h)为我们管理了缓冲区,通常有三种缓冲策略:

缓冲模式行为典型应用
全缓冲(Fully Buffered)缓冲区填满时,才执行真正的I/O操作。磁盘文件。这是最高效的方式,因为磁盘块读写有固定大小。
行缓冲(Line Buffered)遇到换行符 ‘\n’ 时,或者缓冲区填满时,执行I/O操作。标准输入(stdin)和标准输出(stdout)(当它们连接到终端时,即命令行界面)。这方便我们与程序交互,一行的输入/输出是自然的单位。
无缓冲(Unbuffered)数据立即输出,不经过缓冲区。标准错误(stderr)。这样错误信息可以立即被用户看到,即使程序后面崩溃了,错误信息也已经打印出来了。
关键函数:fflush

int fflush(FILE *stream);
这个函数强制将指定流的缓冲区中的内容立即写入,即使它还没满。这在需要确保用户立即看到提示信息时非常有用。

示例:

#include <stdio.h>
#include <unistd.h> // for sleep()int main() {// 不加 fflushprintf("Waiting for 3 seconds");sleep(3); // 程序休眠3秒printf("...Done.\n");// 加 fflushprintf("Waiting for 3 seconds");fflush(stdout); // 强制刷新标准输出的缓冲区sleep(3);printf("...Done.\n");return 0;
}
  • 第一种情况:字符串 “Waiting for 3 seconds” 被放入行缓冲区,但因为它没有以 ‘\n’ 结尾,缓冲区不会自动刷新。你要等到3秒后 “...Done.\n” 被打印时,才会一次性看到全部输出。
  • 第二种情况:调用 fflush(stdout) 后,缓冲区被强制清空,内容立即显示在屏幕上,然后程序才开始休眠。这样用户就能立即看到提示信息。

四、一个常见的“坑”与调试技巧

很多初学者在调试程序时,会用 printf 打印日志来跟踪执行流程,有时会遇到令人困惑的情况:

#include <stdio.h>
#include <unistd.h>int main() {printf("Starting the loop...\n");for (int i = 0; i < 5; i++) {printf("Counter: %d ", i); // 注意:没有换行符sleep(1); // 每秒一次}printf("\nLoop finished.\n");return 0;
}

期望的输出是每秒打印一个数字:
Counter: 0 Counter: 1 Counter: 2 ...

实际的输出可能是:程序休眠了5秒,然后一下子把所有 Counter: X 都打印了出来。

为什么?
因为 printf(“Counter: %d “, i); 的输出内容没有换行符 ‘\n’,不足以触发行缓冲的刷新条件。并且缓冲区很小,这几条短消息也没能把它填满。所以所有的消息都暂存在缓冲区里,直到程序最后遇到 printf(“\nLoop finished.\n”); 中的 ‘\n’,或者 main 函数结束自动刷新所有缓冲区时,才被一次性输出。

解决方法

  1. 在需要立即输出的消息末尾加上换行符 \n
  2. printf 后手动调用 fflush(stdout)
  3. (不推荐)可以使用 setvbuf 函数将 stdout 设置为无缓冲模式。

小点总结

方面解释
是什么一块用于暂存输出数据的内存区域。
为什么要有核心目的:提升I/O效率。通过减少昂贵的系统调用次数,协调CPU与I/O设备之间的速度差异。
工作模式全缓冲(文件)、行缓冲(终端)、无缓冲(stderr)。
如何控制使用 \n 触发行缓冲刷新;使用 fflush(stdout) 强制立即刷新。
注意事项理解缓冲机制是写出正确交互式程序的关键,避免因缓冲导致的输出延迟误解程序逻辑。

输出缓冲区是系统设计中的一个经典权衡:用一点点内存空间作为代价,换取巨大的性能提升

终端倒计时

结合回车和缓冲区的知识,我们可以在Linux写一个倒计时的小程序:

#include <stdio.h>
#include <unistd.h>int main()
{int n = 10;while(n >= 0){printf("%-2d\r",n);fflush(stdout);--n;sleep(1);}return 0;
}

这里有同学可能就会对代码中的一句有疑惑:

//为啥是这样
printf("%-2d\r",n);
//而不是这样
printf("%d\r",n);

大家可以试试看这样打印出来的倒计时是不是这样的:

10 90 80 70 60 50...10 00(在这里我无法给大家演示回车的效果,只能在一行里面写完了。其实说完整点就是:终端会在同一位置依次显示 10 90 →…→1000

很明显这个并不是我们想要的倒计时的结果。

这个就涉及printf打印的本质了。

核心结论

printf 本质上并不是在直接打印整数、浮点数等,它最终都是在打印单个字符。 它扮演的是一个“翻译官”或“格式化器”的角色,其核心工作是:将内存中各种格式的二进制数据,按照格式说明符(如 %d, %f)的指示,转换为相应的字符序列(字符串),然后将这个字符序列逐个字符地送入输出缓冲区。

我们可以用一个简单的公式来理解:
printf = 解析格式字符串 + 将参数转换为字符序列 + 写入缓冲区


详细原理分解

让我们一步步拆解 printf("Hello, %d!", 42); 这个调用的执行过程。

第1步:解析格式字符串

printf 接收到的第一个参数是一个字符串 "Hello, %d!"。函数会逐个扫描这个字符串的每一个字符:

  1. 遇到普通字符 'H':直接将这个字符放入输出缓冲区。
  2. 'e':放入缓冲区。
  3. 'l':放入缓冲区。
  4. 'l':放入缓冲区。
  5. 'o':放入缓冲区。
  6. ',':放入缓冲区。
  7. ' '(空格):放入缓冲区。
  8. 遇到 '%':这表示一个格式说明符的开始。函数会继续读取下一个字符 'd',从而知道它需要处理一个 int 类型的参数。
第2步:处理可变参数并转换

C语言的可变参数机制(va_list, va_arg)使得 printf 能从栈上或指定的寄存器中按顺序获取你传入的额外参数。

  1. 获取参数:根据格式说明符 %d,函数知道要去获取一个 int 类型的参数。于是它从参数列表中取得整数值 42
  2. 整数到字符串的转换:这是最关键的一步。函数并不会直接去打印数字 42 的二进制形式。相反,它在内部调用了一个类似 itoa (integer to ASCII) 的函数,将整数值 42 转换为对应的字符序列 ['4', '2']
    • 转换算法大致是:不断将数字除以10,得到余数,然后将余数加上字符 '0' 的ASCII码,得到对应的数字字符。
    • 42 / 10 = 4 … 余 2 -> '2' = 2 + ‘0’
    • 4 / 10 = 0 … 余 4 -> '4' = 4 + ‘0’
    • 将得到的字符逆序排列,得到 '4', '2'
第3步:将转换后的字符序列送入缓冲区

现在,printf 有了需要输出的完整字符序列:
'H', 'e', 'l', 'l', 'o', ',', ' ', '4', '2', '!'

它将这些字符一个一个地放入与 stdout(标准输出)关联的输出缓冲区中。

第4步:刷新缓冲区

根据缓冲区的设置(通常是行缓冲):

  • 如果缓冲区满了,或者
  • 如果遇到了换行符 \n,或者
  • 程序正常结束

缓冲区的内容会被一次性刷新(flush)。这时,操作系统才真正执行系统调用(如 write),将缓冲区中的每一个字符的ASCII码发送到标准输出(通常是终端屏幕)。

终端接收到这些字符的ASCII码,再根据字体设置,将它们在屏幕上渲染成你看得懂的图形。


用代码和内存视角来理解

假设我们在一个32位小端序系统上:

int num = 42; // 整数42在内存中的二进制表示: 0x0000002A
char str[] = "Hello"; // 字符串在内存中: 'H','e','l','l','o','\0'printf("Number: %d", num);
printf("String: %s", str);
  • 对于 %d

    • printf 找到变量 num 的内存地址,读取4个字节的数据 0x2A000000(注意字节序)。
    • 不会直接输出这4个字节!而是将这4个字节代表的整数值 42 计算出来。
    • 然后将数值 42 转换为两个字符 '4' (ASCII码 52) 和 '2' (ASCII码 50)。
    • 最后将 N','u','m','b','e','r',':',' ','4','2' 这些字符的ASCII码送入缓冲区。
  • 对于 %s

    • printf 获取到 str 的地址,然后从这个地址开始,逐个读取字符直到遇到终止符 '\0'
    • 它直接将读到的字符 'H','e','l','l','o' 送入缓冲区。字符串本身在内存中就已经是字符序列了,所以这里不需要转换,只是简单的内存拷贝。

总结与类比

概念解释类比
内存中的数据整数、浮点数等以二进制形式存储。原材料(如小麦、生肉)。
格式说明符 %d, %x指定如何“解读”和“转换”二进制数据。菜谱(是做面包还是做面条)。
转换过程将二进制数转换为十进制、十六进制等对应的字符序列。烹饪过程(将原材料加工成食物)。
输出缓冲区暂存转换后得到的字符序列。餐盘(存放做好的食物)。
最终输出将缓冲区中的字符ASCII码发送到设备。上菜(把餐盘里的食物端上桌)。

所以:
printf 打印的不是原始的整数或浮点数本身,它打印的是这些数据经过转换后得到的字符表示。无论是整数、浮点数,还是字符串,最终都是以单个字符的形式被处理和输出的。字符串之所以看起来是“直接”打印的,只是因为它的内存表示本身就已经是最终的字符序列形式,无需转换。

继续

#include <stdio.h>
#include <unistd.h>int main()
{int n = 10;while(n >= 0){printf("%d\r",n);//fflush(stdout);--n;sleep(1);}return 0;
}

知道printf打印的原理后,理解这个程序的结果就很简单了。因为我们第一个打印的数字是10。那么printf实际在终端上打印出来的是两个字符'1''0'

当我们打印完这两个字符后,就进行了回车操作\r,所以这个时候我们又回到了行首,也就是字符'1'所在位置。接下来我们要打印的9 8 7...1 0都是单个字符的,所以这些9 8 7...1 0字符只会会覆盖字符'1'。所以就导致了我们看到的是10 90 80 70 60 50...10 00。原因就是下一位字符'0'没有被覆盖,所以会一直显示在屏幕上。

那么我们把printf("%d\r",n);换成printf("%-2d\r",n);就好了,这是为什么呢?

printf("%-2d\r", n); 这条语句的作用是在终端的当前行开头,以左对齐的方式打印一个占 2 个字符宽度的整数 n,并将光标移回行首,具体拆解如下:

1. 格式说明符 %-2d 的含义
  • %d:表示打印整数(n 的值)。

  • 2:指定字段宽度为 2 个字符。如果 n 的位数少于 2(比如 n=3),会在数字后面补空格以凑满 2 个字符;如果 n 的位数等于或超过 2(比如 n=10),则按实际位数打印,不截断。这样一来补的空格就可以覆盖掉打印10的时候的'0'了。

  • -:表示左对齐(默认是右对齐)。即数字靠左显示,多余的宽度用空格在右侧填充。

    示例

    • n=5 时,%-2d 会打印 5 (数字 5 + 1 个空格,共 2 个字符,左对齐)。
    • n=12 时,%-2d 会打印 12(刚好 2 个字符,无需补空格)。
2. 转义字符 \r 的作用

\r回车符,它的功能是将终端的光标移回当前行的开头(但不换行)。

这意味着:下一次在同一行打印内容时,会覆盖当前行已有的内容(从行首开始覆盖)。

整体效果

这条语句通常用于动态更新终端显示的数值(比如计数器、进度值),避免频繁换行导致的界面混乱。

进度条代码讲解

#include "processbar.h"
#include <unistd.h>
const char* lable = "|\\-/";// version 1
void process()
{char buffer[NUM] = {'\0'};int count = 0;buffer[0] = Head;while(count<=100){//打印字符数组printf("[%-100s][%3d%%] %c\r",buffer,count,lable[count%4]);//打印执行后要立即刷新屏幕,让打印内容出现在屏幕上fflush(stdout);buffer[count++] = Body;if(count < 100){buffer[count] = Head;}usleep(50000);}printf("\n");
}// version 2static char buffer[NUM] = {'\0'};
static int  count = 0;
void process_flush_a(double rate)
{int temp = (int)rate;if(temp < 1) buffer[0] = Head;printf("[%-100s][%.1f%%] %c\r",buffer,rate,lable[count%4]);fflush(stdout);buffer[temp++] = Body;if(temp < 100){buffer[temp] = Head;}count++;count %= 4;}

我们先看第一版本的。

#include "processbar.h"
#include <unistd.h>
//#define NUM 103
//#define Body '='
//#define Head '>'
const char* lable = "|\\-/";// version 1
void process()
{char buffer[NUM] = {'\0'};int count = 0;buffer[0] = Head;while(count<=100){//打印字符数组printf("[%-100s][%3d%%] %c\r",buffer,count,lable[count%4]);//打印执行后要立即刷新屏幕,让打印内容出现在屏幕上fflush(stdout);buffer[count++] = Body;if(count < 100){buffer[count] = Head;}usleep(50000);}printf("\n");
}

这个版本的进度条是为了实现进度条写的。也就是说它就是只能看看而已,没有什么实际作用。

版本1的进度条的原理和我们打印倒计时的类似,打印倒计时我们是打印整数,打印进度条我们打印的是字符串。

我们让字符数组buffer中的内容,随着count的增加,逐渐被字符'='覆盖。然后我们打印字符数组就ok了。这样我们就可以实现一个进度条的效果出来。

在这里插入图片描述

为什么我们把数组的大小设置为103呢?首先,count从0开始,直到count==100,那么也就是要进入101次循环,所以数组中就得有101个位置存储'=',其次就是'\0',所以起码要有102个位置。为了保险起见,我们就设置了103的大小。

其次我们还有一个常量字符串lable,这个主要是用来模仿,加载时候有东西在转,我们用count%4主要是为了能一直打印lable中的字符。


接着我们看第二个版本的,这个要结合main.c来看

main.c

#include "processbar.h"
#define FILESIZE 1024*1024*1024//模拟场景:下载任务
void download(callback_t cb)
{srand(time(NULL)^1023);int total = FILESIZE;while(total){usleep(10000);int one = rand()%(1024*1024);total -= one;if(total < 0) total = 0;int doned = FILESIZE - total;double rate = ((double)doned / (double)(FILESIZE)) * 100.00;cb(rate);}//这里直接调用函数也是可以的//process_flush_a(rate);printf("\n");
}int main()
{// process();download(process_flush_a);//download(process_flush_b);return 0;
}

version 2

#include "processbar.h"
#include <unistd.h>
//#define NUM 103
//#define Body '='
//#define Head '>'
const char* lable = "|\\-/";// version 2static char buffer[NUM] = {'\0'};
static int  count = 0;
void process_flush_a(double rate)
{int temp = (int)rate;if(temp < 1) buffer[0] = Head;printf("[%-100s][%.1f%%] %c\r",buffer,rate,lable[count%4]);fflush(stdout);buffer[temp++] = Body;if(temp < 100){buffer[temp] = Head;}count++;count %= 4;}

version2主要是为了更加贴近现实,因为我们日常中的进度条不只是单纯打印出来看个样子而已,而是为了展示某个运行的认为的进度,最常见的就是下载任务了。也就是说进度条这个东西是依附于运行中的任务的,所以我们就模拟了一个场景。

在这里插入图片描述


在这里插入图片描述

后面还有一个version3,这个版本主要是让进度条有了颜色,大家试一下就知道了。是基于version2版本改的,逻辑是差不多的,不过需要了解一下printf打印颜色的机制,这里我就不过多赘述了。


文章转载自:

http://dw73FnCd.trmpj.cn
http://lQUdH32b.trmpj.cn
http://VUu4X3gM.trmpj.cn
http://A0TGozhj.trmpj.cn
http://b4WYaR5r.trmpj.cn
http://KkWWRBEf.trmpj.cn
http://OjAPbePs.trmpj.cn
http://Py2xT3Oa.trmpj.cn
http://UHXNaBNp.trmpj.cn
http://YhknlABU.trmpj.cn
http://VGKPhjCN.trmpj.cn
http://rt0semoU.trmpj.cn
http://KnUiX2Uz.trmpj.cn
http://zF3IJuUt.trmpj.cn
http://YHfxOM4y.trmpj.cn
http://JGBhtoLa.trmpj.cn
http://xEAayFhm.trmpj.cn
http://CYUjLCPu.trmpj.cn
http://25TfNX6E.trmpj.cn
http://b2fUfjHu.trmpj.cn
http://8JQl3n9p.trmpj.cn
http://KaMN0vES.trmpj.cn
http://1hpvxTLx.trmpj.cn
http://bnjKrU8N.trmpj.cn
http://wU5J4DE1.trmpj.cn
http://ebgPmT8h.trmpj.cn
http://DlcNY5UX.trmpj.cn
http://NTzffhnc.trmpj.cn
http://id7SXgFj.trmpj.cn
http://IcmMAVVf.trmpj.cn
http://www.dtcms.com/a/384033.html

相关文章:

  • SpringAOP中的通知类型
  • Python之文件读写 day9
  • 深度学习和神经网络之间有什么区别?
  • Linux驱动学习(SPI驱动)
  • 【MySQL|第七篇】DDL语句——数据库定义语言
  • 计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
  • Redis集群部署模式全解析:原理、优缺点与场景适配
  • ESP32的烧录和执行流程
  • ABP vNext + OpenXML / QuestPDF:复杂票据/发票模板与服务器端渲染
  • Java 注解入门:从认识 @Override 到写出第一个自定义注解
  • 网络层 -- IP协议
  • 社招面试BSP:BootROM知识一文通
  • Knockout.js DOM 操作模块详解
  • 面试题知识-NodeJS系列
  • 【层面一】C#语言基础和核心语法-02(反射/委托/事件)
  • Jmeter性能测试实战
  • CSP-S 2021 提高级 第一轮(初赛) 阅读程序(3)
  • TTC定时器中断——MPSOC实战3
  • [数据结构——lesson10.2堆排序以及TopK问题]
  • Maven 本地仓库的 settings.xml 文件
  • 绑定数据管理
  • RTU 全面科普:从入门到 AI 时代的智能化演进
  • lxml对于xml文件的操作
  • 第23课:行业解决方案设计
  • 深入理解 Java 内存模型与 volatile 关键字
  • Alibaba Lens:阿里巴巴推出的 AI 图像搜索浏览器扩展,助力B2B采购
  • I.MX6UL:主频和时钟配置实验
  • 【前端知识】package-lock.json 全面解析:作用、原理与最佳实践
  • 计算机视觉(opencv)实战二十——SIFT提取图像特征
  • Android开发-SharedPreferences