1.背景知识的补充

\r or \n

回车和换行是两个动作,只是在C语言中将\n解释成了回车换行 其实本质含义是换行回车\r:让光标回到最开始。

请添加图片描述
这两段代码的执行效果是不同的,第一段代码是先刷出hello world等两秒后程序执行完毕。
而第二段代码确是两秒后才刷出hello world 众所周知C语言程序是顺序进行的 也就是说这两段代码都一定是先执行printf的 那第二个hello world呢?

“hello world”被缓存起来,存储于内存之中,当程序结束后,缓存器内部数据会自动刷新。
而缓存区的刷新,是按照行为单位的,\n or \r\n 就会自动以行为单位刷新

sleep&usleep

sleep 和 usleep 都是 延时 / 休眠命令,核心作用是让程序 / 脚本暂停指定时间 只不过前者默认单位为秒 后者为毫秒。头文件为unistd.h
( sleep man手册)

fflush

fflush 是 C 语言标准库(<stdio.h>)中的 缓冲区刷新函数,核心作用是 强制将标准 I/O 流(如 stdout、文件流)的缓冲区数据立即写入实际设备(终端、文件等),避免数据因缓冲区机制延迟输出 / 写入
请添加图片描述
Linux下一切皆文件,你的显示设备也可以理解是文件一种 属于IO流中的输出流,缓冲区是有归属的,很显然 这片缓冲区里的数据就归属于显示器(stdout

练手 倒计时程序

当你往显示器输入数字 123 他不是以一百二十三的方式进行显示的 而是以 一二三字符的形式显示的,显示器输出的全部都是字符。

#include<stdio.h>
#include<unistd.h>

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

最终呈现出倒计时效果。

2. 进度条程序

2.1 Makefile的实现

关于make详细知识 请转跳

BIN=process
Cc=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
Rm=rm -f

$(BIN):$(OBJ)
        $(Cc) -o $@ $^
%.o:%.c
        $(Cc) -c $<

.PHONY:clean
clean:
        $(Rm) $(BIN) $(OBJ)     

2.2 进度条的打印

这里的C语言代码很简单 小编不仔细讲了 把一些重要的地方加了注释。

#include "processbar.h"
#include<string.h>
#include<unistd.h>
#define SIZE 101
#define STYLE '='

void Process()
{
   char processbuff[SIZE];
   memset(processbuff,'\0',sizeof(processbuff));//初始化
   int cnt=0;
   while(cnt<=100)
   {   
         /* 
         * 打印进度条核心逻辑:
         * 1. \r:光标回到当前行首,实现覆盖式更新(不刷屏)
         * 2. [%-100s]:格式化输出,%-100s 表示左对齐占 100 个字符宽度的字符串
         *    - 左对齐确保进度条从左到右填充
         *    - 100 个字符宽度对应 100% 进度(每个字符代表 1%)
         * 3. processbuff:缓存的进度条内容(填充 STYLE 字符,未填充部分为 '\0',printf 会忽略)
         */
     printf("[%-100s]\r",processbuff);
     fflush(stdout);//强制刷新标准输出缓冲区:printf 无 \n 时默认缓存,fflush 确保实时显示进度
     processbuff[cnt++]=STYLE;//延时 50 毫秒(50000 微秒):模拟任务执行耗时,控制进度条刷新速度(数值越小刷新越快)
     usleep(50000);
   }   
   printf("\n");// 进度完成后换行:避免后续终端输出与进度条同行覆盖
}

效果:
在这里插入图片描述

2.3进度条的数字进度和旋转光标

进度条光有=填充也不行啊 他得有进度啊 而且现实中的进度条 是不是还有个小圈圈在那里转圈圈
补充的代码块:

const char* lable ="|/-\\";// 动态旋转光标字符集:循环显示 '|' '/' '-' '\',模拟加载动画(\ 需转义为 \\)
   int len = strlen(lable);// 计算旋转光标字符集长度(用于循环取模,实现光标循环切换)

        //[%d%%]:显示当前进度百分比,%% 转义为单个 % 符号(如 cnt=50 时显示 50%)
        // [%c]:显示动态旋转光标,通过 cnt%len 循环取 lable 中的字符(实现旋转效果)
     printf("[%-100s] [%d%%][%c]\r",processbuff,cnt,lable[cnt%len]);

完整代码和注释:

#include "processbar.h"
#include<string.h>
#include<unistd.h>
#define SIZE 101
#define STYLE '='

void Process()
{
   const char* lable ="|/-\\";// 动态旋转光标字符集:循环显示 '|' '/' '-' '\',模拟加载动画(\ 需转义为 \\)
   int len = strlen(lable);// 计算旋转光标字符集长度(用于循环取模,实现光标循环切换)
   char processbuff[SIZE];
   memset(processbuff,'\0',sizeof(processbuff));//初始化
   int cnt=0;
   while(cnt<=100)
   {   
         /*  
         * 打印进度条核心逻辑:
         * 1. \r:光标回到当前行首,实现覆盖式更新(不刷屏)
         * 2. [%-100s]:格式化输出,%-100s 表示左对齐占 100 个字符宽度的字符串
         *    - 左对齐确保进度条从左到右填充
         *    - 100 个字符宽度对应 100% 进度(每个字符代表 1%)
         * 3. processbuff:缓存的进度条内容(填充 STYLE 字符,未填充部分为 '\0',printf 会忽略)
         * 4. [%d%%]:显示当前进度百分比,%% 转义为单个 % 符号(如 cnt=50 时显示 50%)
         * 5. [%c]:显示动态旋转光标,通过 cnt%len 循环取 lable 中的字符(实现旋转效果)
         */
         */
     printf("[%-100s] [%d%%][%c]\r",processbuff,cnt,lable[cnt%len]);
     fflush(stdout);//强制刷新标准输出缓冲区:printf 无 \n 时默认缓存,fflush 确保实时显示进度
     processbuff[cnt++]=STYLE;//延时 50 毫秒(50000 微秒):模拟任务执行耗时,控制进度条刷新速度(数值越小刷新越快)
     usleep(50000);
   }   
   printf("\n");// 进度完成后换行:避免后续终端输出与进度条同行覆盖
}

效果:
请添加图片描述

2.4 进度条的场景模拟

在现实中,进度条肯定不是按照一样的速度在加载 他是根据我们的下载速度进行加载的 这里我们就要模拟现实中的加载情况
老样子 代码不难所以 依然以注释的方式讲代码呈现出来

文件:main.c
#include "processbar.h"
#include <unistd.h>
#include <string.h>

// 宏定义:进度条相关配置(与 FlashProcess 函数中 100 字符宽度匹配)
#define SIZE 101    // 进度条缓存数组大小:100个填充字符 + 1个字符串结束符 '\0'
#define STYLE '='   // 进度条填充字符(可改为 '#'、'█' 等,自定义视觉样式)

// 全局变量:模拟下载场景的核心参数(方便后续修改,无需改动函数逻辑)
double total = 1024.0;  // 文件总大小(单位可自定义,如 KB/MB,仅用于进度计算)
double speed = 1.0;     // 下载速度(单位与 total 一致,此处为 1.0 单位/次循环)

// 下载模拟函数:模拟文件下载过程,循环更新已下载大小并触发进度条刷新
void DownLoad()
{
    double curr = 0.0;  // 当前已下载大小(初始为 0,逐步累加至 total)
    // 循环下载:直到已下载大小 >= 总大小,结束下载
    while (curr <= total)
    {   
        // 刷新进度条:传入总大小和当前已下载大小,实时更新进度显示
        FlashProcess(total, curr);
        // 模拟下载行为:已下载大小累加下载速度(每次循环推进一点进度)
        curr += speed;
        // 延时 30 毫秒(30000 微秒):控制下载进度更新频率,避免刷新过快
        usleep(30000);
    }   
}
// 主函数:程序入口,仅调用下载模拟函数
int main()
{
    DownLoad();  // 启动下载模拟(含进度条显示)
    return 0;
}

***********************************************************************************
文件:processbar.c
// 进度条刷新函数:根据总大小和当前进度,动态生成并打印进度条
// 参数:total - 任务总大小(如文件总大小);curr - 当前已完成大小(如已下载大小)
void FlashProcess(double total, double curr)
{
    // 边界处理:防止当前进度超出总大小(避免进度百分比超过 100%)
    if (curr > total)  
        curr = total;    
    // 计算进度百分比:(已完成大小 / 总大小) * 100,保留小数精度(如 50.2%)
    double rate = curr / total * 100;
    // 进度条填充计数:将百分比转为整数(如 50.8% → 50,对应 50 个填充字符)
    int cnt = (int)rate;                                   
    // 进度条缓存数组:存储已填充的进度字符,未填充部分用 '\0' 占位
    char processbuff[SIZE];      
    // 初始化缓存数组:将所有元素设为 '\0'(字符串结束符),避免内存垃圾值干扰输出
    memset(processbuff, '\0', sizeof(processbuff));
    // 填充进度条:根据计数 cnt,在缓存数组中写入 cnt 个 STYLE 字符(如 '=')
    for (int i = 0; i < cnt; i++)
        processbuff[i] = STYLE;
    // 动态旋转光标:循环显示 '|' '/' '-' '\',模拟加载动画(提升视觉体验)
    static const char *lable = "|/-\\";  // static 确保只初始化一次,优化性能
    static int index = 0;                // 光标索引(static 保持状态,循环递增)
    /* 格式化打印进度条:实现覆盖式实时更新 */
    printf(
        "[%-100s][%.1lf%%][%c]\r",  // 格式说明:
        processbuff,                // 1. 进度条主体:左对齐(-)占 100 字符,填充 STYLE
        rate,                       // 2. 进度百分比:保留 1 位小数(如 50.2%),%% 转义为 %
        lable[index++]              // 3. 动态光标:取当前索引对应的光标字符,索引后移
    );
    index %= strlen(lable);  // 光标索引循环:取模字符集长度(4),实现 '|/-\' 循环切换
    // 强制刷新标准输出缓冲区:
    // printf 无 \n 时默认行缓冲,fflush 确保进度条即时更新,避免缓存导致的显示延迟
    fflush(stdout);
    // 进度完成处理:当已完成大小 >= 总大小时,换行(避免后续输出与进度条同行覆盖)
    if (curr >= total)                                                           
    {                                                                         
        printf("\n");                                                           
    }                                                                         
}     

效果:
在这里插入图片描述

2.5 网络浮动模拟

正常的网络存在波动,这里我们就用随机数种子进行网络模拟

//start为网速基础值,range 为网速浮动
double SpeedFloat(double start,double range)
{
  int int_range=(int)range;
  return start + rand()%int_range + (range - int_range);
}
void DownLoad()
{
   srand(time(NULL));//随机数种子
   double curr=0.0;
   while(curr<=total)
   {   
     FlashProcess(total,curr);//更新进度,按照下载进度,进行更新进度条
     curr+=SpeedFloat(speed,4.3);//模拟下载行为 修改部分
     usleep(30000);
   }   
}

随机网速的实现原理:
目标:生成 [start, start+range] 区间内的随机数(如 start=1.0、range=4.3 时,网速 1.0~5.3);
拆分处理:rand() 只能生成整数随机数,因此将 range 拆分为 “整数部分 + 小数部分”,既保证整数浮动,又保留小数精度(如 4.3 拆为 4 + 0.3);
示例:当 rand()%4 生成 2 时,网速 = 1.0 + 2 + 0.3 = 3.3;生成 4 时(rand()%4 最大为 3),网速 = 1.0 + 3 + 0.3 = 4.3。

但是这么做会出现一种情况
在这里插入图片描述
他有的时候加载到99.几就突然停止加载了。

bug出现在这里

   while(curr<=total)

   curr+=SpeedFloat(speed,4.3);

有的时候生成的随机数加上原本的curr,导致curr大于了total 从而不进入循环,导致进度条无法到达100%。

修改:

void DownLoad()
{
   srand(time(NULL));//随机数种子
   double curr=0.0;
   while(1)
   {   
     if(curr >= total)
      {
          curr=total;
          FlashProcess(total,curr);
          break;
      }
     FlashProcess(total,curr);//更新进度,按照下载进度,进行更新进度条
     curr+=SpeedFloat(speed,4.3);//模拟下载行为
     usleep(30000);
   }   
}

将while循环条件始终为真,当curr>=total时候主动将curr赋值为total 然后主动进行刷新到100%,然后break跳出循环即可。

下载文件的大小模拟

你网速有快有慢 文件大小是不是也是有大有小

void DownLoad(int total)//加个函数参数

test
DownLoad(20.0);
DownLoad(200.0);
DownLoad(2000.0);

2.6 代码的解耦(函数指针)

函数指针的核心价值是「解耦代码、动态扩展」,适合需要灵活切换函数实现、隐藏底层细节、统一接口的场景 —— 这些场景下,函数指针能大幅降低代码修改成本,提升扩展性。

比如说:多实现切换(如进度条需要支持 “旋转光标”“百分比”“动画进度条” 等多种风格) 我们可以通过函数指针自动调用不同风格,调用处与具体函数解耦,依赖 “函数类型” 而非 “函数名”。

我们最终通过函数里面加函数指针的方式(回掉函数)

2.6.1函数指针的语法规则(结合示例)

核心概念:什么是函数指针?

  • 函数在内存中会占据一块连续的存储空间,其入口地址(函数第一条指令的地址)就是函数指针指向的值。
  • 函数指针的本质是“指针”,但专门指向函数(而非普通变量),其类型由函数的返回值类型参数列表决定(与函数名无关)。

比如之前的 FlashProcess 函数,它的入口地址可以用一个函数指针存储,之后通过这个指针就能调用 FlashProcess


1. 函数指针的定义格式

返回值类型 (*指针变量名)(参数类型1, 参数类型2, ...);
  • 关键括号:(*指针变量名) 必须加括号,否则会被解析为“返回值为指针的函数”(而非函数指针)。
  • 类型匹配:函数指针的返回值类型、参数类型/个数,必须与它指向的函数完全一致。

语法示例(结合 FlashProcess 函数)
FlashProcess 函数原型如下(之前的代码):

void FlashProcess(double total, double curr);  // 返回值void,2个double参数

对应的函数指针定义:

// 定义一个名为 pFlash 的函数指针,指向“返回值void、参数为(double, double)”的函数
void (*pFlash)(double, double);

2. 函数指针的赋值与调用
赋值:让函数指针指向目标函数
函数名本身就是函数的入口地址,因此赋值时直接用函数名(无需加 &,加了也可以,效果相同):

pFlash = FlashProcess;  // 推荐:函数名即地址
// 或 pFlash = &FlashProcess;  // 等价,&可省略

调用:通过函数指针调用函数
有两种调用方式,效果完全一致:

// 方式1:指针变量名 + 参数列表(最常用)
(*pFlash)(total, curr);  // 等价于 FlashProcess(total, curr)

// 方式2:简化写法(编译器自动解析)
pFlash(total, curr);     // 与方式1等价,更简洁

2.6.2 函数指针的常见误区

  1. 语法错误:忘记加括号
    错误写法:
void *pFlash(double, double);  // 错误:这是“返回值为void*的函数”,而非函数指针

正确写法:

void (*pFlash)(double, double);  // 必须加 (*pFlash) 括号
  1. 类型不匹配
    函数指针的返回值、参数类型/个数必须与目标函数完全一致,否则编译报错或运行异常:
// 错误示例:FlashProcess返回值是void,函数指针返回值是int
int (*pFlash)(double, double) = FlashProcess;  // 编译报错:类型不兼容
  1. 未初始化函数指针
    未赋值的函数指针是野指针,调用会导致程序崩溃:
void (*pFlash)(double, double);  // 未初始化(野指针)
pFlash(1024.0, 512.0);           // 危险:野指针调用,程序崩溃
  • 解决:使用前必须赋值(指向合法函数)。

2.6.3 代码(函数指针的别名&&回调函数)

// 给“返回值void、参数(double, double)的函数指针类型”起别名 pFlash
typedef void (*pFlash)(double, double);
  • 此时 FlashFunc 不再是 “函数指针变量”,而是 “函数指针类型” 的别名;
  • 后续可像使用 int、double 等基础类型一样,用 FlashFunc 定义函数指针变量。

//pf:回调函数
void DownLoad(int total,pFlash pf) 
{
   srand(time(NULL));//随机数种子
   double curr=0.0;
   while(1)
   {
     if(curr >= total)
      {
          curr=total;
          pf(total,curr);
          break;
      }   
     pf(total,curr);//更新进度,按照下载进度,进行更新进度条
     curr+=SpeedFloat(speed,4.3);//模拟下载行为
     usleep(30000);
   }   
}

int main()
{
 DownLoad(20.0,FlashProcess);
 DownLoad(200.0,FlashProcess);
 DownLoad(2000.0,FlashProcess);
 DownLoad(20000.0,FlashProcess);
 return 0;
}

这里可以有FlashProcess 1 2 3 4 .....多种不同的进度条版本 从而造成函数的解耦。

3.成品代码和git引言

main.c:

#include"processbar.h"
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
double gtotal=1024.0;//文件总大小
double speed=1.0;//下载速度

typedef void(*pFlash)(double,double);

//start为网速基础值,range 为网速浮动
double SpeedFloat(double start,double range)
{
  int int_range=(int)range;
  return start + rand()%int_range + (range - int_range);
}

//pf:回调函数
void DownLoad(int total,pFlash pf) 
{
   srand(time(NULL));//随机数种子
   double curr=0.0;
   while(1)
   {   
     if(curr >= total)
      {   
          curr=total;
          pf(total,curr);
          break;
      }   
     pf(total,curr);//更新进度,按照下载进度,进行更新进度条
     curr+=SpeedFloat(speed,4.3);//模拟下载行为
     usleep(30000);
   }   
}


int main()
{
 DownLoad(20.0,FlashProcess);
 DownLoad(200.0,FlashProcess);
 DownLoad(2000.0,FlashProcess);
 DownLoad(20000.0,FlashProcess);
 return 0;
}                                                                                                                                      

processbar.h:

#pragma once

#include<stdio.h>

//version1
void Process();
void FlashProcess(double total,double curr);

processbar.c:

#include "processbar.h"
#include<string.h>
#include<unistd.h>
#define SIZE 101
#define STYLE '='

void FlashProcess(double total,double curr)
{
   if(curr>total)
      curr=total;
   double rate=curr/total*100;//1024.0/512.0 -> 0.5*100=50.0
   int cnt=(int)rate;//50.8, 49.9 -> 50,49
   char processbuff[SIZE];
   memset(processbuff,'\0',sizeof(processbuff));
   for(int i=0;i<cnt;i++)
       processbuff[i]=STYLE;
    
   static const char *lable="|/-\\";
   static int index=0;
   //刷新
   printf("[%-100s][%.1lf%%][%c]\r",processbuff,rate,lable[index++]);
   index%=strlen(lable);
   fflush(stdout);
   if(curr>=total)
   { 
     printf("\n");
   }
}



void Process()
{
   const char* lable ="|/-\\";// 动态旋转光标字符集:循环显示 '|' '/' '-' '\',模拟加载动画(\ 需转义为 \\)
   int len = strlen(lable);// 计算旋转光标字符集长度(用于循环取模,实现光标循环切换)
   char processbuff[SIZE];
   memset(processbuff,'\0',sizeof(processbuff));//初始化
   int cnt=0;
   while(cnt<=100)
   {
     printf("[%-100s] [%d%%][%c]\r",processbuff,cnt,lable[cnt%len]);
     fflush(stdout);//强制刷新标准输出缓冲区:printf 无 \n 时默认缓存,fflush 确保实时显示进度
     processbuff[cnt++]=STYLE;//延时 50 毫秒(50000 微秒):模拟任务执行耗时,控制进度条刷新速度(数值越小刷新越快)
     usleep(50000);
   }
   printf("\n");// 进度完成后换行:避免后续终端输出与进度条同行覆盖
}

当我们把代码写完是不是要上传码云,那么该怎么做? 请移步我写的git板块

求三

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