手把手写Linux第一个小程序 - 进度条(5种版本)

本专栏内容为:Linux学习专栏,分为系统和网络两部分。 通过本专栏的深入学习,你可以了解并掌握Linux。

💓博主csdn个人主页:小小unicorn
⏩专栏分类:linux
🚚代码仓库:小小unicorn的代码仓库🚚
🌹🌹🌹关注我带你学习编程知识

目录

  • Linux第一个小程序 - 进度条
    • 行缓冲区的概念
    • \r和\n
    • 进度条代码及效果展示
    • 版本一:
      • Makefile:
      • process.h
      • process.c
      • main.c
    • 版本二:
      • Makefile:
      • process.h
      • process.c
      • main.c
    • 版本3
      • makefile
      • ProcessBar.c
      • ProcessBar.h
      • main.c
    • 版本4
      • ProcessBar.h
      • ProcessBar.c
      • main.c
    • 版本5(封神)
      • ProcessBar.h
      • ProcessBar.c
      • main.c
  • 函数回调
    • 回调函数的特点:
    • 工作原理
    • 使用场景
    • 示例

Linux第一个小程序 - 进度条

行缓冲区的概念

首先,我们来感受一下行缓冲区的存在,在Linux当中以下代码的运行结果是什么样的?
在这里插入图片描述
对于此代码,大家应该都没问题,当然是先输出字符串hello world然后休眠3秒之后结束运行。那么对于以下代码呢?
在这里插入图片描述
可以看到代码中仅仅删除了字符串后面的’\n’,那么代码的运行结果还与之前相同吗?答案否定的,该代码的运行结果是:先休眠3秒,然后打印字符串hello linux之后结束运行。该现象就证明了行缓冲区的存在。

显示器对应的是行刷新,即当缓冲区当中遇到’\n’或是缓冲区被写满才会被打印出来,而在第二份代码当中并没有’\n’,所以字符串hello linux先被写到缓冲区当中去了,然后休眠3秒后,直到程序运行结束时才将hello linux打印到显示器当中。

\r和\n

\r: 回车,使光标回到本行行首。
\n: 换行,使光标下移一格。

而我们键盘上的Enter键实际上就等价于\n+\r。
在这里插入图片描述
既然是\r是使光标回到本行行首,那么如果我们向显示器上写了一个数之后再让光标回到本行行首,然后再写一个数,不就相当于将前面一个数字覆盖了吗?

但这里有一个问题:不使用’\n’进行换行怎么将缓冲区当中数据打印出来?
这里我们可以使用fflush函数,该函数可以刷新缓冲区,即将缓冲区当中的数据刷新当显示器当中。
对此我们可以编写一个倒计时的程序。

在这里插入图片描述
在输出下一个数之前都让光标先回到本行行首,就得到了倒计时的效果。
在这里插入图片描述

进度条代码及效果展示

知道了\r这个概念我们就可以实现一个简单的进度条了。

首先在目录下创建一下文件:
在这里插入图片描述

版本一:

Makefile:

在这里插入图片描述

process:process.c main.cgcc -o $@ $^
.PHONY:clean
clean:rm -f process

process.h

#pragma once #include<stdio.h>void process();

process.c

在这里插入图片描述

#include"process.h"
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#define SIZE 101
#define MAX_RATR 100
#define STYLE '#'
#define STIME 1000*200
const char *str="|/-\\";
void process()
{//version1int rate=0;char bar[SIZE];memset(bar,'\0',sizeof(bar));int num=strlen(str);while(rate<=MAX_RATR){printf("[%-100s][%d%%][%c]\r",bar,rate,str[rate%num]);fflush(stdout);usleep(STIME);bar[rate++]=STYLE;}printf("\n");
}

main.c

在这里插入图片描述

#include"process.h"int main()
{process();return 0;
}

运行结果:
在这里插入图片描述

版本二:

Makefile:

在这里插入图片描述

process:process.c main.cgcc -o $@ $^
.PHONY:clean
clean:rm -f process

process.h

