手把手写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.c
gcc -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()
{
//version1
int 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.c
gcc -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()
{
//version1
int 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.c
gcc -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()
{
// version1
int 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()
{
// version2
int 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()
{
// version3
int 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();
// 版本3
ProcessBar3();
// 倒计时
// 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()
{
// version1
int 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()
{
// version2
int 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()
{
// version3
int 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; // 每次下载10MB
usleep(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()
{
// 版本5
printf("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()
{
// version1
int 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()
{
// version2
int 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()
{
// version3
int 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; // 每次下载10MB
usleep(50000);
}
printf("\n");*/
// 版本5
printf("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. 调用时机:当一个函数执行到特定的时刻(比如完成某项工作、接收到事件等),它会调用传入的回调函数。
使用场景
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
,后者在适当的时候调用它。这样就实现了函数之间的灵活交互。
运行结果: