在之前的Linux基础开发工具当中我们已经了解了vim、gcc、makefile等基本的开发工具,那么有了这些开发工具我们就可以来实现我们Linux旅程当中的第一个程序------进度条。相信通过该项目的实现能让你对vim等开发工具更加的熟悉。一起加油吧!!!

1.补充知识回车与换行
在实现进度条的项目之前我们先要来了解关于回车和换行的基础补充知识,在了解之后才能让接下来的项目的编写更加的顺畅。
首先我们要了解的是回车和换行有什么区别,此时你可能就会疑惑这两个实现的效果不是一样的吗?我们在点击回车的时候不就是实现了换行了吗?
其实这两个实现的效果是不同的,只不过在现代的计算机当中基本将这两个概念不进行区分了,但在上个实际使用的老式打字机上换行自是将对应的纸向下移动,而要将实现回车还需要将指针移动回起始位置。

那通过以上的示例就可以理解在现代的计算机当中回车其实是 换行+回车 的,通过以下的老式键盘就可以看出这一特点

其实在之前在C语言当中使用的**\n** 就是本质上就是实现了换行+回车的功能,\r实现的就是回车的功能。
2.练手小程序------倒计时
以上我们了解了回车的本质,那么接下来就来通过一个倒计时的小程序来作为实现进度条项目之前的联手项目,不过在实现之前先要通过以下的代码得出一个规则。
首先来看以下的代码:
cpp
#include<stdio.h>
#include <unistd.h>
int main()
{
printf("hello world\n");
sleep(5);
return 0;
}
在以上的代码当中运行会出现什么样的现象呢?
通过运行形成可执行程序以上的代码输出的效果如下所示:
此时就可以看出首先会在显示器当中打印出hello world,之后在休眠5妙再让程序结束
再看以下的代码会有什么现象:
cpp
#include<stdio.h>
#include <unistd.h>
int main()
{
printf("hello world");
sleep(5);
return 0;
}
以上的代码相比原来的代码只是少了一个换行符\n,此时该程序运行的结果会和之前的一样吗?
通过将该程序形成可执行程序运行之后就可以看出和之前不同,该程序一开始没有将hello world输出到显示器上,而是到程序结束的时候才打印到显示器上,那么为什么会有这样的现象呢?

要解答以上的问题就需要了解C语言当中进行输出的流程是怎么样的,其实在将对应的信息输出到显示器之前是会将数据先存储到缓冲区,之后再统一的将缓冲区内的数据刷新到显示器上,因此以上的两份代码再执行完printf("hello world\n")与printf("hello world")之后都是将执行完的结果存储到缓冲区当中,那么为什么第一份代码能直接将结果显示到显示器上呢?
这其实和其实和显示器行刷新的策略有关,当使用\n之后会将缓冲区当中的数据刷新到显示器上,而第二份代码当中未使用\n就使得程序缓冲区当中的数据需要在程序结束的时候才刷新到显示器上,这也是第二份代码形成的可执行程序在最后才会显示出来。
那么在第二份代码当中不使用\n但要使其与第一份代码执行出相同的效果,要使用什么样的方式呢?
在此就需要了解一个库函数fflush

在此fflush的作用就是将缓冲区当中的数据进行强制刷新 。我们知道在C语言的程序启动时默认会打开stdin、stdout、stderr三个标准输入输出流,那么此时就可以将缓冲区当中的数据强制刷新到标准输出流stdout上。
改进的代码如下所示:
cpp
#include<stdio.h>
#include <unistd.h>
int main()
{
printf("hello world");
fflush(stdout);
sleep(5);
return 0;
}
以上代码形成可执行程序之后输出结果如下所示:

了解了缓冲区之后接下来就来尝试编写倒计时程序的代码,在此要求是从10开是倒计时,倒计时的数字在同一行内一直进行刷新,直到最后数字未0时结束
那么接下来就很容易的能实现出以下的代码
cpp
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt= 10;
while(cnt>=0)
{
printf("%d\r",cnt);
cnt--;
fflush(stdout);
sleep(1);
}
printf("\n");
return 0;
}
以上的代码就是使用了以上我们学习的fflush在进行每一次的printf之后就进行强制的刷新,并且在每次输出一个数字之后使用\r进行回车,这样就可以使得每一秒显示器上会出现一个数字且在同一行内输出。但是以后的代码还有一个问题,我们来通过运行看看。
通过以下的输出就可以看出问题的所在是在输出时一开始打印的10是两位数,之后再进行打印的是一位数,这就会使得打印出的结果变为了90、80......
这个问题形成的原因是由于printf在输出时默认的是右对齐,此时要修改为左对齐只需要在printf的占位符%之后加上一个-2即可,这样就可以限定最小宽度为2
修改之后的代码如下所示:
cpp
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt= 10;
while(cnt>=0)
{
printf("%-2d\r",cnt);
cnt--;
fflush(stdout);
sleep(1);
}
printf("\n");
return 0;
}
输出结果如下所示:

