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

发布于:2025-04-20 ⋅ 阅读:(60) ⋅ 点赞:(0)

在这里插入图片描述

1.回车与换行

回车(CR, \r):

  • 作用:将光标移动到当前行的行首(最左侧),但不换到下一行。
  • 历史来源:源自打字机的“回车”操作——打字机的滑架(Carriage)需要手动推回行首。
  • ASCII码0x0D(十六进制),或\r(转义字符表示)。

换行(LF, \n):

  • 作用:将光标移动到下一行的同一列位置(垂直向下移动一行),但不改变水平位置。
  • 历史来源:打字机的“换行”操作——滚筒旋转一行。
  • ASCII码0x0A(十六进制),或\n(转义字符表示)。
printf(“hello world!\n”);

这里的\n把回车换行的动作都做了,因为C语言会根据当前操作系统自动处理 \n,无需手动写 \r\n(Windows)或 \n(Unix)。

2.行缓冲区

下面的这段代码执行会有什么现象?

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

int main()
{
	printf("hello world!\n");
	sleep(2);
	return 0;
}

答案是,这里显示器打印出hello world!,然后再sleep两秒钟程序结束。

那么要是把\n去掉,代码执行会有什么现象?

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

int main()
{
	printf("hello world!");
	sleep(2);
	return 0;
}

我们执行后发现,刚开始程序不显示,过了两秒之后,程序快结束时,hello world!打印出来。

那为什么有\n显示器打印是立即显示呢?而没有\n程序是过了两秒再打印呢?sleep在printf之前执行??

我们要知道,C语言执行代码时,一直是从上向下执行的,所有一定是先执行printf,再执行sleep。但为什么打印的内容也是在休眠2秒之后才显示呢?这期间已执行完的内容在哪?——缓存区!!

缓冲区是给显示器提供的,所以只要有缓冲区就必然存在一个刷新策略!

显示器的刷新策略:行刷新!所以有\n,立即显示;没有\n,整个程序结束,然后显示。

如果我们就是想让这个不带\n的程序立即刷新怎么办?

C提供了对应的fflush策略,可以直接强制刷新。

在这里插入图片描述
在这里插入图片描述

stdin对应键盘

stdout,stderror对于显示器

printf打印,是把信息写在stdout(标准输出)里面,所以打印时信息没有刷出来,就用fflush刷新一下标准输出,就可以把这个字符串立即刷新出来。

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

int main()
{
	printf("hello world!");
	fflush(stdout);
	
	sleep(2);
	return 0;
}

3.demo——倒计时程序

字符输入到显示器上什么位置由谁决定?

是由光标决定。

如果我们的光标在第一个位置,写入9,这时光标会向后移动,但我们想办法让光标再移动到开头,再向里面写8,…,按照这样的原理,我们就可以很容易的实现一个倒计时程序。

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

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

在这里插入图片描述

这虽然是个倒计时,但并不是我们想要的那种。

今天我们了解了回车,那就可以修改一下我们的代码。

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

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

我们执行后发现,它竟然什么都不打印了!?

哦哦!!原来是因为把\n变成\r后,没有\n了,所以数字会被写在缓存区里,一直不被更新。

那我们刷新一下吧。

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

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

执行后发现一切正常。?

那0为什么被覆盖掉了?

原来当我们程序退出时,命令行也要打命令行字符串,所以就会把0覆盖掉。

所以我们最后再加一个打印换行符,不然0就会被覆盖掉。

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

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

此时就很完美了。

如果我们要是从10开始倒计时呢?修改后发现为什么只有个位数变化呢?!?

在这里插入图片描述
在这里插入图片描述

其实显示器只认字符,显示器是字符设备。

我们在显示器上打的10,其实不是十,而是1和0两个字符。

我们再修改一下,指定输出十进制整数,宽度至少2个字符,不足补空格。不过%2d默认是左对齐补位(空格填充在数值的左侧),我们想要右对齐,可使用%-2d。

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

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

这样就可以了。

4.进度条

我们想写一个进度条不断的推进,旁边有一个百分比不断在增长,和一个不断在旋转的光标。

process.c

#include "process.h"
#include<string.h>
#include<unistd.h>
  
#define SIZE 101
#define STYLE '#'
  
//v2:根据进度,动态刷新一次进度条
void FlushProcess(const char *tips,double total,double current)
{
    const char *lable = "|/-\\";
    int len = strlen(lable);
    static int index =0;
    char buffer[SIZE];
    memset(buffer,0,sizeof(buffer));

    double rate = current*100.0/total;
    int num=(int)rate;

    int i = 0;
    for(;i < num;i++)
      buffer[i]=STYLE;

    printf("%s...[%-100s][%.1lf%%][%c]\r",tips,buffer,rate,lable[index++]);
    fflush(stdout);
    index %= len;

    if(num >= 100)printf("\n"); 
}
  		
//v1:展示进度条基本功能
void process()
{
    int rate=0;
    char buffer[SIZE];
    memset(buffer,0,sizeof(buffer));
    const char *lable="|/-\\";
    int len = strlen(lable);

    while(rate <= 100)
    {
        printf("[%-100s][%d%%][%c]\r",buffer,rate,lable[rate%len]);
        fflush(stdout);
        buffer[rate] = STYLE;
        rate++;
        usleep(10000);
    }

    printf("\n");
}                                                                                          

process.h

#pragma once

#include <stdio.h>
 
//v1
void process();

//v2
void FlushProcess(const char* ,double total,double current); 

main.c

#include "process.h"
#include <unistd.h>
#include <time.h>
#include<stdlib.h>

//函数指针类型
typedef void (*call_t)(const char*,double,double);

double total = 1024.0;
//double speed[]=1.0;
double speed[]={1.0,0.5,0.3,0.2,0.1,0.01};

void download(int total,call_t cb)
{
  srand(time(NULL));
  double current=0.0;
  while(current <= total)
  {
      cb("下载中",total,current);//进行回调
      if(current >= total) break;
      //下载代码
      int random = rand() % 6;
      usleep(5000);
      current += speed[random];
      if(current>=total) current=total;
  }
}

void upload(int total,call_t cb)
{
    srand(time(NULL));
    double current=0.0;
    while(current <= total)
    {
        cb("上传中",total,current);//进行回调
        if(current >= total) break;
        //下载代码
        int random = rand() % 6;                                                              
        usleep(5000);
        current += speed[random];
        if(current>=total) current=total;
    }
}

int main()
{                                                                                       
    download(1024.0,FlushProcess);
    printf("download 1024.0 MB done\n");
    download(512.0,FlushProcess);
    printf("download 512.0 MB done\n");
    download(256.0,FlushProcess);
    printf("download 256.0 MB done\n");
    download(128.0,FlushProcess);
    printf("download 128.0 MB done\n");
    download(64.0,FlushProcess);
    printf("download 64.0 MB done\n");

    upload(500.0,FlushProcess);
    return 0;
}

Makefile

BIN=process
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
RM=rm -f

$(BIN):$(OBJ)
    @$(CC) $^ -o $@
    @echo "链接 $^ 成 $@"
%.o:%.c
    @$(CC) -c $< #-g
    @echo "编译 ... $< 成 $@"

.PHONY:clean
clean:
    @$(RM) $(OBJ) $(BIN)

.PHONY:test
test:
    @echo $(BIN)
    @echo $(SRC)
    @echo $(OBJ)

在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到