Linux终端输出哲学:从回车换行到进度条实战,掌握缓冲区刷新与ANSI控制,告别输出延迟焦虑

🔥@雾忱星: 个人主页
👀专栏:《C++学习之旅》《Linux学习指南》
💪学习阶段:C/C++、Linux
⏳"人理解迭代,神理解递归。"


文章目录

  • 引言
  • 一、核心知识储备:回车换行与缓冲区
    • [1.1 解疑:回车和换行别再分不清](#1.1 解疑:回车和换行别再分不清)
    • [1.2 缓冲区:回车换行的潜在作用 ------ 刷新缓冲区](#1.2 缓冲区:回车换行的潜在作用 —— 刷新缓冲区)
  • 二、进度条实战:从基础到彩色进阶
    • [2.1 自动化构建:适配多文件的 makefile 编写](#2.1 自动化构建:适配多文件的 makefile 编写)
    • [2.2 基础版本:进度条核心原理(多文件协作实现)](#2.2 基础版本:进度条核心原理(多文件协作实现))
    • [2.3 进阶版本:打造彩色进度条](#2.3 进阶版本:打造彩色进度条)
      • [2.3.1 头文件设计:函数接口声明](#2.3.1 头文件设计:函数接口声明)
      • [2.3.2 测试源文件:模拟下载场景实现](#2.3.2 测试源文件:模拟下载场景实现)
      • [2.3.3 核心源文件:彩色进度条逻辑实现](#2.3.3 核心源文件:彩色进度条逻辑实现)
  • 三、最终成果展示与操作演示
  • 总结

引言

Linux系统编程的学习,离不开理论与实操的结合 。本次进度条小实战,并非孤立的功能开发,而是对前期所学知识的综合复盘与落地运用

我们将依托缓冲区机制、回车换行原理、多文件编程、Makefile自动化构建等前置知识点,一步步搭建可运行的动态进度条,把零散的语法、底层逻辑串联成完整的开发链路,夯实Linux环境下编程的实操能力。


一、核心知识储备:回车换行与缓冲区

在进行本篇的的主要任务之前,需要补充几个关键知识点,为第二节内容打下基础。

1.1 解疑:回车和换行别再分不清

在前面 C语言、C++ 的学习,你对于**"回车换行"**这个概念肯定不陌生。比如 C语言 的\n,C++ 的std::endl,这两个换行控制符都起到了回车换行的作用。但是,你可能不知道,"回车","换行"其实是两个行为。

回车\r 光标只在当前位置的垂直方向移动,水平位置不变;

换行\n 光标只回到本行的最开头,垂直位置不变;

在 C语言 和 C++中,将"回车"、"换行"这两种行为综合到了一起。

1.2 缓冲区:回车换行的潜在作用 ------ 刷新缓冲区

📖 【"药引子"】:体会回车换行的潜在的作用------刷新缓冲区

  • 版本一:\n\r
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
 
int main()
{
     printf("hello world\n\r");
     sleep(1);     //休眠函数
     return 0;
} 

显示结果 :先输出hello world,再休眠。

  • 版本二: 没有\n\r
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
int main()
{
     printf("hello world");
     sleep(1);     //休眠函数
     return 0;
} 

显示结果 :先休眠,再输出hello world

🔦 为什么不加回车换行就输出延迟?------存在缓冲区

如果在此时前不了解缓冲区,就会误以为上面的情况是先执行的休眠在输出。实则不然,执行顺序已经定好了就不会改变。

C语言的缓冲区就好比购物车 ,购物时是先将想买的物品放到购物车,最后在一次性完成。缓冲区的原理就是如此,它就是内存中的一块缓存区域,目的是减少I/O操作,提高效率。

对于printf函数,只有在遇到缓冲区满或者\n\r换行符才会立即将内容刷新到显示器上。这也就是版本二看着像是先执行的休眠后执行的输出的原因。

【刷新缓存区方法】:

  • 默认程序结束时刷新缓存区;
  • printf函数加上回车换行符\n\r,主要是换行\n
  • 使用fflush(stdout)函数强制刷新;

【综合练手】:倒计时

c 复制代码
#include <stdio.h>
#include <unistd.h>
 
int main()
{                                                                                                                              
    int time = 10;                      //倒计时从10开始
    for(;time>=0;time--)                //循环time-1
    {
        printf("倒计时:%-2d\r", time);//注意域宽覆盖0,回车到开头不需要换行
        sleep(1);                       //休眠,防止程序直接结束
         
        fflush(stdout);                 //刷新缓存区,立即显示
    }                                                     
	return 0;                                          
 }                           

🔍为什么设置域宽为2?

不设置会导致从9开始回车只会覆盖10的"1"而"0"则会一直呆着,就会变成90,80......


二、进度条实战:从基础到彩色进阶

准备进行多文件编写,为了方便编译,重新写一遍 makefioe文件实现自动化构建。

2.1 自动化构建:适配多文件的 makefile 编写

在前面已经学习过如何编写一个完整的makefile文件,这里只是对前版本进行修改适配现在的多文件。

bash 复制代码
Bin=process_bar         #进度条文件    
Src=$(wildcard *.c)     #获取源文件    
Obj=$(Src:.c=.o)        #替换后缀    
cc=gcc                  #编译工具    
    
$(Bin):$(Obj)    
    @echo "$^ Link To $@"    
    @$(cc) -o $@ $^    
%.o:%.c    
    @echo "Compling $< To $@"    
    @$(cc) -c $<    
    
#清理项目    
.PHONY:clean    
clean:    
    @echo "File Clean... Done"                                                                                                                     
    @rm -f $(Bin) $(Obj)    
    
.PHONY:Print    
Print:    
    @echo $(Bin)    
    @echo $(Obj)    
    @echo $(Src)    
    @echo $(cc)

2.2 基础版本:进度条核心原理(多文件协作实现)

多文件协作:main.c/process.c/process.h三个文件,process.c/.h文件实现进度条项目。

c 复制代码
#include "process.h"
#include <unistd.h>
#include <string.h> 
#define SIZE 102
#define LABLE '='                //定义宏,显示进度
 
void Process()
{
    int cnt = 0;                 //进度
    char out_bar[SIZE];          //存储图形
    const char *lable = "|/-\\";
    int len = strlen(lable);     //计算提示图案的长度
    memset(out_bar, '\0', sizeof(out_bar));     //初始化数组out_bar
 
    while(cnt<=100)
    {
        printf("[%-100s][%3d%%][%c]\r", out_bar, cnt, lable[cnt%len]);
        fflush(stdout);          //刷新缓冲区         
        out_bar[cnt] = LABLE;    //设置数组,进度可视化
        cnt++;
        usleep(50000);                                                                                                                         
    }
    printf("\r\n");
}

【头文件】:
#include "process.h":自定义头文件,包含实现的函数接口的声明;
#include <unistd.h>:使用函数usleep,进行程序休眠,更好体现进度条的推进;
#include <string.h> :使用函数strlen计算图标的长度,memset预先设置数组内容;

【逻辑实现】:

存储图形数组预先全部设置为\0,无需再注意字符串结尾问题。进度为100,在循环中完成进度条的推进。循环中,每次循环向数组填充一个图形,再刷新缓冲区,回车进行下一次输出,营造整体推进的感觉。

【细节处理】:

  • 定义宏SIZE,设置为102大小。进度为100,加上\0,整体为101,设置为102为安全冗余设计;
  • 最后进度条完成推进,仍要回车换行,保证图形完整;

🔍 效果演示:

2.3 进阶版本:打造彩色进度条

2.3.1 头文件设计:函数接口声明

c 复制代码
#include <stdio.h>                                                                                                                                                                                                                   
#include <unistd.h>    
#include <string.h>    
#include <stdlib.h>    
#include <time.h>    
    
//void Process();    
    
//自定义函数别名,方便调用    
typedef void(*flush_t)(double total, double current, double speed, const char *userinfo);    
    
//下载接口    
void Download(double total, flush_t cb);    
void Process(double total, double current, double speed, const char *userinfo);  

【头文件】:
#include <time.h> :使用time函数获取系统时间,升华成随机种子;
#include <stdlib.h>:设置随机数,模拟网速的波动;

【细节处理】:

  • 自定义回调函数别名 typedef void(*flush_t),方便其他版本的进度条函数应用,移植性较强;
  • 声明下载函数接口,主要应用场景在该函数实现,并测试进度条函数;

2.3.2 测试源文件:模拟下载场景实现

c 复制代码
#include "process.h"    
      
//主要目的是下载文件    
void Download(double total, flush_t cb)    
{
	double current = 0.0;         				//当前下载量    
        
    double level[] = {0.01, 0.05, 1.0, 0.5, 1.5, 2.5, 5.5, 10.0, 3.6, 20.0, 30.0, 40.0, 50.0 , 68.9, 89.1, 40.9, 30.1};//网速梯度    
    int num = sizeof(level) / sizeof(level[0]); //计算网速总数
    
    //只要下载未完成,进度条就推进    
    while(1)    
    {    
        usleep(50000);             				//进度条刷新时间    
        double speed = level[rand()%num];       //取余保证在网速梯度中选择
        current += speed;     				    //当前下载量根据网速决定递增    
        if(current >= total)    
        {                                                                                                                                                                                                                            
           current = total;    
           cb(total, current, speed, "MB/s");  //调用进度条函数    
           break;                              //跳出,防止重复刷新    
        }    
        else    
        {    
            cb(total, current, speed, "MB/s"); //调用进度条函数    
        }    
    }    
  
}    
    
int main()    
{    
    // ProcessVersion1();    
   	srand(time(NULL));   //时间种子    
  	printf("任务下载1:\r\n");    
   	Download(gtotal, Process);
   	printf("任务下载完成!\r\n");    
    printf("任务下载2:\r\n");    
    Download(102.0, Process);    
    printf("任务下载完成!\r\n");    
    printf("任务下载3:\r\n");    
    Download(72.4, Process);    
    printf("任务下载完成!\r\n");    
    printf("任务下载4:\r\n");    
    Download(789.2, Process);    
    printf("任务下载完成!\r\n");
    return 0;
}

【应用实践】: 下载场景

  • 函数中需要定义网速梯度模拟网速波动
  • 若当前下载量小于总量,调用进度条函数,需要将参数(下载总量、当前下载量、网速、提示信息)传过去。反之直接输出最后完整的进度条,

2.3.3 核心源文件:彩色进度条逻辑实现

c 复制代码
#include "process.h"

#define SIZE 102			//进度条长度
#define LABEL '='			//进度条填充字符
void Process(double total, double current, double speed, const char *userinfo)
{
    //超出总量,直接结束下载,代表完成
    if(current > total)
    {
        return;
    }
    
    //旋转光标:只和进度条函数有没有被调用有关
    static const char *label = "|/-\\";
    int size = strlen(label);    
    static int index = 0;                     //保证lable始终在有效范围内    
    
    //1.计算下载比率    
    double rate = current * 100.0 / total;    //占比,不满1.0%不推进    
                                                                                                                                                                                                                                     
    char out_bar[SIZE];                        //进度条数组    
    memset(out_bar, '\0', sizeof(out_bar));   //初始化数组    
    //2.填充进度条字符
    int i = 0;    
    for(; i < (int)rate; i++)    
    {
        out_bar[i] = LABEL;
    }
    
    
    // ========== 新增ANSI颜色控制序列 ==========
    // 绿色进度条主体,黄色百分比,蓝色文件大小,青色网速,重置颜色
    const char *COLOR_BAR    = "\033[32m";    // 进度条填充字符 -> 绿色
    const char *COLOR_RATE   = "\033[33m";    // 进度百分比 -> 黄色
    const char *COLOR_SIZE   = "\033[34m";    // 已下载/总大小 -> 蓝色
    const char *COLOR_SPEED  = "\033[36m";    // 网速信息 -> 青色
    const char *COLOR_RESET  = "\033[0m";     // 重置终端颜色,防止污染后续输出
    const char *COLOR_CURSOR = "\033[35m";    // 旋转光标 -> 紫色

	//3.输出进度条
    //printf("[%-100s][%5.1lf][%.2lf/%.2lf][speed:%.2lf %s][%c]\r", out_bar, rate, current, total, speed, userinfo, lable[index]);
                                                                                                                                  
    //3.输出进度条(仅修改printf的颜色拼接,格式、参数完全不变)                                                                  
    printf("[%s%-100s%s][%s%5.1lf%%%s][%s%.2lf/%.2lf%s][speed:%s%.2lf %s%s][%s%c%s]\r",                                           
           COLOR_BAR,    out_bar, COLOR_RESET,  // 彩色进度条                                                                     
           COLOR_RATE,   rate, COLOR_RESET,     // 彩色百分比                                                                     
           COLOR_SIZE,   current, total, COLOR_RESET,  // 彩色文件大小                                                            
           COLOR_SPEED,  speed, userinfo, COLOR_RESET, // 彩色网速                     
           COLOR_CURSOR, label[index], COLOR_RESET);    // 彩色旋转光标                
                                                                                                                                                         
    index++;                                                           
    index %=  size;                                                    
    fflush(stdout);                                                    
                                                                       
    //4. 进度条完成,换行
    if(current >= total)
    {
    	printf("\r\n"); 
    	index = 0;			//重置旋转光标状态    	    	
    }       
}                        

【进阶改动】: 对基础版进行改动

  • 计算下载比率,显示当前下载进度,图形填充改为根据比率进行(每次都重更新填充);
  • 增加彩色显示,便于区分;

【细节处理】:

  • 格式输出%%转义输出一个%(也可以\%);
  • 完成一个任务下载,立即将旋转光标状态重置,使得下载连贯;
  • 我使用的Linux终端,是C89 标准,不支持在循环条件中定义变量,需要注意;

【彩色输出】: 大家可以通过AI工具获取彩色输出格式。


三、最终成果展示与操作演示

【操作示范】:

bash 复制代码
[tac@VM-0-6-centos lesson12]$ ll
total 16
-rw-rw-r-- 1 tac tac 1573 Feb  6 16:38 main.c
-rw-rw-r-- 1 tac tac  396 Feb  5 21:13 makefile
-rw-rw-r-- 1 tac tac 3030 Feb  6 00:06 process.c
-rw-rw-r-- 1 tac tac  383 Feb  5 22:51 process.h
[tac@VM-0-6-centos lesson12]$ make
Compling main.c To main.o
Compling process.c To process.o
main.o process.o Link To process_bar
[tac@VM-0-6-centos lesson12]$ ./process_bar 
任务下载1:
[====================================================================================================][100.0%][1024.00/1024.00][speed:30.00 MB/s][/]
任务下载完成!
任务下载2:
[====================================================================================================][100.0%][102.00/102.00][speed:89.10 MB/s][/]
任务下载完成!
任务下载3:
[====================================================================================================][100.0%][72.40/72.40][speed:68.90 MB/s][\]
任务下载完成!
任务下载4:
[====================================================================================================][100.0%][789.20/789.20][speed:40.00 MB/s][/]
任务下载完成!
[tac@VM-0-6-centos lesson12]$ make clean
File Clean... Done

在终端演示中:

  • 呈现绿色:进度条部分 "==" ;
  • 呈现黄色:下载比率部分 " ...... % " ;
  • 呈现蓝色:下载量部分 " ...... / ...... ";
  • 呈现青色:网速部分 " ...... MB / s " ;
  • 呈现紫色:旋转光标部分 " | / - \ ";

【动态演示】


总结

html 复制代码
🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:

这场轻量化实战,完美承接了前期Linux系统编程的核心知识点,通过进度条开发完成了知识闭环。从底层缓冲区操控,到多文件协作、编译脚本编写,再到进阶彩色效果实现,既巩固了旧知,也培养了工程化开发思维。后续可基于这套框架继续拓展功能,为更复杂的系统编程项目打下扎实基础。

相关推荐
赵孝正2 小时前
Python分块计算(Chunk Processing)详解:解决大规模数据内存溢出的工程实践
数据库·人工智能·python
lpfasd1232 小时前
2026年第12周GitHub趋势周报:Claude生态爆发,AI工程化加速,开发者工具链重构
人工智能·重构·github
无巧不成书02182 小时前
【开源AI视频笔记工具】BiliNote部署教程:本地+Docker双方案,小白也能轻松上手!
人工智能·开源·音视频·bilinote部署教程·cookie获取
C++ 老炮儿的技术栈2 小时前
Qt 开发机器人客户端程序
c语言·开发语言·c++·windows·qt·机器人
同元软控2 小时前
同元“AI工程七步法”实践:把桌面CAD搬到Web
前端·人工智能
2401_833197732 小时前
现代C++多线程编程实战
开发语言·c++·算法
m0_587958952 小时前
C++中的适配器模式实战
开发语言·c++·算法
Dfreedom.2 小时前
集成学习完全解析:从核心思想到常见误区
人工智能·机器学习·集成学习
杜子不疼.2 小时前
Linux 部署 RocketMQ 实操:从内网到公网,搞定远程消息服务
linux·运维·人工智能·rocketmq