3.进度条项目实现
通过以上的倒计时小程序我们完成了在实现进度条项目之前的练手,那么接下来就可以开始实现我们Linux学习当中的第一个项目,在此会实现两个版本的进度条项目,其中第一个版本的代码较为简单,但是不具有实用性,第二份的代码才具有实用性。
在实现代码之前首先要来明确我们的实现的项目的要求是什么,在此实现出的效果需大致如下所示:

首先是有一个进度条进行进度的推进直到进度条满为止,在进度条之后有一个显示当前进度的百分比数,之后有一个旋转的光标来表示当前进度条是在推进的。
3.1 v1版本
在实现该进度条项目时分为三个文件,在process.h内进行实现函数的声明,在process.c内实现对应的函数,在main.c使用实现的函数来实现进度条

那么接下来就先来实现第一个版本的进度条,在此创建一个函数process_v1,在该函数内实现对应的代码。在该版本当中的进度条只需要每秒使得进度条向后推进指定的进度即可,并且在进度条之后的百分数也显示对应的进度值。

但在实现process_v1内的函数代码之前我们先要将该程序对应的makefile进行构建
makefile内的内容如下所示:
cpp
BIN=process.exe
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
$(BIN):$(OBJ)
$(CC) -o $@ $^
%.o:%.c
$(CC) -c $<
.PHYNO:clean
clean:
rm -f $(OBJ) $(BIN)
接下来就来实现第一个版本的进度条
首先在实现进度条要从0到100,那么就创建一个大小为101的字符数组来存储对应的进度数据,使用'#'来表示进度元素,接下来使用memset函数将数组buffer内的元素都初始化为0。

由于要在进度条的最后使用一个动态的翻转来表明进度条是在运行的,那么在此就创建一个数组tmp,该数组内存储对应的字符,进度条每增长一位就将对应的状态字符改变,

在此字符串内使用\\是因为两个\\才会被转译为\
接下来创建一个变量cnt来表示当前进度条的进度值,使用while喜欢来实现0到100进度。在此过程中每次打印完字符串、进度条百分比、状态元素之后都要回车并且进行强制刷新。
每次打印完之后将buffer数组内对应cnt下标位置元素修改为'#',再让cnt++
注:在此每次状态元素都使用cnt%len,这样就可以使得得到的下标一直在tmp字符串的范围内

以上使用的函数usleep和sleep类似也是进行休眠,不过单位是毫秒

完整代码如下所示:
cpp
#include"process.h"
//函数实现
#define MAX 101
#define STYLE '#'
void process_v1()
{
char buffer[MAX];
memset(buffer,0,sizeof(buffer));
const char* tmp="|/-\\";
int len=strlen(tmp);
int cnt=0;
while(cnt<=100)
{
printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]);
fflush(stdout);
usleep(50000);
buffer[cnt]=STYLE;
cnt++;
}
printf("\n");
}
编写完process_v1的代码之后接下来就使用make命令生成对应的可执行程序process.exe

执行process.exe效果如下所示:

3.2 v2版本
在上述内容中,我们已经实现了进度条的第一个版本。尽管上述进度条程序确实能够运行,但问题在于我们上面实现的进度条速度是均匀的。这在现实中几乎是不可能的情况。更多时候,进度条会呈现出速度的变化。例如,在下载文件时,由于网速的影响,网速快时进度条移动得快,网速慢时进度条移动得慢。因此,接下来实现的第二个版本的进度条将更符合实际情况。
在此创建一个process_v2来表示该版本进度条的实现

该函数的参数有两个分别为总的数据值以及当前完成任务的数据值。在该函数的内部就需要实现cur占totle总量的百分比进度条,在此数据的变化就不在该函数内实现。
首先和之前的process_v1函数类似也是先创建一个大小为101的数组buffer来存储进度条的内容,再创建一个数组tmp来存储动态变化的字符。

之后和之前实现函数不同的是使用cnt变量的值来确定对应状态数组的下标,创建一个浮点数的变量count来统计当前进度条的百分比,再使用变量sum来统计进度条内#的个数

