Linux-Linux第一个系统程序-进度条

目录

1.大概成果显示

2.回车换行问题

3.行缓冲区

4.小试牛刀-倒计时程序

5.实现进度条


1.大概成果显示

我们最终会实现如下结果:

### \]\[3%\]\[/

#### \]\[4%\]\[-

##### \]\[5%\]\[\\

###### \]\[6%\]\[-

####### \]\[7%\]\[/

也就是说第一个\[\]里面要有100个字符,第二个\[\]要包含一个数字,数字是前面含#的字符个数,以及一个%,最后一个\[\]里面有一个字符,会实现从/到-到\到-到/的循环!

2.回车换行问题

回车、换行不是一个概念

比如一个作文:

我们写完一行要换行,那我们的步骤是:先把笔回到第一行开头的位置,然后换到下一行。而把笔回到第一行开头的位置称为:回车,换到下一行称为:换行,而我们在C语言的\n是回车换行(所以平时我们说的\n不是换行的意思)。在系统里,你写的位置相当于光标位置

3.行缓冲区

在C语言中有一个头文件unistd.h,里面有一个我们常用的函数:sleep函数,它接收一个参数,可以让程序休眠m秒再执行下面的代码。

如,我在code.c里写以下代码(打印没有换行):

此时我们可以预测一下结果,按照正常情况,应该是先打印出hello world再休眠两秒吧,此时我们make一下有:

此时的code.exe就可执行了,回车后瞬间是这个:

过2秒后才显示:

这和我们的预期不一样啊!代码应该永远都是从上往下运行的啊,不应该是先打印再休眠吗

那这就代表:此时是先执行了sleep再进行的打印,那么,sleep时,我们的hello world在哪里?

实际上这个hello world在C语言的缓冲区,这个缓冲区后面会讲,现在我们就把缓冲区理解成:它是一块内存空间 就行。

printf本质是拷贝函数,一般缓冲到缓冲区里,就有刷新策略,核心策略:按行刷新。①\n或\r\n,\n之前的所有内容,全部直接刷新到显示器上;②程序退出的时候,曾经的历史数据,会被刷新

因此,我们可以把代码改为:

此时再make,并./code.exe开始有:

过2s后有:

如果我想强制看到呢?

手动刷新缓冲区即可,C语言有个函数:

cpp 复制代码
int fflush(FILE* stream);

printf是输出是标准输出,它的结果会显示到显示器上,而标准输出是:

cpp 复制代码
extern FILE* stdout;

所以我们可以使用fflush(stdout)来使回车后的一瞬间就输出到显示器上,因此把代码改成:

此时我们make,然后./code.exe一瞬间后就有:

再过2秒有:

我们也就可以用这种方式来强制显示到显示器上。

在C语言,我们学到:

cpp 复制代码
printf("%d",123456);

我们知道%d表示为int类型的输出,但是实际上,打印的123456不是整数,而是字符串

printf叫格式化输出,整数转为字符串再输出 ,即123456这个数变成'1''2''3''4''5''6'了,而且显示器设备被称为字符设备!键盘输的也为字符串,scanf就也是字符函数

4.小试牛刀-倒计时程序

我们在命令行输入9,开始为:

输入后为:

而此时我们回车,会变成:

也就是说:回车会将光标回到一行最开始的地方,而由于一个位置只能有一个字符,所以如果我们回车,那么后面的数字就会覆盖前面一个数字

所以倒计时程序就可以这样写:

其中/r的意思是:只回车(只把光标回退到一行最开始的位置)但不换行。

此时我们make然后./code.exe一下一直会有:

最后:

这是因为信息在缓冲区,我们刚刚没有把显示结果强制从缓冲区里刷新出来,所以啥都看不到,我们可以改code.c代码为:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

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

然后我们再次执行有:

此时就能正常的倒计时了。

但这样不好,因为运行完后界面为:

也就相当于我们只有运行时才能看到结果,这是因为它会自动生成命令行,把最后的结果覆盖了

要解决,我们需要在循环结束时换行,这样就不会被覆盖了:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

int main()
{
    int cnt=9;
    while(cnt)
    {
        printf("%d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
    return 0;
}

此时我们再运行最后结果为:

这样看似没问题,但是如果我们把cnt置为10试一下:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

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

此时代码的运行结果为从10到90到80/70/60......:

为什么?

因为光标覆盖只会覆盖第一个字符,第二个字符0没有覆盖,所以我们需要改为如下代码:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>

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

此时,我们就可以正常显示了(-2d代表输出两位,并向左对齐):

此时就能完成真正的倒计时!

5.实现进度条

我们需要先创建三个文件:process.h,process.c,main.c:

在process.h写:

cpp 复制代码
//process.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void Process();

在process.c里写:

cpp 复制代码
#include"process.h"
#define LENGTH 101

void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        printf("[%-100s]\r",bar);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        sleep(1);
    }
}

在main.c里写:

cpp 复制代码
#include "process.h"

