【Linux】自定义项目-进度条

更多精彩内容.....

🎉❤️播主の主页✨😘

Stark、-CSDN博客

准备工作:"\r"与"\n"字符

①:基本含义

在C语言和Linux环境中,\r是回车符,\n是换行符,用于控制文本格式和输出。在创建进度条小程序前我们应该理解这两个字符的含义。

\r(回车符):表示回到行首。在文本中,它会将光标移回当前行的开始,但不会换到下一行。

\n(换行符):表示换到下一行,他会将光标移动到下一行的开始位置。在Linux和Unix系统中,使用\n来表示行结束。

②:深入研究

下面我们将一步一步来使用这两个符号从而引导读者去理解其在进度条程序中的用法:

从上面我们可以看到,输出第一行的123456后,将\r输出我们的光标回到了行首,所以下一行的printf将9999输出时,覆盖掉了原来的1234,对此,我们就可以实现在一行之中不断更新新的内容的问题了,这不也正是进度条所必备的嘛。我们使用循环来做一遍:

按理来说我这里将会显示出最后的结果,但是并没有,为什么?其实不是没有显示,而是被前面的主机名给覆盖掉了,也就是所谓的【stark@ubuntu: ... $ 】 ,那我们只需要输出完毕之后,在程序的结束处加一个换行就可以了吧:

结果是显示出来了,但是有点快,(这里注释有个问题,usleep的参数的单位是微秒不是毫秒,所以我们这里其实是0.005秒), 调整时间 ,但是输出结果是一直处于无显示,直到最后才显示,这是为什么?

此时我猜测,并不只是覆盖那么简单,还有\r回退后会导致行走路径上显示的字符消失。那么我们先调换顺序,将打印回车符放到睡眠下面:再来观测结果,但结果依旧不对,并不是。

猜测的原因并不成立。那么我就猜测,延迟输出是因为每次输出并不是直接输出到屏幕上的,而是有一个中间过程在卡着这个操作,此时我们似乎想到了之前学习C语言时持续输入东西直至\n,从而遇到的问题,我们当时好像涉及到一个叫做缓冲区的词。那这里我们就大胆猜测,是不是先输出到缓存区,等到缓存区中的内容到达一个特定条件时才会输出到屏幕中?为了验证猜想,我们试着询问AI,得到了答案:

输出没有立即显示在屏幕上的问题通常是因为标准输出(stdout)是缓冲的。缓冲意味着输出数据首先被存储在内存中的一个缓冲区中,而不是立即发送到屏幕或其他输出设备。当缓冲区满了或者遇到特定的刷新条件时,缓冲区的内容才会被实际输出。

根据这段结果,我们可以尝试刷新缓冲区来让它马上输出:

经过这样的编码修改后,我们得到了想要的结果:一个一个的输出@。此时有人开始想了,那我不用\r,每次输出@,然后睡眠,下次继续输出一个@,这样既不需要内部循环,也能达到同样的效果。但是你要知道,进度条并不单单只是修改@的个数,而是有一个固定的进度条样式,然后不断地修改输出的内容,进度条的思想 与 在终端中设置一个自动计时的时钟一样,是修改输出内容,而非单纯的增加输出内容达到新的结果。

那么现在我们对\r和\n的使用应该明确了:前者与fflush(stdout)用于刷新输出结果,后者用于防止主机名覆盖输出结果。下面就开始实现进度条:

设计工作:进度条的设计

进度条是一个什么样子的呢?首先有一个待填充进度槽,有填充内容,有进度百分比显示,有加载动画。

Ⅰ、进度槽

进度槽我们可以使用 来设计,中间给出待填充的空间。下面使用多行文本模拟出基本样式:

