Linux操作系统从入门到实战(十二)Linux操作系统第一个程序(进度条)

发布于:2025-07-23 ⋅ 阅读:(14) ⋅ 点赞:(0)


前言

在前文中,我们学习了Linux开发工具中make/Makefile的自动化构建知识,包括基础语法、推导过程与扩展规则

  • 今天,我们将基于这些知识,动手实现Linux环境下的第一个实战程序——动态进度条。这个程序虽简单,却能帮我们理解C语言输入输出、缓冲区机制、Makefile构建流程等核心知识点,为后续复杂程序开发打下基础。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482


一、Makefile自动化构建

要实现进度条程序,首先需要通过Makefile管理编译流程。一个清晰的Makefile能让我们更高效地编译、调试和清理代码。

完整Makefile代码

BIN=download   # 最终生成的可执行文件名
CC=gcc         # 编译器指定为gcc
SRC=$(wildcard *.c)  # 匹配当前目录下所有.c源文件
OBJ=$(SRC:.c=.o)     # 将所有.c文件替换为.o目标文件

# 链接阶段:将所有目标文件合并为可执行文件
$(BIN): $(OBJ)
        @$(CC) -o $@ $^ 
        @echo "链接完成:$^ → $@"

# 编译阶段:将单个.c文件编译为.o目标文件
%.o: %.c
        @$(CC) -c $< 
        @echo "编译中:$< → $@"

# 清理编译产物(伪目标)
.PHONY: clean
clean:
        rm -f $(OBJ) $(BIN)
        @echo "清理完毕!"

Makefile解析

  1. 变量定义

    • BIN=download:指定最终生成的可执行文件名为download
    • CC=gcc:指定编译器为gcc
    • SRC=$(wildcard *.c):通过wildcard通配符匹配当前目录下所有.c源文件(如process.cmain.c);
    • OBJ=$(SRC:.c=.o):将SRC中所有.c后缀替换为.o,自动生成目标文件列表(如process.omain.o)。
  2. 核心规则

    • 链接规则 $(BIN): $(OBJ):表示download依赖所有.o文件,通过gcc -o $@ $^将目标文件链接为可执行文件($@代表目标download$^代表所有依赖的.o文件);
    • 编译规则 %.o: %.c:通配符规则,将任意.c文件编译为同名.o文件($<代表依赖的.c文件);
    • 清理规则 clean:通过rm删除编译生成的.o和可执行文件,PHONY声明clean为伪目标(避免与同名文件冲突)。
  3. 静默编译
    规则前的@符号用于隐藏命令本身的输出,只显示自定义提示(如“编译中:process.c → process.o”),使输出更简洁。

使用方法

  1. 编译程序:在终端执行make,自动完成编译和链接,生成download可执行文件;
  2. 运行程序:执行./download,即可看到动态进度条效果;
  3. 清理文件:执行make clean,删除所有编译生成的文件(.o和可执行文件)。

二、进度条核心实现

进度条的核心是动态刷新显示进度,需要解决三个问题:如何计算进度、如何实时更新显示、如何控制刷新频率。我们将通过process.h(声明)、process.c(实现)和main.c(场景模拟)三个文件实现。

1. 头文件 process.h

#ifndef PROCESS_H  // 头文件防护:防止重复包含
#define PROCESS_H

// 进度条长度:100个字符+1个终止符'\0'
#define LENGTH 101
// 进度条填充字符
#define LABLE '*'

// 依赖的头文件
#include <string.h>   // 字符串操作(strlen/memset)
#include <unistd.h>   // 延时函数(usleep)
#include <stdio.h>    // 输入输出(printf/fflush)

// 函数声明
// 根据目标值和当前值刷新进度条
void FlushProcess(double target, double current);
// 简易进度条(带动态符号)
void Process();

#endif  // PROCESS_H

解析

  • 宏定义LENGTH=101:进度条最多显示100个*,加上字符串终止符\0,总长度为101;
  • 头文件防护#ifndef PROCESS_H:避免同一头文件被多次包含导致的函数/变量重复定义;
  • 函数声明:明确对外提供的接口,方便其他文件调用。

