linux----进度条实现和gcc编译
前言:本文介绍在linux中简单实现一个进度条,以及一个.c源文件如何到可执行程序的过程。
一. c程序的生成可执行程序和运行
现在有一个.c源文件,由他通过gcc编译器得到可执行程序的过程就是,
gcc -E t.c -o t.i // 预处理
gcc -S t.i -o t.s // 编译
gcc -C t.s -o t.o // 汇编
gcc t.o -o test // .o文件链接成可执行程序了 链接
// 运行 ./test
可执行程序生成的过程:
对于c文件的处理先是:
1. 预处理: 展开头文件(调出其中你使用的函数声明printf等)等展开#define对应的宏处理 ,移除注释 (处理之后的文件后缀为 .c变为 .i文件)
2.编译: 这个过程主要是把预处理之后的c代码转换成汇编代码 得到一个 .s文件 汇编代码 (.i文件变为.s文件)
3. 汇编: 这个过程主要是把汇编代码转化为指定的二进制代码进而生成目标文件也就是 .o文件(.s文件变为了.o文件)
4. 链接过程: 你其实简单一想我现在.o文件中已经是二进制代码了机器指令啦,那么可以运行了呀?实则不然,你仔细想想第一部分头文件展开的时候这里的预处理过程,对头文件展开其实这里只是头文件的声明哈! 这也就意味着对于具体的实现还需要去链接,那有人问了为什么不直接插入到你的代码去(简单一说就是减小内存的销毁对吧头文件很多也很大),总之我们还需要有具体的一些函数的实现在库里面放着所以我们的目标文件还需要链接这个过程! 与库链接才能生成真正的可执行程序!
(链接过程:.o文件变为 可执行程序(不用后缀))
二. processBar实现(进度条)
2.1 linux中缓冲区概念
在linux中有一个东西叫做在内存上的屏幕缓冲区: 当你打印printf("xxx")打印东西的时候他会把东西暂时放到这里:
刷新的情况: 在终端上刷新: 遇到了\n,首先会刷新缓冲区所有的内容,然后光标会移动到下一行的开头不需要手动刷新。
程序结束: 程序结束相当于一个\n
手动刷新:手动加一个fflush,手动刷新缓冲区中的东西
溢满刷新: 当缓冲区的东西太多了溢满了该内存大小就刷新了。
\n和\t和\r 与缓冲区
\n的意思就是前面解释咯。
\r的意思就是光标回到当前行的开头,但是不会自动刷新缓冲区的内容,所以我们后面的代码让你感觉没有输出内容就是这个原因
\t放到这里只是顺手学习罢了,它其实相当于键盘上的tab键平常就输出4个空格。
分析下面代码:
#include<unistd.h>19 #include<stdio.h>20 21 int main(){22 23 24 printf("hello 这里是加了\\n \n");25 sleep(3);26 printf(" \r这里加了\\r的\r ");27 sleep(1);28 29 30 31 return 0;32 }
~
首先直接输出hello 这里是加了\n 因为直接遇到了\n直接刷新,然后进程停顿3s,之后内容是空格 然后又遇到\r 光标来到当前行的开头, 内容直接被覆盖为: 这里加了\r 但是后面又遇到了\r 内容 又被覆盖为空格" " 但是这个时候内容没有被刷新,会光标会在开头,然后等待1s之后在输出空格
最后输出 :hello 这里是加了\n
空格" "
等待1s以后输出:
空格 后面的是系统的命令行提示符不用管。
左右对齐:
#include <stdio.h>int main() {// 假设字段宽度统一为 15 个字符,方便对比printf("左对齐示例:\n");printf("|%-15s|\n", "apple"); // %-15s:左对齐,占15个字符宽度printf("|%-15s|\n", "banana");printf("|%-15s|\n\n", "cherry");printf("右对齐示例:\n");printf("|%15s|\n", "apple"); // %15s:右对齐,占15个字符宽度printf("|%15s|\n", "banana");printf("|%15s|\n", "cherry");return 0;
}
2.2 进度条的设计
这里的进程停顿我们用的是usleep(int mics) 停顿时间是毫秒具体的使用man手册对应的截图我放在上面了。
首先我们得感受一下:进度条长什么样:
渐入的感受;
[-------------- ] [%22][|]
[----------------------- ] [%50][--]
[-------------------------------------- ] [%72][\]
首先有括号来展示进度,其次就是右边展示进度百分比,以及旋转条互动。
初步优化: 显然我们的进度条一直都是在一行中出现的所以我们肯定要用到\r这家伙,而且我们的进度是要时时刻刻看见的啦! 自然每次进度更新都需要我们手动刷新一次!
初步代码并分析:
36 int main(){37 38 // 简单进度条的实现39 // 一个数组 还有一个循环即可40 char bar[100]; 41 const char* rotate ="|/-\\";42 memset(bar,' ',sizeof(bar));43 44 // 直接循环100次打印每次间隔100ms更新一下进度45 int i=0;
W> 46 for(i;i<100;i++)47 {48 printf("[%s][%% %d][%c]\r",bar,i,rotate[i%4]);49 fflush(stdout);50 usleep(100000);51 bar[i]='#';52 53 54 55 }56 57 58 return 0;59 }
运行的时候:
结束后:
我们发现下一行的命令行提示符并没有跳出来 这说明没有换行 光标落在了开头,以及第一个【这个括号丢失了,以及最终%99 没有到100.
问题分析:
1. 首先我们的bar打印的时候这里%s,我们进行memset全部置为了' '空格,但是末尾没有\0所以可能存在越界的问题
int main(){37 38 // 简单进度条的实现39 // 一个数组 还有一个循环即可40 char bar[101];// 进度是从0-100 所以一共有101个数 但是最后一共放\0 所以我们打算开辟102个大小41 const char* rotate ="|/-\\";42 memset(bar,' ',sizeof(bar));43 44 bar[100] ='\0';45 // 直接循环100次打印每次间隔100ms更新一下进度46 //47 //48 int i=0;
W> 49 for(i;i<101;i++)50 {51 printf("[%-100s][%d%%][%c]\r",bar,i,rotate[i%4]);52 fflush(stdout);53 usleep(10000); 54 if(i<100){55 bar[i]='#';56 }57 }58 printf("\n");59 return 0;60 }61
现在改进之后输出的情况就不会再有左边被占有的情况了。 所以在c中很多时候打印是依赖于\0来判断终止的所以这个地方格外注意就行了
三. 多文件编写实现进度条
3. 1 配置makefile以及创建三个文件
这里一共四个文件 :
makefile的配置:
mycode:processBar.c Main.c2 gcc -o $@ $^3 4 .PHONY:mycode5 .PHONY:clean6 clean:7 @rm -rf mycode
3.2 功能设计
在.h文件中包含的头文件: <unistd.h> <string.h> <stdlib.h> <stdlib.h>
以及声明一些函数
processBar.c 文件 这里放两个函数的具体实现一共是Bat的函数的打印,以及Bar的初始化
所以这里放的就是Bar,我们封装到一个文件中去1. 然后对应一些属性用全局变量来使用。
main.c: 可能会存在一些任务函数来调用进度条
3.3 补充 linux下c语言的颜色处理
可以去网上搜颜色的ANSI转义序列,对应的序列,使用方式就是
序列+"我是字符串" 这样就是控制了文本颜色 : 举个栗子:
#include <stdio.h>int main() {// 设置文本颜色为红色printf("\033[31mThis is red text\033[0m\n");// 重置文本颜色printf("This is default text\n");return 0;
}
但是我们可以提取宏定义一下这样可读性会高一些:
#define RED "\033[31m"4 5 int main(){6 7 // processBar(100);8 printf("普通文本\n");9 printf(RED "被染红文本\n"); 10 11 12 13 14 return 0;15 }
常见的颜色: 宏定义
// 定义ANSI转义序列宏
#define RESET "\033[0m"
#define BLACK "\033[30m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define YELLOW "\033[33m"
#define BLUE "\033[34m"
#define MAGENTA "\033[35m"
#define CYAN "\033[36m"
#define WHITE "\033[37m"
#define RESET "\033[0m" // 重置所有颜色和属性
如果不重置颜色最后会到这bash也被变色: 所以注意最后一次重置一下终端颜色
3.3 初步进度条带颜色的:功能实现
processBar.c
4 #include<unistd.h> h>5 6 #define BLUE "\033[34m" 7 #define YELLOW "\033[33m" 8 #define RESET "\033[0m" // 重置所有颜色和属性 9 10 #define NUM 101 11 #define body '#' 12 #define LEFT '[' 13 #define RIGHT ']' 14 15 char bar[101]; 16 const char* rotate ="|/-\\"; 17 18
W> 19 void processBar(int rate){ 20 21 22 bar[100] ='\0'; 23 int i=0;
W> 24 for(i;i<101;i++) 25 { 26 printf(YELLOW "[%-10s]" BLUE"[%d%%]" BLUE "[%c]\r" ,bar,i,rotate[i%4]); 27 fflush(stdout); 28 usleep(10000); 29 if(i<100){ 30 bar[i]='#'; 31 } 32 } 33 printf(RESET"\n"); 34 } 35 36 void BarInit(){ 37 38 memset(bar,' ',sizeof(bar)); 39
processBar.h
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 6 7 extern void processBar(int rate); 8 extern void BarInit(); 9
Main.c
1 #include "processBar.h" 2 3 #define RED "\033[31m" 4 5 int main(){ 6 7 // processBar(100); 8 // printf("普通文本\n"); 9 // printf(RED "被染红文本\n");10 11 processBar(100); 12 13 14 15 return 0; 16 }
~
这样代码输出的情况是这样的; ================
显然body部分用的是= 这两个相邻字符之间还是会存在间隙的,所以我们如何才能消除这样的间隙呢
3.4 方块字符 "█"
注意这个字符也是一个字符 只不过它对应的是utf8下的一个字符,相比原来的ascll编码中更广的一共具体的可以参考我之前文字对编码的了解,总之我们这里的思路就是利用utf8编码下的方块字符,这样就不会有间隙了: ██████████████████,看吧这是我复制了好几个在一块这就是方块字符的魅力,它的颜色也是可以改变的 :█, 所以方块字符的学习也会很轻松的。
第一我们这里使用的依然是char数组 但是 由于utf-8下的编码大小一个字符对应的是3自己所以原来对应101个字节大小可以修改为 char bar[301] // 原因就是 100个方块 最后一位放\0.
11111111111111
222222222222222
###########
显然这些常见的字符都会有一些间隙的这就是为什么要引入方块字符的原因。
3.5 进度条终极版的实现
processBar.c
#include<stdio.h>2 #include<string.h>3 #include<stdlib.h>4 #include<unistd.h>5 6 #define WHITE "\033[37m"7 #define RED "\033[31m"8 #define GREEN "\033[32m"9 #define BLUE "\033[34m"10 #define YELLOW "\033[33m" // 进度条颜色(作用于方块字符)11 #define RESET "\033[0m"12 13 #define NUM 301 // 100个方块×3字节 + 1个终止符(3×100+1=301)14 #define BODY "█" // 用字符串形式存储多字节方块字符(关键!)15 16 char bar[NUM]; // 足够存储100个方块(每个3字节)17 const char* rotate = "|/-\\";18 19 void BarInit() {20 memset(bar, 0, sizeof(bar)); // 初始化为空字符(避免多余空格)21 }22
W> 23 void processBar(int rate) {24 int i = 0;
W> 25 for (i; i < 101; i++) {26 // 打印时用YELLOW包裹整个进度条,确保方块字符颜色正确27 printf(WHITE "[" GREEN "%-s" WHITE "]" WHITE "[%d%%]" WHITE "[%c]""\r" RESET, 28 bar, i, rotate[i%4]);29 fflush(stdout);30 usleep(100000);31 if (i < 100) {32 // 用strcat拼接方块字符串(而非单字符赋值,适配多字节)33 strcat(bar, BODY); // 每次拼接一个"█"34 }35 }36 printf(RESET "\n");37 }38
processBar.h
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 6 7 extern void processBar(int rate);8 extern void BarInit();
Main.c
1 #include "processBar.h" 2 3 #define RED "\033[31m"4 5 int main(){6 7 // processBar(100); 8 // printf("普通文本\n"); 9 // printf(RED "被染红文本\n");10 11 processBar(100); 12 13 14 15 return 0;16 }
效果