目录
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++。