2. 实现文件 process.c

#include "process.h"

// 根据目标值和当前值刷新进度条
void FlushProcess(double target, double current)
{
    // 计算进度百分比(当前值/目标值*100)
    double rate = (current / target) * 100.0;
    // 进度条填充的字符数(取整数部分)
    int cnt = (int)rate;
    
    // 初始化进度条缓冲区
    char bar[LENGTH];
    memset(bar, 0, sizeof(bar));  // 清空缓冲区(避免脏数据)
    
    // 填充进度条:前cnt个字符为LABLE('*')
    for(int i = 0; i < cnt; i++)
        bar[i] = LABLE;
    
    // 打印进度条:
    // [%-100s]:左对齐100个字符(确保进度条长度固定)
    // [%.lf%%]:显示百分比(%%转义为%)
    // \r:回到行首(覆盖当前行,实现动态刷新)
    printf("[%-100s][%.lf%%]\r", bar, rate);
    // 强制刷新缓冲区(避免输出被缓冲不显示)
    fflush(stdout);
}

// 简易进度条(带动态旋转符号)
void Process()
{
    // 动态旋转符号(循环显示:| / - \)
    const char *symb = "|/-\\"; 
    int s_len = strlen(symb);  // 符号长度为4
    
    char bar[LENGTH];
    memset(bar, '\0', sizeof(bar));  // 清空缓冲区
    int cnt = 0;  // 当前进度(0~100)
    
    // 从0%到100%循环刷新
    while(cnt <= 100)
    {
        // 打印格式:进度条 + 百分比 + 旋转符号
        printf("[%-100s][%d%%][%c]\r", bar, cnt, symb[cnt % s_len]);
        fflush(stdout);  // 强制刷新
        
        // 更新进度:填充一个字符,进度+1
        bar[cnt++] = LABLE;
        // 延时20ms(控制刷新速度,避免太快看不清)
        usleep(20000);  // 单位:微秒(20000us = 20ms)
    }
    // 进度完成后换行(避免后续输出覆盖)
    printf("\n");
}

核心解析

  • FlushProcess函数:根据“当前值/目标值”计算进度,用*填充进度条,通过printf打印;
  • \r(回车符):使光标回到行首,下次打印时覆盖当前行,实现“动态刷新”(区别于\n换行);
  • fflush(stdout):标准输出默认是“行缓冲”(遇到\n才输出),这里用fflush强制刷新,确保实时显示;
  • usleep(20000):延时20毫秒,控制进度条刷新速度(若不延时,进度会瞬间完成);
  • 旋转符号symb:通过cnt % 4循环显示| / - \,模拟“加载中”的动态效果。

3. 主函数 main.c(场景模拟)

#include "process.h"

// 模拟下载场景参数
#define TARGET_SIZE 1024.0  // 目标文件大小(单位:M)
#define SPEED 1.0           // 下载速度(单位:M/100ms)

// 模拟下载过程
void Download(double target, double step)
{
    double current = 0.0;  // 当前已下载大小
    while(current < target)
    {
        // 刷新进度条(当前进度=current/目标)
        FlushProcess(target, current);
        // 模拟下载耗时(100ms)
        usleep(100000);  // 100000us = 100ms
        // 累加已下载大小
        current += step;
    }
    // 下载完成后换行(避免后续输出覆盖)
    printf("\n");
}

int main()
{
    // 调用下载模拟函数
    Download(TARGET_SIZE, SPEED);
    return 0;
}

场景说明

  • 模拟“文件下载”场景:目标文件大小1024M,每次下载1M,耗时100ms;
  • 循环调用FlushProcess:每次下载后更新进度条,直到下载完成;
  • 最后printf("\n"):进度完成后换行,避免终端后续输出与进度条重叠。

三、效果

效果展示

在这里插入图片描述


以上就是这篇博客的全部内容,下一篇我们将继续探索Linux的更多精彩内容。

我的个人主页
欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述


网站公告

今日签到

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