【Linux】缓冲区 进度条小程序

news2024/11/23 12:22:16

目录

一、\r && \n

二、缓冲区的概念

 三、小程序编写

1、倒数小程序

2、进度条小程序


一、\r && \n

C语言中有很多字符,但是宏观上可以分成两类:可显字符、控制字符。

可显字符包括我们见到的 1、2、3....,a、b、c....等等。控制字符则包括 '\n'、'\t'、'\r'、'\b'等等。

换行操作,就是使用控制字符来完成的,换行的过程包括两个部分:1、换到下一行,2、光标移动到下一行的开头。分别对应到控制字符'\n':换行 '\r':回车

至于我们在写C语言代码时只需要输入字符 '\n' 就直接自动换行到下一行的开头,是因为在语言范畴中默认把 '\n' 解释成了回车加换行。

因为回车与换行是两个动作,所以我们可以观察到键盘上 enter 键上的图标是一个“竖折”。

二、缓冲区的概念

我们先写一个程序:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\n");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

我们在打印hello world之后添加了一个休眠函数,执行程序,现象如下:

 可以看到屏幕上打印出字符后,延时了一段时间,程序才结束,下一个命令行出现。


现在把 printf 函数中的 '\n' 符号删除掉,再次编译:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

 

 可以看到去除掉 '\n' 符号后,不仅命令行不会换行显示了,而且 hello world 是经过了一段延时之后与命令行一起显示的。

 首先我们知道,程序在执行时,一定时按照从上往下的顺序执行的,也就是说,一定是先打印的 hello world ,再执行休眠函数。只不过 hello world 在休眠期间没有被刷新出来而已。此时, hello world 被保存在了 缓冲区

 那么为什么我们加上 '\n' 符号,数据就可以直接显示出来呢?原因很简单,不管我们带不带上 '\n' 符号,数据都会以 行缓冲 的方式被保存在缓冲区里。缓冲区有自己的刷新策略,如果遇到了换行符 '\n' ,就把换行符之前的所有数据都刷新出来。否则就会默认在程序退出时自动刷新缓冲区里的数据。


接下来我们再来修改一下代码:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\r");
  7   sleep(1);                                                                                                                                          
  8   return 0;                                                                              
  9 }

 在字符串 hello world 后面加上回车控制符 '\r' ,编译后运行,现象如下:

延时一段时间后,hello world 竟然直接不显示了,直接出现了下一行命令行。

这是因为 hello world 被打印出来之后,由于回车符 '\r' 的作用,光标直接移动到了该行的最开头,并在此光标位置打印下一个命令行,直接把 hello world 覆盖住了。

为了更好的观察这一过程,我们在 printf函数 与 sleep 函数之间添加一个主动刷新函数 fflush ,即直接刷新出缓冲区里的数据:

                                                                                                                            
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world\r");
  7   fflush(stdout);                                                                                                                                    
  8   sleep(1);                                                                                                               
  9   return 0;                                                                                                               
 10 }  

 编译运行,现象如下:

hello world 先被刷新出来,休眠一段时间,程序结束,下一个命令行出现并覆盖 hello world。 


 现在我们再把 '\r' 去掉,就会有一个更深的理解:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   printf("hello world");
  7   fflush(stdout);                                                                                                                                    
  8   sleep(1);                                                                                                               
  9   return 0;                                                                                                               
 10 }  

 编译运行: 

 三、小程序编写

在了解了以上知识之后,我们就可以进行一些小程序的编写。

1、倒数小程序

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   int i = 9;
  7   for(i = 9; i > 0; i--) //从9开始倒数
  8   {
  9     printf("%d\r", i); // 使用\r,覆盖上一个数 
 10     fflush(stdout);  //直接输出缓冲区里的数据
 11     sleep(1);   //休眠延时
 12   }                                                                                                                                                  
 13   printf("\n"); //保留最后一个数字,不被下一个命令行覆盖
 14   return 0;
 15 }

执行结果如下:

这里会有一个问题,就是如果我们把 i 的初值设为 10 ,执行出来的结果与上面的结果有所差别,会像这样:

这时因为凡是向显示器打印的所有内容,都是字符。就像我们写 printf("%d", 10); 看起来好像是打印了一个十进制数字 10,实际上这个数字根本不是一个 4 个字节的整数,而是两个字符 1 和 0 。因此,我们每次打印都只覆盖了第一个字符,而第二个字符没有发生变化。

补充内容:

printf工作原理是先获取指定数据,再把这个数据全部转换成字符串,然后把字符串遍历一遍并使用 putc 来把这些字符串显示出来。

 那么我们怎么解决呢?也很简单,我们只需要把 "%d" 改成 "%2d" 就可以了,保留两位字符。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   int i = 10;
  7   for(i = 10; i > 0; i--) //从9开始倒数
  8   {
  9     printf("%2d\r", i); // 使用\r,覆盖上一个数 
 10     fflush(stdout);  //直接输出缓冲区里的数据
 11     sleep(1);   //休眠延时
 12   }                                                                                                                                                  
 13   printf("\n"); //保留最后一个数字,不被下一个命令行覆盖
 14   return 0;
 15 }

明白了这个原理之后,任何数的倒数我们都能够实现了。 

2、进度条小程序

先讲一下思想:

  1. 我们先预留出 100 个字符的空间,用 [ ] 括起,然后在这个空间里不断的填充 '#' 号,每隔一段时间,填充的 '#' 号就多一个。在 '#' 不断增多的过程中,右边的 ' ] ' 的位置保持不变。
  2. 在进度条的后面,使用百分比数字来显示当前进度条的进度。
  3. 在进度条最后打印一个不断旋转的字符,来表示当前程序正在运行。

程序采用多文件实现:

  • proc.h
  • proc.c
  • main.c 

进度条程序编写:

我们创建一个字符数组,通过打印不同的字符,来模拟旋转字符的实现:

const char* lable = "|/-\\"

因为 '\' 是特殊字符,所以我们使用 '\\' 转义一下。

因为要打印 100 个字符 '#' 加上一个字符串结束字符 '\0' ,所以我们给字符串数组 bar 初始化空间为 101 ,初始化字符全为 '\0'

#define SIZE 101 

char bar[SIZE];
memset(bar, '\0', sizeof(bar));//先把字符串里的所有字符都设置为'\0'

打印进度条主体时,我们使用循环语句,每次打印都覆盖上一次打印的内容:

int i = 0;
while(i <= 100)//因为百分之0 到 百分之100 是101个数,所以循环101次
{
  printf("[%-100s][%d%%][%c]\r", bar, i, lable[i%4]);//先预留100个字符的空间,向左对齐,每一次 
                                                      //打印都覆盖前一次打印
  fflush(stdout); //直接刷新出缓冲区里的内容
  bar[i++] = STYLE; //给字符串赋值
  usleep(50000);  //每次休眠0.5ms
}

printf 函数里打印字符串使用 %-100s ,是为了提前预留出 100 个字符的位置,并向左对齐,以便进度条是从左向右打印的。

因为 '%' 是特殊字符,所以为了打印出 '%' ,需要写成 '%%'

最终程序编译完成后,执行结果如下:

完整代码:

proc.h:

  1 #pragma once                                                                                                                                         
  2 #include <stdio.h>     
  3 extern void process(); 

proc.c:

  1 #include "proc.h"
  2 #include "string.h"
  3 #include "unistd.h"
  4 
  5 #define SIZE 101                                                                                                                                     
  6 #define STYLE '#'
  7 
  8 void process()
  9 {
 10   const char* lable = "|/-\\"; //顺时针旋转字符
 11   char bar[SIZE];
 12   memset(bar, '\0', sizeof(bar));//先把字符串里的所有字符都设置为'\0'
 13   int i = 0;
 14   while(i <= 100)//因为百分之0 到 百分之100 是101个数,所以循环101次
 15   {
 16     printf("[%-100s][%d%%][%c]\r", bar, i, lable[i%4]);//先预留100个字符的空间,向左对齐, 
                                                           //每一次打印都覆盖前一次打印
 17     fflush(stdout); //直接刷新出缓冲区里的内容
 18     bar[i++] = STYLE; //给字符串赋值
 19     usleep(50000);  //每次休眠0.5ms
 20   }
 21   printf("\n"); //最后换行,防止下一个命令行覆盖进度条
 22 }

 main.c:

  1 #include "proc.h"                                                                                                                                    
  2 int main()
  3 {         
  4   process();
  5 }  

