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

news2024/11/6 17:29:29

本专栏内容为: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. 异步编程:常用于异步操作中,比如在网络请求完成后执行特定的代码。
  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.coloradmin.cn/o/2230564.html

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

相关文章

TikTok如何用邮箱注册?用哪种邮箱比较好?

要在TikTok上创建一个账号&#xff0c;首先需要进行注册&#xff0c;这是一个简单但至关重要的步骤。在本篇文章中&#xff0c;我们将详细介绍如何用邮箱注册TikTok的整个过程&#xff0c;包括每个步骤的细节和注意事项。此外&#xff0c;我们还将讨论选择哪种邮箱比较好&#…

LabVIEW在Windows和Linux开发的差异

LabVIEW广泛应用于工程和科研领域的自动化和测量控制系统开发&#xff0c;其在Windows和Linux平台上的开发环境有所不同。这些差异主要体现在操作系统兼容性、硬件支持、软件库和驱动程序、实时系统开发以及部署选择上。以下从各个方面详细对比分析LabVIEW在Windows与Linux系统…

哪个牌子的宠物空气净化器好?口碑好的宠物空气净化器推荐!

哪个牌子的宠物空气净化器好&#xff1f;作为一名家电测评博主&#xff0c;我发现市面上宠物空气净化器的牌子越来越多了&#xff0c;很多厂家都看中了宠物行业的红利&#xff0c;想来分一杯羹&#xff0c;这就导致很多技术不成熟的产品流入了市场。今年我测试了50多台宠物空气…

ios 快捷指令扩展(Intents Extension)简单使用 swift语言

本文介绍使用Xcode15 建立快捷指令的Extension&#xff0c;并描述如何修改快捷指令的IntentHandler&#xff0c;带参数跳转主应用&#xff1b;以及展示多个选项的快捷指令弹框(配置intentdefinition文件)&#xff0c;点击选项带参数跳到主应用的方法 创建快捷指令 快捷指令是…

计算机的错误计算(一百四十一)

摘要 探讨 MATLAB中正弦、余弦的计算精度问题。当自变量为大数时&#xff0c;输出可能出错。 从 IEEE-754-2019 知&#xff0c;三角函数的定义域是实数域。 例1. 计算 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB的输出均为错误结果&#xff0c;即没有正确有效数字。…

医院绩效考核管理系统源码,医院如何构建绩效考核体系?

医院绩效考核管理系统作为现代医院管理的重要组成部分&#xff0c;其核心功能旨在提高医院运营效率、优化资源配置、确保医疗服务质量&#xff0c;以及增强医院竞争力。 业务科室绩效考核体系的构建 临床医疗与医技科室绩效考核的设置 临床医疗的绩效考核采用百分制&#xff…

「C/C++」C/C++标准库之#include<cstdlib>通用工具库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

Resnet代码实现

图2 18-layer、34-layer的残差结构 图3 50-layer、101-layer、102-layer的残差结构 import torch import torch.nn as nn#这个18或者34层网络的残差模块&#xff0c;根据ResNet的具体实现可以自动匹配 class BasicBlock(nn.Module):conv1 stride1对应的实线残差&#xff0c;因…

为什么大家都在学数字孪生呢?

随着物联网&#xff0c;大数据、人工智能等技术的发展&#xff0c;新一代信息技术与制造业正在深度融合&#xff0c;人们与物理世界的交互方式正在发生转折性的变化。数字化转型正在成为企业的重要战略&#xff0c;而数字孪生则成为全新的焦点。 当下&#xff0c;在数字技术和…

IDEA使用Maven Helper查看整个项目的jar冲突

在插件市场安装Maven Helper&#xff0c;安装好后&#xff0c;重启IDEA&#xff1b;双击打开可能存在jar冲突的pom文件&#xff1b;在右侧面板查看冲突,text是引入的依赖明细&#xff0c;点击Dependecy Analyzer选项卡即可查看冲突的jar。