#pragma once #include<stdio.h>void process();

process.c

在这里插入图片描述

#include"process.h"
#include<string.h>
#include<unistd.h>
#include<stdlib.h>#define SIZE 101
#define MAX_RATR 100
#define STYLE '#'
#define STIME 1000*200const char *str="|/-\\";void process()
{//version1int rate=0;char bar[SIZE];memset(bar,'\0',sizeof(bar));int num=strlen(str);while(rate<=MAX_RATR){printf("\033[%d;%dm[%-100s]\033[0m\033[40;%dm[%d%%]\033[0m\033[40;%dm[%c]\033[0m\r",rand()%10+40,rand()%10+30,bar, rand()%10+30,rate,rand()%10+30, str[rate%num]);//printf("[%-100s][%d%%][%c]\r",bar,rate,str[rate%num]);fflush(stdout);usleep(STIME);bar[rate++]=STYLE;}printf("\n");
}

main.c

在这里插入图片描述

#include"process.h"int main()
{process();return 0;
}

运行结果:
在这里插入图片描述

版本3

当然,上面的进度条是’#'的方式体现的,我们还可以将它改为箭头:

makefile

ProcessBar:ProcessBar.c main.cgcc -o $@ $^
.PHONY:clean
clean:rm -f ProcessBar

ProcessBar.c