Makefile 编写:

  1 process:main.c proc.c
  2     gcc main.c proc.c -o process 
  3 .PHONY:clean
  4 clean:
  5     rm -f process  

有关缓冲区的内容就讲到这里,这个进度条小程序大家在自己编写的时候可以增加点新的东西,比如进度条颜色、背景颜色等等。希望同学们多多支持,如果有不对的地方还请大佬指正,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

历史最全事件抽取任务分类、经典论文、模型及数据集整理分享

事件抽取技术是从非结构化信息中抽取出用户感兴趣的事件&#xff0c;并以结构化呈现给用户。事件抽取任务可分解为4个子任务: 触发词识别、事件类型分类、论元识别和角色分类任务。其中&#xff0c;触发词识别和事件类型分类可合并成事件识别任务。事件识别判断句子中的每个单词…

Linux面试题

Linux 概述 什么是Linux Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想…

Java使用Zxing二维码生成

目录 1、二维码简介 二维码纠错级别 2、ZXing简介 3、示例 3.1 搭建一个maven项目&#xff0c;引入Zxing依赖包 3.2 创建QrCodeUtil.java 类 1、二维码简介 二维条形码是用某种特定的几何图形按一定规律在平面&#xff08;二维方向上&#xff09;分布的黑白相间的图形记录…

C++ 001:C++ 基础语法

1. 开始之前 1.1 学习路线 这次我是下定决心要学 C 了&#xff0c;而且是系统地&#xff0c;不半途而废地学习 C 了~ 有这个新专栏为证~ 由于某次偶然的机会&#xff0c;我看见了一张 C 竞赛的学习路线表&#xff08;这里由于表格内容太多就不贴出来&#xff09;&#xff0c…

Xinlinx zynq7020国产替代 FMQL20S400 全国产化 ARM 核心板+扩展板

TES720D 是一款基于FMQL20S400 的全国产化核心 模 块 。 该核心 模 块 将FMQL20S400 &#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个 50*70mm 的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用在控制领域&#xff0…

【Java】的面向对象笔记(中)

继承性基础 哲学三问 什么是继承性 银行卡有很多种&#xff0c;有借记卡、信用卡、亲情卡、工资卡等等&#xff0c;他们各有不同&#xff0c;但都具有相同的银行卡特征&#xff0c;即余额、卡号等共有的属性&#xff0c;如果每定义一个类都需要写一次&#xff0c;那就太麻烦…

word、excel文档内容更新技术方案

需求背景 惯例先说下背景。 生产、研发业务上往往使用大量word和excel文档来作为资料载体&#xff0c;如操作规程、控制手册、卡片……&#xff0c;这些文档会反复使用到一些设备、工艺等参数数据。参数属性主要是名称、编码、正常范围、报警上下限、单位等&#xff0c;这些参…

SQL---DDL

目录 一、数据库的相关概念 二、MySQL数据库 1. 关系型数据库&#xff08;RDBMS&#xff09; 2. 数据数据库 3. MySQL客户端连接的两种方式 方式一&#xff1a;使用MySQL提供的客户端命令行工具 方式二&#xff1a;使用系统自带的命令行工具执行指令 三、SQL SQL的…

Callable接口_JUC的常见类_多线程环境使用ArrayList

目录 1.Callable接口 相关面试题 2.ReentrantLock 相关面试题 3.信号量Semaphore 4.CountDownLatch 5.多线程环境使用ArrayList 热加载 1.Callable接口 Callable是一个接口,把线程封装了一个"返回值",方便程序员借助多线程的方式计算结果. 类似于Runnable,…