完整代码如下所示:
cpp
#define MAX 101
#define STYLE '#'
void process_v2(double totle,double cur)
{
char buffer[MAX];
memset(buffer,0,sizeof(buffer));
const char* tmp="|/-\\";
int len=strlen(tmp);
static int cnt=0;
double count=cur*100/totle;
int sum=(int)count;
int i=0;
for(;i<=sum;i++)
{
buffer[i]=STYLE;
}
printf("[%-100s][%.1lf%%][%c]\r",buffer,count,tmp[cnt]);
fflush(stdout);
cnt++;
cnt%=len;
}
实现了process_v2函数之后接下来就需要在main.c当中实现一个具体的场景,假设在此实现的是文件的下载,下载的总量是1024mb,下载的速度是0.1mb每秒,那么接下来实现的函数就如下所示:
cpp
#include"process.h"
double totle=1024.0;
double speed=1.0;
void DownLoad()
{
double cur=0;
while(cur<=totle)
{
process_v2(totle,cur);
usleep(10000);
cur+=speed;
}
}
int main()
{
//函数使用
//process_v1();
DownLoad();
return 0;
}
执行以上代码make之后形成的可执行程序,输出结果如下所示:

以上虽然实现的进度条能执行下去,但是以上还是匀速的,那么此时要使得呈现出来的进度条快慢是变化就需要使用随机数。
并且还将下载总的数据量的大小由mai函数内调用DownLoad时传参。
在此之前先在process.h内引用对应的头文件

cpp
#include"process.h"
void DownLoad(double totle)
{
srand(time(NULL));
double speed=rand()%51;
double cur=0;
while(cur<=totle)
{
process_v2(totle,cur);
usleep(10000);
cur+=speed;
}
printf("\n");
}
int main()
{
//函数使用
//process_v1();
DownLoad(10240);
return 0;
}
以上代码此时就会存在一个问题就是最终cur的值可能会无法和totle正好匹配,那么此时最后进度条就无法显示到100%,因此接下来就要对这种情况进行处理。

在此只需要按照以上的方式处理即可实现最终的进度条到100%。
以上实现的进度条以及基本满足我们的预期了,接下来就是对以上代码进行优化,在以上代码中DownLoad和process_v2的耦合度 较高,在此我们可以修改为回调函数的方式来实现函数的调用。
并且在process_v2函数当中再添加一个变量来表示当中进度进行的操作是什么。
这样就可以实现上传等其他的操作
完整代码如下所示:
process.h
cpp
#pragma once
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
//函数声明
void process_v1();
void process_v2(const char* s,double totle,double cur);
process.c
cpp
#include"process.h"
//函数实现
#define MAX 101
#define STYLE '#'
void process_v2(const char* s,double totle,double cur)
{
char buffer[MAX];
memset(buffer,0,sizeof(buffer));
const char* tmp="|/-\\";
int len=strlen(tmp);
static int cnt=0;
double count=cur*100/totle;
int sum=(int)count;
int i=0;
for(;i<=sum;i++)
{
buffer[i]=STYLE;
}
printf("[%s][%-100s][%.1lf%%][%c]\r",s,buffer,count,tmp[cnt]);
fflush(stdout);
cnt++;
cnt%=len;
}
void process_v1()
{
char buffer[MAX];
memset(buffer,0,sizeof(buffer));
const char* tmp="|/-\\";
int len=strlen(tmp);
int cnt=0;
while(cnt<=100)
{
printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]);
fflush(stdout);
usleep(50000);
buffer[cnt]=STYLE;
cnt++;
}
printf("\n");
}
main.c
cpp
#include"process.h"
typedef void(*call_t)(const char*,double,double);
void DownLoad(double totle,call_t cb)
{
srand(time(NULL));
double speed=rand()%51;
double cur=0;
while(cur<=totle)
{
cb("下载中:",totle,cur);
if(cur>=totle)break;
usleep(1000);
cur+=speed;
if(cur>totle)cur=totle;
}
printf("\n");
}
void UPLoad(double totle,call_t cb)
{
srand(time(NULL));
double speed=rand()%51;
double cur=0;
while(cur<=totle)
{
cb("上传中:",totle,cur);
if(cur>=totle)break;
usleep(1000);
cur+=speed;
if(cur>totle)cur=totle;
}
printf("\n");
}
int main()
{
//函数使用
//process_v1()
printf("下载总量:%d\n",10000);
DownLoad(10000,process_v2);
printf("下载总量:%d\n",222222);
DownLoad(222222,process_v2);
printf("下载总量:%d\n",99);
DownLoad(99,process_v2);
printf("下载总量:%d\n",5555);
DownLoad(5555,process_v2);
printf("下载总量:%d\n",1);
DownLoad(1,process_v2);
printf("上传总量:%d\n",20000);
UPLoad(20000,process_v2);
return 0;
}
以上代码运行结果如下所示:
以上就是本篇的全部内容了,希望通过本篇的学习能让你对这些基础的开发工具有更深的认识