int main()
{
    Process();
    return 0;
}

此时运行结果为:

直到最后:

但是我们发现,这样刷新也太慢了,1秒刷新一次,而sleep单位为秒,因此需要用到另外一个函数:usleep,单位:10^-6s(微秒),我们设置为20000微妙刷新一次,把process.c代码改为:

cpp 复制代码
//process.c
#include"process.h"
#define LENGTH 101

void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        printf("[%-100s]\r",bar);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
}

但是此时的运行结果为:

此时我们输入一个t有:

也就代表我们刚刚需要换行才行,把process.c的代码改为:

cpp 复制代码
#include"process.h"
#define LENGTH 101

void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        printf("[%-100s]\r",bar);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

运行结果为:

但是此时我们还少了两个部分,我们先加第一个部分:10%这个:

把代码改为:

cpp 复制代码
//process.c
#include"process.h"
#define LENGTH 101

void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%]\r",bar,cnt);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

中途结果为:

最后我们还需要一个东西能显示正在加载的:| -> / -> - -> \ -> |这样的方式,我们可以把代码改为:

cpp 复制代码
#include"process.h"
#define LENGTH 101

void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

此时我们再运行代码有:

但是这个代码并不能运用到实际上,因为这个进度跳不符合实际情况,总是有可能有网络波动和其他影响的。

所以可以改为如下形式:

对于process.h:

cpp 复制代码
#pragma once
#include<stdio.h>
#include<string.h>
#include<unistd.h>
// 声明为外部变量(不加赋值,加 extern)
extern double target_file_size;
extern double speed;
void FlushProcess(double target,double current);
void DownLoad(double size,double split);
void Process();

对于process.c:

cpp 复制代码
#include"process.h"
double target_file_size = 1024.0;
double speed = 1.0;
#define LENGTH 101
void FlushProcess(double target,double current)
{
    //更新出进度
    double rate=(current/target)*100.0;//算的是小数,应该以百分比显示
    //整数个进度递增,刷新一个#
    int cnt=(int)rate;
    //构建缓冲区
    char bar[LENGTH];
    memset(bar,'\0',sizeof(bar));
    for(int i=0;i<cnt;i++)
        bar[i]='#';
    //刷新
    printf("[%-100s][%lf%%]\r",bar,rate);
    fflush(stdout);//显示刷新
}
void DownLoad(double size,double split)
{
    double current_total=0.0;//已下载进度
    while(current_total < size)
    {
        //现在没有学下载,用usleep进行模拟
        current_total += split;//刷新一次进度条
        usleep(10000);//代表一次下载
        FlushProcess(size,current_total);//不能直接用Process函数,要用具体进度来刷新
    }
}
void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

对于main.c:

cpp 复制代码
#include "process.h"

int main()
{
    // Process();
    DownLoad(target_file_size,speed);
    return 0;
}

此时的运行结果为:

但最后有:

所以我们要多打印一个空行,把process.c代码改为:

cpp 复制代码
#include"process.h"
double target_file_size = 1024.0;
double speed = 1.0;
#define LENGTH 101
void FlushProcess(double target,double current)
{
    //更新出进度
    double rate=(current/target)*100.0;//算的是小数,应该以百分比显示
    //整数个进度递增,刷新一个#
    int cnt=(int)rate;
    //构建缓冲区
    char bar[LENGTH];
    memset(bar,'\0',sizeof(bar));
    for(int i=0;i<cnt;i++)
        bar[i]='#';
    //刷新
    printf("[%-100s][%lf%%]\r",bar,rate);
    fflush(stdout);//显示刷新
}
void DownLoad(double size,double split)
{
    double current_total=0.0;//已下载进度
    while(current_total < size)
    {
        //现在没有学下载,用usleep进行模拟
        current_total += split;//刷新一次进度条
        usleep(10000);//代表一次下载
        FlushProcess(size,current_total);//不能直接用Process函数,要用具体进度来刷新
    }
    printf("\n");
}
void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

最后结果:

但是我们不想要六位数,因此可以把输出为.1lf,即:printf("%.1lf")此时就保留一位小数,因此改process.c为:

cpp 复制代码
#include"process.h"
double target_file_size = 1024.0;
double speed = 1.0;
#define LENGTH 101
void FlushProcess(double target,double current)
{
    //更新出进度
    double rate=(current/target)*100.0;//算的是小数,应该以百分比显示
    //整数个进度递增,刷新一个#
    int cnt=(int)rate;
    //构建缓冲区
    char bar[LENGTH];
    memset(bar,'\0',sizeof(bar));
    for(int i=0;i<cnt;i++)
        bar[i]='#';
    //刷新
    printf("[%-100s][%.1lf%%]\r",bar,rate);
    fflush(stdout);//显示刷新
}
void DownLoad(double size,double split)
{
    double current_total=0.0;//已下载进度
    while(current_total < size)
    {
        //现在没有学下载,用usleep进行模拟
        current_total += split;//刷新一次进度条
        usleep(10000);//代表一次下载
        FlushProcess(size,current_total);//不能直接用Process函数,要用具体进度来刷新
    }
    printf("\n");
}
void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