五个了解自己天赋优势的分析工具(三)DISC性格测评

DISC性格测评 DISC系统源于1928年&#xff0c;马斯顿在他的著作《正常人的情绪》(The Emotion of Normal People)中公布了他所发现及发展的性格理论。 该书首次尝试将心理学从纯粹的临床应用向外延伸应用到一般人身上。人有四种基本的性向因子&#xff0c;即Dominance -支配&…

Duet 安装教程

Duet 安装教程1. Duet 概述2. Duet 安装教程2.1 PC 端下载安装Duet2.2 iPad 下载安装 Duet3. 将iPad作为Windows电脑副屏的几种方法结束语1. Duet 概述 Duet 是一款能将iPad或iPhone 变成 Mac 或者 PC 的显示屏的软件&#xff1b; 通过线材连接两台不同的设备&#xff0c;Duet…

抖音聊天”上线,字节最后的社交梦?

转眼间时间来到2023年&#xff0c;距离中国接入国际互联网&#xff08;即中国互联网起点&#xff09;已过40年。回顾中国的互联网江湖&#xff0c;先有BAT三足鼎立&#xff0c;后有TMD后浪居上。所谓BAT&#xff0c;即互联网时代领头羊百度、阿里巴巴和腾讯&#xff0c;而TMD则…

【Java入门】常量和变量

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java入门 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又一…

HTTP/HTTPS协议介绍

数据来源 HTTP 01 什么是HTTP 超文本传输协议(HyperTextTransferProtocol缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议。 HTP( Hyper Text Transfer Protocol超京本传输协议) 是一个基于请求与响应 无状态的&#xff0c;应用层…

mysql快速生成100W条测试数据(7)虚拟网站、IP地址并存入mysql数据库

这是之前的文章里面包含一些以前的一些操作流程可以进行参考学习 更加详细操作步骤在第一篇文章里面 mysql快速生成100W条测试数据&#xff08;1&#xff09;&#xff1a;游戏人物数据 mysql快速生成100W条测试数据&#xff08;2&#xff09;公司员工信息 mysql快速生成100W条测…

《Unity Shader 入门精要》第2章 渲染流水线

第2章 渲染流水线 2.1 什么是渲染流水线 渲染流水线的工作在于由一个三维场景出发&#xff0c;生成一张二维图像。换句话说&#xff0c;计算机需要从一系列的顶点数据、纹理等信息出发&#xff0c;把这些信息最终转换成一张肉眼可见的图像&#xff0c;而这个过程通常由CPU与G…

静态链接过程分析

前期准备这边使用《程序员的自我修养》中的例子//a.cpp extern int shared;void swap(int* a, int *b);int main(){int a 100;swap(&a, &shared); }//b.cpp int shared 1;void swap(int* a, int* b){*a ^ *b ^ *a ^ *b; }通过gcc -c 命令编译出相对应的.o文件&#x…

五,Spring Bean生命周期

1 Spring Bean的生命周期&#xff08;概念重点&#xff09; 对象的生命周期&#xff1a;对象从生到死的过程。 Spring工厂中Bean的生命周期并不像想象的那么简单&#xff0c;Spring对工厂中的Bean的生命周期进行了细致的划分&#xff0c;并允许开发者通过编码或配置的方式定制…

Ubuntu18 sqlyog配置mysql5.7远程连接

mysql 配置远程连接 1. mysql安装和配置 sudo apt-get install mysql-server-5.7 systemctl status mysql service mysql status修改mysql的配置文件&#xff1a; sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf以下为mysqld.cnf文件主要内容&#xff0c;这里的skip-grant-ta…

基于51单片机的pm2.5空气质量监测仪仿真设计

51单片机pm2.5监测仪仿真设计( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus 7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0032 51单片机pm2.5监测仪仿真设计主要功能&#xff1a;讲解演示视频仿真程序设计…