#include "ProcessBar.h"
#include <string.h>#define SIZE 101         // 定义进度条的大小为 101(用于表示 100% 的进度和一个终止字符)
#define MAX_RATR 100     // 最大的进度比例,设置为 100
#define STYLE '*'        // 进度条的显示字符,这里选择的是 #
#define STIME 1000 * 200 // 每次更新进度条的时间延迟,设置为 200 毫秒(1000 微秒 × 200)
#define BODY '='         // 进度条身体
#define TAIL '>'         // 进度条尾巴// 定义一个字符串,包含四个字符,用于在进度条更新时显示不同的进度指示符
const char *str = "|/-\\";void ProcessBar1()
{// version1int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}
void ProcessBar2()
{// version2int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{//\033[: 开始一个 ANSI 转义序列。\033 是 ASCII 字符 27(ESC),[ 后面跟随的数字定义了颜色和格式。//%d;%d: 这里有两个 %d,它们用于随机生成背景和前景的颜色。// 第一个 %d:rand() % 10 + 40,生成一个范围在 40 到 49 之间的整数,用于背景色(40-49 是标准的 ANSI 颜色代码)。// 第二个 %d:rand() % 10 + 30,生成一个范围在 30 到 39 之间的整数,用于前景色(30-39 是标准的 ANSI 颜色代码)。//%dm 是 printf 函数中用于格式化字符串的一个占位符,通常用于指定 ANSI 转义码中的颜色或样式。// 在这个中,d代表一个整数,m用于结束颜色或样式的设置。//\033[0m: 重置所有的属性和颜色,将后续的输出恢复到默认状态。这确保了前面的颜色设置只影响了进度条的显示,而不会影响之后的输出。//\033[40;%dm[%d%%]:(显示进度百分比)// rand() % 10 + 30:用于设置前景色,显示当前进度百分比。//[%d%%]:显示当前的进度百分比,rate 是当前进度值。//\033[40;%dm[%c]:(显示进度指示符)// rand() % 10 + 30:又一次设置前景色,用于显示当前的进度指示符。//[%c]:显示当前的进度指示符,使用 str[rate % num] 来从 str 字符串中选择一个字符。printf("\033[%d;%dm[%-100s]\033[0m\033[40;%dm[%d%%]\033[0m\033[40;%dm[%c]\033[0m\r",rand() % 10 + 40, rand() % 10 + 30, Bar, rand() % 10 + 30, rate, rand() % 10 + 30, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}void ProcessBar3()
{// version3int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = BODY;// 此时我们的rate在下一个位置,我们让=的下一个位置弄为>// 小心越界问题if (rate <= 100)Bar[rate] = TAIL;}printf("\n");
}
// 倒计时
void Countdown()
{int Size = 10;while (Size){printf("%-2d\r", Size);fflush(stdout);sleep(1);Size--;}// printf("hello world");
}

ProcessBar.h

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>// 版本1
void ProcessBar1();
// 版本2
void ProcessBar2();
// 版本3
void ProcessBar3();
// 倒计时
void Countdown();

main.c

#include "ProcessBar.h"
#include <unistd.h>int main()
{// 进度条// 版本一// ProcessBar1();// 版本二// ProcessBar2();// 版本3ProcessBar3();// 倒计时//  Countdown();return 0;
}

展示一下运行结果:
在这里插入图片描述
很明显这个版本的进度条比之前的好看多了。

版本4

我们为了更好的理解我们的进度条是如何被调用的,我们可以进行更改进度条版本4:
在这里插入图片描述
在之前的进度条版本里,我们的进度条可以说是通过循环来进行控制。

其实我们可以把这个循环抽离出来,抽离出来就是,给他一个比率,他打印多少。
在这里插入图片描述
接下来我们可以模拟一下实际的一个应用场景:
在这里插入图片描述
假设我们要下一个1000MB的东西,平常下载肯定是一点一点的下载,我们将当前值先从0开始,每次下载10MB,注意传参,我们传的是比率。

完整代码:

ProcessBar.h

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>// 版本1
void ProcessBar1();
// 版本2
void ProcessBar2();
// 版本3
void ProcessBar3();
// 版本4   
void ProcessBar4(int rate);
// 倒计时
void Countdown();

ProcessBar.c

#include "ProcessBar.h"
#include <string.h>#define SIZE 101         // 定义进度条的大小为 101(用于表示 100% 的进度和一个终止字符)
#define MAX_RATR 100     // 最大的进度比例,设置为 100
#define STYLE '*'        // 进度条的显示字符,这里选择的是 #
#define STIME 1000 * 200 // 每次更新进度条的时间延迟,设置为 200 毫秒(1000 微秒 × 200)
#define BODY '='         // 进度条身体
#define TAIL '>'         // 进度条尾巴char processbar[SIZE];
// 定义一个字符串,包含四个字符,用于在进度条更新时显示不同的进度指示符
const char *str = "|/-\\";void ProcessBar1()
{// version1int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}
void ProcessBar2()
{// version2int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{//\033[: 开始一个 ANSI 转义序列。\033 是 ASCII 字符 27(ESC),[ 后面跟随的数字定义了颜色和格式。//%d;%d: 这里有两个 %d,它们用于随机生成背景和前景的颜色。// 第一个 %d:rand() % 10 + 40,生成一个范围在 40 到 49 之间的整数,用于背景色(40-49 是标准的 ANSI 颜色代码)。// 第二个 %d:rand() % 10 + 30,生成一个范围在 30 到 39 之间的整数,用于前景色(30-39 是标准的 ANSI 颜色代码)。//%dm 是 printf 函数中用于格式化字符串的一个占位符,通常用于指定 ANSI 转义码中的颜色或样式。// 在这个中,d代表一个整数,m用于结束颜色或样式的设置。//\033[0m: 重置所有的属性和颜色,将后续的输出恢复到默认状态。这确保了前面的颜色设置只影响了进度条的显示,而不会影响之后的输出。//\033[40;%dm[%d%%]:(显示进度百分比)// rand() % 10 + 30:用于设置前景色,显示当前进度百分比。//[%d%%]:显示当前的进度百分比,rate 是当前进度值。//\033[40;%dm[%c]:(显示进度指示符)// rand() % 10 + 30:又一次设置前景色,用于显示当前的进度指示符。//[%c]:显示当前的进度指示符,使用 str[rate % num] 来从 str 字符串中选择一个字符。printf("\033[%d;%dm[%-100s]\033[0m\033[40;%dm[%d%%]\033[0m\033[40;%dm[%c]\033[0m\r",rand() % 10 + 40, rand() % 10 + 30, Bar, rand() % 10 + 30, rate, rand() % 10 + 30, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}void ProcessBar3()
{// version3int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = BODY;// 此时我们的rate在下一个位置,我们让=的下一个位置弄为>// 小心越界问题if (rate <= 100)Bar[rate] = TAIL;}printf("\n");
}void ProcessBar4(int rate)
{// 版本4// 我们重点理解一下我们的进度条是如何被调用的// 判断越界if (rate < 0 || rate > 100)return;int num = strlen(str);printf("[%-100s][%d%%][%c]\r", processbar, rate, str[rate % num]);fflush(stdout);processbar[rate++] = BODY;if (rate < 100)processbar[rate] = TAIL;
}
// 倒计时
void Countdown()
{int Size = 10;while (Size){printf("%-2d\r", Size);fflush(stdout);sleep(1);Size--;}// printf("hello world");
}

main.c

#include "ProcessBar.h"
#include <unistd.h>int main()
{// 进度条// 版本一// ProcessBar1();// 版本二// ProcessBar2();// 版本3//ProcessBar3();// 版本4// 我们模拟一下实际的应用场景:int total = 1000; // 假设我们有一个1000MB的东西int curr = 0;     // 当前下载了多少MB;while (curr <= total){ProcessBar4(curr * 100 / total);// 执行着某种下载的任务curr += 10; // 每次下载10MBusleep(50000);}printf("\n");// 倒计时//  Countdown();return 0;
}

我们看一下运行效果:
在这里插入图片描述
但是其实会发现,这个版本跟我们的版本一本质其实是一样的。来我们将我们的版本4在进行加工,写一份巨diao的一个进度条!!

版本5(封神)

我们将主函数里面的抽离成一个函数DownLoad。

我们让这个函数模拟我们的安装或者是下载。

// 模拟一种安装或者下载
void DownLoad()
{int total = 1000; // 假设我们有一个1000MB的东西int curr = 0;     // 当前下载了多少MB;while (curr <= total){// 执行着某种下载的任务usleep(50000);int rate = curr * 100 / total;curr += 10; // 每次下载10MB}printf("\n");
}

接下来我们定义一个函数指针类型;

typedef void (*callback_t)(int); // 函数指针类型

然后修改我们的DownLoad函数:

void DownLoad(callback_t cb)
{int total = 1000; // 假设我们有一个1000MB的东西int curr = 0;     // 当前下载了多少MB;while (curr <= total){// 执行着某种下载的任务usleep(50000);int rate = curr * 100 / total;cb(rate); // 通过回调,展示进度。curr += 10; // 每次下载10MB}printf("\n");
}

这样我们可以通过函数回调的方式来进行。
在主函数(main.c)中直接调用我们的DownLoad函数:

 DownLoad(ProcessBar4);

我们运行一下结果:
在这里插入图片描述
不仅如此,我们还可以实现多任务下载的一个情况:
我们在主函数写这样的语句

int main()
{// 版本5printf("DownLoda1:\n");DownLoad(ProcessBar4);printf("DownLoad2:\n");DownLoad(ProcessBar4);printf("DownLoad3:\n");DownLoad(ProcessBar4);printf("DownLoad4:\n");DownLoad(ProcessBar4);printf("DownLoad5:\n");DownLoad(ProcessBar4);return 0;
}

接下来我们运行一下:
在这里插入图片描述
我们会看到,当第二个任务的时候跟第一个不一样,欸?为什么呢?这是因为,我们刚在定义bar数组的时候,他是全局的,我们并没有刷新它的状态,所以可以理解为我们的数组是满的!!

我们有两种,一种是在我们的ProceBar函数中写一个mesert函数即可,另一种我们把他封装成函数,在主函数李直接掉用即可!

void InitBar()
{memset(processbar, '\0', sizeof(processbar));
}

我们在来看一下结果:
在这里插入图片描述
这样就不会出现上面的情况了,好了,介绍到这,我们的进度条5个版本就都介绍完了。

下面是完整代码的展示:

ProcessBar.h

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
typedef void (*callback_t)(int);
// 版本1
void ProcessBar1();
// 版本2
void ProcessBar2();
// 版本3
void ProcessBar3();
// 版本4
void ProcessBar4(int rate);
//
extern void InitBar();
 模拟一种安装或者下载
extern void DownLoad(callback_t cb);
// 倒计时
void Countdown();

ProcessBar.c

#include "ProcessBar.h"
#include <string.h>#define SIZE 101                 // 定义进度条的大小为 101(用于表示 100% 的进度和一个终止字符)
#define MAX_RATR 100             // 最大的进度比例,设置为 100
#define STYLE '*'                // 进度条的显示字符,这里选择的是 #
#define STIME 1000 * 200         // 每次更新进度条的时间延迟,设置为 200 毫秒(1000 微秒 × 200)
#define BODY '='                 // 进度条身体
#define TAIL '>'                 // 进度条尾巴
typedef void (*callback_t)(int); // 函数指针类型char processbar[SIZE];
// 定义一个字符串,包含四个字符,用于在进度条更新时显示不同的进度指示符
const char *str = "|/-\\";void ProcessBar1()
{// version1int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}
void ProcessBar2()
{// version2int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{//\033[: 开始一个 ANSI 转义序列。\033 是 ASCII 字符 27(ESC),[ 后面跟随的数字定义了颜色和格式。//%d;%d: 这里有两个 %d,它们用于随机生成背景和前景的颜色。// 第一个 %d:rand() % 10 + 40,生成一个范围在 40 到 49 之间的整数,用于背景色(40-49 是标准的 ANSI 颜色代码)。// 第二个 %d:rand() % 10 + 30,生成一个范围在 30 到 39 之间的整数,用于前景色(30-39 是标准的 ANSI 颜色代码)。//%dm 是 printf 函数中用于格式化字符串的一个占位符,通常用于指定 ANSI 转义码中的颜色或样式。// 在这个中,d代表一个整数,m用于结束颜色或样式的设置。//\033[0m: 重置所有的属性和颜色,将后续的输出恢复到默认状态。这确保了前面的颜色设置只影响了进度条的显示,而不会影响之后的输出。//\033[40;%dm[%d%%]:(显示进度百分比)// rand() % 10 + 30:用于设置前景色,显示当前进度百分比。//[%d%%]:显示当前的进度百分比,rate 是当前进度值。//\033[40;%dm[%c]:(显示进度指示符)// rand() % 10 + 30:又一次设置前景色,用于显示当前的进度指示符。//[%c]:显示当前的进度指示符,使用 str[rate % num] 来从 str 字符串中选择一个字符。printf("\033[%d;%dm[%-100s]\033[0m\033[40;%dm[%d%%]\033[0m\033[40;%dm[%c]\033[0m\r",rand() % 10 + 40, rand() % 10 + 30, Bar, rand() % 10 + 30, rate, rand() % 10 + 30, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = STYLE;}printf("\n");
}void ProcessBar3()
{// version3int rate = 0;                   // 初始化进度变量 rate 为 0。char Bar[SIZE];                 // 创建一个字符数组 Bar 用于表示进度条memset(Bar, '\0', sizeof(Bar)); // 将 Bar 数组的所有元素初始化为 '\0',即清空数组。int num = strlen(str);          // 计算字符串 str 的长度(4),用于后续的指示符更新while (rate <= MAX_RATR)        // 当进度小于等于 100 时,继续循环。{// 打印格式化的字符串//%-100s: 打印进度条,宽度为 100,左对齐//%d%%: 打印当前进度百分比  %%: 是用来打印字面上的百分号字符 %//%c: 打印当前进度指示符//\r: 回车符,使下一次输出覆盖当前行printf("[%-100s][%d%%][%c]\r", Bar, rate, str[rate % num]);// 刷新输出缓冲区,确保进度条立即显示。fflush(stdout);// 暂停程序执行一段时间(200 毫秒),用于控制更新速度。usleep(STIME);// 将当前进度位置用 # 更新,rate++ 后自增Bar[rate++] = BODY;// 此时我们的rate在下一个位置,我们让=的下一个位置弄为>// 小心越界问题if (rate <= 100)Bar[rate] = TAIL;}printf("\n");
}void ProcessBar4(int rate)
{// 版本4// 我们重点理解一下我们的进度条是如何被调用的// 判断越界if (rate < 0 || rate > 100)return;int num = strlen(str);printf("[%-100s][%d%%][%c]\r", processbar, rate, str[rate % num]);fflush(stdout);processbar[rate++] = BODY;if (rate < 100)processbar[rate] = TAIL;
}// 模拟一种安装或者下载
void DownLoad(callback_t cb)
{int total = 1000; // 假设我们有一个1000MB的东西int curr = 0;     // 当前下载了多少MB;while (curr <= total){// 执行着某种下载的任务usleep(50000);int rate = curr * 100 / total;cb(rate); // 通过回调,展示进度。curr += 10; // 每次下载10MB}printf("\n");
}void InitBar()
{memset(processbar, '\0', sizeof(processbar));
}// 倒计时
void Countdown()
{int Size = 10;while (Size){printf("%-2d\r", Size);fflush(stdout);sleep(1);Size--;}// printf("hello world");
}

main.c

#include "ProcessBar.h"
#include <unistd.h>int main()
{// 进度条// 版本一// ProcessBar1();// 版本二// ProcessBar2();// 版本3// ProcessBar3();// 版本4// 我们模拟一下实际的应用场景:/*int total = 1000; // 假设我们有一个1000MB的东西int curr = 0;     // 当前下载了多少MB;while (curr <= total){ProcessBar4(curr * 100 / total);// 执行着某种下载的任务curr += 10; // 每次下载10MBusleep(50000);}printf("\n");*/// 版本5printf("DownLoda1:\n");DownLoad(ProcessBar4);InitBar();printf("DownLoad2:\n");DownLoad(ProcessBar4);InitBar();printf("DownLoad3:\n");DownLoad(ProcessBar4);InitBar();printf("DownLoad4:\n");DownLoad(ProcessBar4);InitBar();printf("DownLoad5:\n");DownLoad(ProcessBar4);// 倒计时//  Countdown();return 0;
}

上述我们用到了函数回调,那么什么是函数回调呢?

函数回调

回调函数是指通过函数指针传递给另一个函数的函数。当这个函数执行到某个特定点时,它会调用传入的回调函数。这种机制在许多编程语言中广泛应用,尤其是在处理异步操作、事件驱动编程高阶函数时。

回调函数的特点:

  1. 灵活性:通过传递不同的回调函数,可以在同一个操作中实现不同的行为。
  2. 异步编程:常用于异步操作中,比如在网络请求完成后执行特定的代码。
  3. 代码解耦:可以将业务逻辑与具体实现分离,提高代码的可读性和可维护性。

工作原理

1. 函数指针:回调函数通常通过函数指针来传递。函数指针允许我们引用一个函数并在需要时调用它。
2. 调用时机:当一个函数执行到特定的时刻(比如完成某项工作、接收到事件等),它会调用传入的回调函数。

使用场景

1. 事件驱动编程:在图形用户界面(GUI)应用程序中,用户的点击、输入等操作会触发事件,程序可以通过回调函数来响应这些事件。
2. 异步操作:例如,在进行网络请求时,程序可以继续执行其他任务,而在请求完成时,通过回调函数来处理响应结果。
3. 排序算法:在许多编程语言中,排序函数允许用户传入自定义的比较函数作为回调,以决定元素的排序方式。

示例

在C语言中,可以通过函数指针来实现回调函数。以下是一个简单的示例:

#include <stdio.h>// 定义一个回调函数类型
typedef void (*Callback)(int);// 一个接受回调函数的函数
void executeCallback(Callback cb, int value) 
{cb(value);  // 调用传入的回调函数
}// 一个具体的回调函数实现
void myCallback(int x) 
{printf("Callback called with value: %d\n", x);
}int main() 
{// 调用executeCallback并传入myCallback作为回调executeCallback(myCallback, 42);return 0;
}

在这个例子中,myCallback 是一个回调函数,它被传递给 executeCallback,后者在适当的时候调用它。这样就实现了函数之间的灵活交互。
运行结果:
在这里插入图片描述

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

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

相关文章

dhcp池没有空闲ip导致手机无法获得ip

得到用户反馈&#xff0c;一个高速项目部的wifi无法接入&#xff0c;让排查原因。 反馈有的手机能接入&#xff0c;有的接入不了。查看ac界面发现有个终端获得的ip是169.254.xxx.xxx。 ip地址是169.254.96.17显然是手机打开wlan开关后&#xff0c;鉴权通过后dhcp过程&#xff0…

《高频电子线路》—— 振荡器稳定性问题

文章内容来源于【中国大学MOOC 华中科技大学通信&#xff08;高频&#xff09;电子线路精品公开课】&#xff0c;此篇文章仅作为笔记分享。 振荡器稳定性问题 频率准确度 & 频率稳定度 希望频率稳定度越小越好。 频率稳定度分类 影响振荡频率稳定度的参数 振荡频率是和电…

HTMLCSS: 打造跳一跳加载器,点燃用户等待热情

效果演示 这段 HTML 代码创建了一个简单的网页&#xff0c;其中包含一个动画效果&#xff0c;用来模拟一个加载器loading HTML <div class"loader"></div>div创建了一个动画效果的加载器 CSS html, body {width: 100vw;height: 100vh;display: flex…

Nginx 的 Http 模块介绍(上)

Nginx 的 Http 模块介绍&#xff08;上&#xff09; 1. http 请求 11 个处理阶段介绍 Nginx 将一个 Http 请求分成多个阶段&#xff0c;以模块为单位进行处理。其将 Http请求的处理过程分成了 11 个阶段&#xff0c;各个阶段可以包含任意多个 Http 的模块并以流水线的方式处理…

网络:ARP的具体过程和ARP欺骗

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》《网络》 《redis学习笔记》 文章目录 前言ARP具体过程ARP欺骗原理总结 前言 本文仅作为ARP具体过程和ARP欺骗的知识总结 硬件类型 &#xff1a;指定发送和接受ARP包的硬件类型&am…

[0260].第25节:锁的不同角度分类

MySQL学习大纲 我的数据库学习大纲 从不同维度对锁的分类&#xff1a; 1.对数据操作的类型划分:读锁和写锁 1.1.读锁 与 写锁概述&#xff1a; 1.对于数据库中并发事务的读-读情况并不会引起什么问题。对于写-写、读-写或写-读这些情况可能会引起一些问题&#xff0c;需要使用…

数据结构之链式结构二叉树的实现(初级版)

本文内容将主会多次用到函数递归知识&#xff01;&#xff01;&#xff01; 本节内容需要借助画图才能更好理解&#xff01;&#xff01;&#xff01; 和往常一样&#xff0c;还是创建三个文件 这是tree.h #pragma once #include<stdio.h> #include<stdlib.h> …

Data+AI━━人群圈选,你被圈中了吗?

DataAI━━人群圈选&#xff0c;你被圈中了吗&#xff1f; 前言我们是否正在失去选择的自主权&#xff1f;AI人群圈选流程AI人群圈选技术发展趋势 前言 智能时代的清晨&#xff0c;打开手机&#xff0c;各类APP精准推送的信息扑面而来。菜市场买过一次三文鱼&#xff0c;生鲜A…

手撕快排的三种方法:让面试官对你刮目相看

快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 &#x1f4af;前言 &#x1f4af;快速排序基础概念 &#x1f4af;Hoare 版本 1.算法思路 2.代码示例 3.有关该代码的问题 3.1&#x1f62e;为什么…

51单片机教程(五)- LED灯闪烁

1 项目分析 让输入/输出口的P1.0或P1.0~P1.7连接的LED灯闪烁。 2 技术准备 1、C语言知识点 1 运算符 1 算术运算符 #include <stdio.h>int main(){// 算术运算符int a 13;int b 6;printf("%d\n", ab); printf("%d\n", a-b); printf("%…

ceph补充介绍

SDS-ceph ceph介绍 crushmap 1、crush算法通过计算数据存储位置来确定如何存储和检索&#xff0c;授权客户端直接连接osd 2、对象通过算法被切分成数据片&#xff0c;分布在不同的osd上 3、提供很多种的bucket&#xff0c;最小的节点是osd # 结构 osd (or device) host #主…

集成ruoyi-it管理系统,遇到代码Bug

前言&#xff1a;这次ruoyi框架开发it管理系统&#xff0c;出现很多问题&#xff0c;也有学到很多东西&#xff0c;出现几个问题&#xff0c;希望下次项目不会出现或者少出现问题&#xff1b;其中还是有很多基础知识有些忘记&#xff0c;得多多复习 1&#xff1a;当写的代码没…

大模型面试-Layer normalization篇

1. Layer Norm 的计算公式写一下? 2. RMS Norm 的计算公式写一下? 3. RMS Norm 相比于 Layer Norm 有什么特点? 4. Deep Norm 思路? 5. 写一下 Deep Norm 代码实现? 6.Deep Norm 有什么优点? 7.LN 在 LLMs 中的不同位置 有什么区别么?如果有,能介绍一下区别么? 8. LLM…

【Linux第七课--基础IO】内存级文件、重定向、缓冲区、文件系统、动态库静态库

目录 引入内存级文件重新使用C文件接口 -- 对比重定向写文件读文件文件流 认识文件操作的系统接口open参数 -- flagflag的内容宏的传参方式 open关闭文件写文件读文件结论 引入文件描述符fd、对文件的理解理解一切皆文件方法集文件fd的分配规则 重定向代码的重定向输入重定向输…

手写实现call,apply,和bind方法

手写实现call&#xff0c;apply和bind方法 call&#xff0c;apply和bind方法均是改变this指向的硬绑定方法&#xff0c;要想手写实现此三方法&#xff0c;都要用到一个知识点&#xff0c;即对象调用函数时&#xff0c;this会指向这个对象&#xff08;谁调用this就指向谁&#…

Redis全系列学习基础篇之位图(bitmap)常用命令的解析

文章目录 描述常用命令及解析常用命令解析 应用场景统计不确定时间周期内用户登录情况思路分析实现 统计某一特定时间内活跃用户(登录一次即算活跃)的数量思路分析与实现 描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高&#xff0c;占用空间少,常被用来统计…

Java | Leetcode Java题解之第518题零钱兑换II

题目&#xff1a; 题解&#xff1a; class Solution {public int change(int amount, int[] coins) {int[] dp new int[amount 1];boolean[] valid new boolean[amount 1];dp[0] 1;valid[0] true;for (int coin : coins) {for (int i coin; i < amount; i) {valid[i…

Java的包、final关键字以及代码块

Java的包、final关键字以及代码块 一、包 包的作用 &#xff1a; ​ 包就是文件夹&#xff0c;用来管理各种不同功能的Java类 包名的书写规则&#xff1a; ​ 公司域名反写 包的作用&#xff0c;需要全部英文小写&#xff0c;见名知意 什么是全类名&#xff1a; ​ 包名…

【AI视频换脸整合包及教程】AI换脸新星:Rope——让换脸变得如此简单

在数字技术迅猛发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;的应用已经渗透到了我们生活的方方面面&#xff0c;从日常的语音助手到复杂的图像处理&#xff0c;无不体现着AI技术的魅力。特别是在娱乐和创意领域&#xff0c;AI技术更是展现出了惊人的潜力。其中&a…

Ubuntu下安装和配置MySQL5.7教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 在ubuntu下安装MySQL数据库 查看操作系统版本 ​编辑 添加 MySQL APT 源 访问下载页面并下载发布包 安装发布包 安装MySQL 查看MySQL状态 开启自启动 登…