cpp 复制代码
[          ]
[#         ]
[##        ]
[###       ]
[####      ]
[#####     ]
[######    ]
[#######   ]
[########  ]
[######### ]
[##########]

如果我们合并到一行来输出这些内容,那么我们就可以实现了。

Ⅱ、填充符

有了槽这个容器,那么接下来我们就可以使用东西来填充这个容器了。我们可以使用任何你喜欢的符号来填充。那么我们该如何保证在填充时还能保持着进度槽这个容器的样式不改变:

对上述输出进行一下分析:

第一行:1个 ,0个#,10个空格,1个

第二行:1个 ,1个#,9个空格,1个

......规律非常的简单,我们来设计一套公式来输出上述内容:

cpp 复制代码
for(int i=1;i<=10;i++){
    printf("[");
    for(int j=1;j<=10;j++){
        if(j<=i)printf("@");
        else printf(" ");
    }
    printf("]\r");
    fflush(stdout);
    usleep(500000);//睡眠0.5秒
}           
printf("\n");                                                                                                                                                       

取代上述我们的@那段代码中的for循环就行了。验证一下,与预期结果一致 。

Ⅲ、百分比

百分比就是当前的填充对槽总容器的占有率,也就是当前的i与总数10的比的百分比形式:

cpp 复制代码
const int len=10;
for(int i=1;i<=len;i++){
    printf("[");
    for(int j=1;j<=len;j++){
        if(j<=i)printf("@");
        else printf(" ");
    }
    printf("]");
    printf("(%3d%%)\r", (int)(i*100/len) );
    fflush(stdout);
    usleep(50000);//睡眠0.05秒
}       
printf("\n");    

%3d代表右对齐三个字符,因为我们的预期百分比最多100三位数。

这里需要注意的是:%%代表的是一个%,不然%加上后面的特定内容代表占位符,会出现错误,所以两个%%就相当于\\,起到转义的作用。

Ⅳ、加载动画

什么是加载动画呢?这里的加载动画其实就是为了模拟我们加载时一直在转圈圈的东西。那么我们知道Linux中大多东西都是用文字或者说字符来实现的,我们怎么整一个转圈圈的东西呢?答案是:转笔,一根笔,转一圈为一个圆,循环这个操作就是不断加载。对应到字符就是:【| / - \】

不妨想一下,将这四个字符连起来,输出,是不是就是一个转笔。相信看过动漫的都知道,动漫是一帧一帧的图片构成的,连续播放就成为了动画。那么我们这四个字符就构成了一个圆所必需的四帧静态画面。那么我们就可以定义一个字符数组,分别存储这四个字符,需要注意的是\为转义字符,需要使用\\来代表一个 \ 字符。

然后循环使用这四个字符就是使用模运算的周期性来进行循环播放,对应代码为:

cpp 复制代码
const int len=100;
const char fillchar='@';
const char gif[4]={'|','/','-','\\'};

for(int i=1;i<=len;i++){
    printf("[");
    for(int j=1;j<=len;j++){
        if(j<=i)printf("%c",fillchar);
        else printf(" ");
    }
    printf("](%3d%%)(%c)\r", (int)(i*100/len) , gif[i%4]);
    fflush(stdout);
    usleep(50000);//睡眠0.05秒
}   
printf("\n");   

Ⅴ、代码优化

优化:我们使用一个char数组来存储待填充字符,初始化为全空格字符,以空间换取内层for循环的时间。

cpp 复制代码
const int len=100;
const char fillchar='@';
const char gif[4]={'|','/','-','\\'};

char buffer[len];
memset(buffer,' ',sizeof(buffer));//初始化为空格

for(int i=1;i<=len;i++){
    printf("[%s](%3d%%)(%c)\r",buffer,(int)(i*100/len) , gif[i%4]);
    fflush(stdout);
    usleep(50000);//睡眠0.05秒
    buffer[i-1]=fillchar;
}   
printf("\n");   

这样写,即提高了代码的可读性,又节省了时间。具体优化方法就是设置一个字符数组,长度为len,然后使用memset函数初始化该字符数组。打印时就可以使用占位符来整体输出结果,然后填充输出内容。经过刷新缓冲区及时输出结果,然后睡眠来让用户有时间看到输出的结果,最后在下次执行循环之前将第i-1个位置的字符设置为填充符fillchar;

运行结果:(看底行)


感谢大家!

相关推荐
用户9718356334665 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪6 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理