运行结果为:

但是有人可能会发现最后的进度条到99.9就不动了,这是因为它current_total<size,应当<=才行,但是这样的话如果进度超100也还会继续刷新,所以要在FlushProcess后加(因为此时最后一个数进度条满了,后面的再刷新没意义):

cpp 复制代码
if(current_total>=size)
    break;

改为:

cpp 复制代码
#include"process.h"
double target_file_size = 1024.0;
double speed = 1.0;
#define LENGTH 101
void FlushProcess(double target,double current)
{
    //更新出进度
    double rate=(current/target)*100.0;//算的是小数,应该以百分比显示
    //整数个进度递增,刷新一个#
    int cnt=(int)rate;
    //构建缓冲区
    char bar[LENGTH];
    memset(bar,'\0',sizeof(bar));
    for(int i=0;i<cnt;i++)
        bar[i]='#';
    //刷新
    printf("[%-100s][%.1lf%%]\r",bar,rate);
    fflush(stdout);//显示刷新
}
void DownLoad(double size,double split)
{
    double current_total=0.0;//已下载进度
    while(current_total <= size)
    {
        //现在没有学下载,用usleep进行模拟
        current_total += split;//刷新一次进度条
        usleep(10000);//代表一次下载
        FlushProcess(size,current_total);//不能直接用Process函数,要用具体进度来刷新
        if(current_total>=size)
            break;
    }
    printf("\n");
}
void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

此时有:

但是我们还缺了一个旋转的东西,应该在FlushProcess加:

cpp 复制代码
const static char* sym="|/-\\";
static int index=0;
int len=strlen(sym);

打印输出再加一个打印字符的即可:

cpp 复制代码
#include"process.h"
double target_file_size = 1024.0;
double speed = 1.0;
#define LENGTH 101
void FlushProcess(double target,double current)
{
    //更新出进度
    double rate=(current/target)*100.0;//算的是小数,应该以百分比显示
    //整数个进度递增,刷新一个#
    const static char* sym="|/-\\";
    static int index=0;
    int len=strlen(sym);
    int cnt=(int)rate;
    //构建缓冲区
    char bar[LENGTH];
    memset(bar,'\0',sizeof(bar));
    for(int i=0;i<cnt;i++)
        bar[i]='#';
    //刷新
    printf("[%-100s][%.1lf%%][%c]\r",bar,rate,sym[index++]);
    index %= len;//只要函数在调用,光标就在旋转
    fflush(stdout);//显示刷新
}
void DownLoad(double size,double split)
{
    double current_total=0.0;//已下载进度
    while(current_total <= size)
    {
        //现在没有学下载,用usleep进行模拟
        current_total += split;//刷新一次进度条
        usleep(10000);//代表一次下载
        FlushProcess(size,current_total);//不能直接用Process函数,要用具体进度来刷新
        if(current_total>=size)
            break;
    }
    printf("\n");
}
void Process()
{
    char bar[101];//存储进度条,需要多一位存放'\0'
    const char* symb="|/-\\";//\\转义为\。
    int s_len=strlen(symb);
    memset(bar,'\0',sizeof(bar));
    int cnt=0;
    while(cnt<=100)
    {
        //%%转义成%
        printf("[%-100s][%d%%][%c]\r",bar,cnt,symb[cnt%s_len]);//往左对齐,且一直显示那么长的长度
        fflush(stdout);
        bar[cnt++]='#';
        usleep(20000);
    }
    printf("\n");
}

最终结果:

如果编译不通过,可以:

bash 复制代码
vim Makefile

改CFLAGS为:

bash 复制代码
CFLAGS=-c -std=c99

或改gcc为g++。

相关推荐
wuminyu1 小时前
JDK21解决虚拟线程IO阻塞原理剖析
java·linux·c语言·jvm·c++
梦想的旅途21 小时前
企业微信自动化实战:客户资料自动修改与备注同步
运维·自动化·企业微信
梦想的旅途21 小时前
企业微信自动化实战:客户通讯录备份与关系自动迁移
运维·自动化·企业微信
茫忙然1 小时前
Linux提权辅助查询工具——BinLens的安装和使用
linux·提权·binlens
FlightYe2 小时前
linux系统编程(八):阻塞与非阻塞IO对比
android·linux·运维·服务器·github·vim
淮北4942 小时前
ubuntu22 默认输入法调整频率
运维·服务器·git·ubuntu·elasticsearch
土星云SaturnCloud2 小时前
大坝结构安全智能诊断:土星云边缘计算全周期管控实践
服务器·人工智能·安全·ai·边缘计算
Percep_gan2 小时前
切割服务器日志
运维·服务器
农村小镇哥3 小时前
C#读取CSV文件的方法
服务器·数据库·c#