「Pytorch」如何理解深度学习中的算子(operator)

在深度学习中&#xff0c;“算子”&#xff08;operator&#xff09;通常指的是在神经网络中进行的各种数学运算或函数。这些算子可以是基本的数学操作&#xff0c;如加法、乘法、卷积&#xff0c;也可以是更复杂的变换&#xff0c;如激活函数和池化操作。 主要类型的算子 线性…

Hbuilder html5+沉浸式状态栏

manifest.json源码视图添加 {"statusbar": {"immersed": true }如图&#xff1a; 2、plusready准备&#xff0c;将状态栏字体变黑&#xff0c;不然背景白色、状态栏白色看不到 //2.1 如果你用了mui&#xff0c; mui.plusReady(function(){plus.navigat…

windows/linux注册服务与阿里镜像仓库使用

这里写目录标题 启动Windows将jar注册服务Linux将jar设置开机启动 外网环境编译打包 启动 Windows将jar注册服务 将jar包导入到服务器上&#xff0c;将WinSW工具也放到服务器上。 winSw下载地址&#xff1a;https://github.com/winsw/winsw/releases 依据下图修改xml内容即可…

建筑行业知识库搭建:好处、方法与注意事项

在建筑行业&#xff0c;知识管理对于提升项目效率、降低成本、增强创新能力以及构建竞争优势具有至关重要的作用。搭建一个高效、系统的建筑行业知识库&#xff0c;不仅有助于实现知识的有效沉淀与便捷共享&#xff0c;还能促进知识在项目实践中的灵活应用&#xff0c;从而加速…

Oracle与SQL Server的语法区别

1&#xff09;日期和日期转换函数。 SQL: SELECT A.*, CASE WHEN NVL(PAA009,) OR PAA009 >Convert(Varchar(10), SYSDATE,120) THEN Y ELSE N END AS ActiveUser FROM POWPAA A WHERE PAA001admin or PAA002admin Oracle: SELECT A.*, CASE WHEN NVL(PAA009,) or PAA009&…

【算法赌场】区间合并

区间问题 区间问题的引入 数学上&#xff0c;用两个数字可以确定数轴上的一个区间&#xff0c;较小的数字叫做区间的左端点&#xff0c;也叫区间起点&#xff0c;较大的数字叫做区间的右端点&#xff0c;也叫区间终点。 在算法竞赛中&#xff0c;很多题目是以区间为单位去进行…

GPT-Sovits-2-微调模型

1. 大致步骤 上一步整理完数据集后&#xff0c;此步输入数据, 微调2个模型VITS和GPT&#xff0c;位置在 <<1-GPT-SoVITS-tts>>下的<<1B-微调训练>> 页面的两个按钮分别执行两个文件: <./GPT_SoVITS/s2_train.py> 这一步微调VITS的预训练模型…

使用列表推导式处理列表中符合条件的元素将结果组成新的列表

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 使用列表推导式处理 列表中符合条件的元素 将结果组成新的列表 执行以下代码后&#xff0c;输出是什么&#xff1f; def process_numbers(numbers): return [1 / x for x in numbers if x ! …

SSM项目部署到服务器

将SSM&#xff08;Spring Spring MVC MyBatis&#xff09;项目部署到服务器上&#xff0c;通常需要以下步骤&#xff1a; 打包项目 生成一个WAR文件&#xff0c;通常位于target目录下 配置Tomcat&#xff1a; 将生成的WAR文件复制到Tomcat的webapps目录下。 配置conf/se…

TortoiseSVN 文件夹以及文件不显示差异感叹解决步骤

直接修改注册表&#xff0c;把TortoiseSVN图标悬浮注册项提前&#xff0c;靠后就不显示&#xff0c; 如下图 打开注册表&#xff0c;重命名TortoiseSVN 相关项&#xff0c;前面加上三四个空格&#xff0c;重启电脑即可。