make/Makefile的重要性
- 会不会写Makefile,从侧面阐明了一个人是否具备完成大型工程的能力。
- 一个工程的源文件不可胜数,按照其范例、功能、模块分别放在若干个目录当中,Makefile定义了一系列的规则来指定:哪些文件需要先编译,哪些文件需要后编译,乃至于进行更复杂的功能操作。
- Makefile带来的好处就是“主动化编译”,一旦写好,只需一个make命令,整个工程完全主动编译,极大的提高了软件开发的服从。
- mak是一个命令工具,是一个解释Makefile当中指令的命令工具,一般来说,大多数的IDE都有这个命令,例如:Delphi的make,Visual
- C++的nmake,Linux下GNU的make。可见,Makefile都成为了一种在工程方面的编译方法。
- make是一条命令,Makefile是一个文件,两个搭配使用,完成项目主动化构建。
依赖关系和依赖方法
在使用make/Makefile前我们起首应该明白各个文件之间的依赖关系以及它们之间的依赖方法。
依赖关系: 文件A的变更会影响到文件B,那么就称文件B依赖于文件A。
例如,test.o文件是由test.c文件通过预处理、编译以及汇编之后天生的文件,所以test.c文件的改变会影响test.o,所以说test.o文件依赖于test.c文件。
依赖方法: 如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。
例如,test.o依赖于test.c,而test.c通过gcc -c test.c -o
test.o指令就可以得到test.o,那么test.o依赖于test.c的依赖方法就是gcc -c test.c -o test.o。
就相称于月末没钱,找父母要,其中依赖关系是我是你孩子,依赖方法就是要钱,缺一不可。
多文件编译
当你的工程当中有源文件的时候,应该怎样进行编译天生可执行程序呢?
gcc
起首,我们可以直接使用gcc指令对源文件进行编译,进而天生可执行程序。
单个文件:使用源文件直接天生可执行程序
但进行多文件编译的时候一般不使用源文件直接天生可执行程序,而是先用每个源文件各自天生本身的二进制文件,然后再将这些二进制文件通过链接天生可执行程序。
1.先各自天生二进制文件
2.将二进制文件通过链接天生可执行程序
原因:
若是直接使用源文件天生可执行程序,那么其中一个源文件进行了修改,再天生可执行程序的时候就需要将所以的源文件重新进行编译链接。
而若是先用每个源文件各自天生本身的二进制文件,那么其中一个源文件进行了修改,就只需重新编译天生该源文件的二进制文件,然后再将这些二进制文件通过链接天生可执行程序即可。
make 和Makefile
但是随着源文件个数的增加,我们每次重新天生可执行程序时,所需输入的gcc指令的长度与个数也会随之增加。这时我们就需要使用make和Makefile(m可以大写或小写)了,这将大大淘汰我们的工作量
第一步:在源文件地点目录下创建一个名为Makefile/makefile的文件。
第二步 :用vim编写格式
第一种
黄色的为依赖关系白色的为方法
make是怎样⼯作的,在默认的⽅式下,也就是我们只输⼊make命令。
那么: 1. make会在当前⽬录下找名字叫“Makefile”或“makefile”的⽂件。
2. 如果找到,它会找⽂件中的第⼀个⽬标⽂件(target),在上⾯的例⼦中,他会找到 myproc 这 个⽂件,并把这个⽂件作为最终的⽬标⽂件。
3. 如果 myproc ⽂件不存在,或是 myproc 所依赖的后⾯的 myproc.o ⽂件的⽂件修改时间要 ⽐ myproc 这个⽂件新(可以⽤ touch 测试),那么,他就会执⾏后⾯所定义的命令来⽣成 myproc 这个⽂件。
4. 如果 myproc 所依赖的 myproc.o ⽂件不存在,那么 make 会在当前⽂件中找⽬标为 myproc.o ⽂件的依赖性,如果找到则再根据那⼀个规则⽣成 myproc.o ⽂件。(这有点像⼀ 个堆栈的过程) 5. 固然,你的C⽂件和H⽂件是存在的啦,于是 make 会⽣成myproc.o ⽂件,然后再⽤myproc.o ⽂件声明 make 的终极任务,也就是执⾏⽂件 hello 了。
6. 这就是整个make的依赖性,make会⼀层⼜⼀层地去找⽂件的依赖关系,直到最终编译出第⼀个 ⽬标⽂件。
7. 在找寻的过程中,如果出现错误,⽐如最后被依赖的⽂件找不到,那么make就会直接退出,并 报错,⽽对于所定义的命令的错误,或是编译不成功,make根本不理。
第二种
Makefile文件的简写方式:
变量定义部门
- BIN=test:定义变量BIN,代表最终天生的可执行文件名,这里设置为test。
- SRC=$(wildcard *.c):使用wildcard函数获取当前目录下所有扩展名为.c的源文件,将结果赋值给变量SRC。
- OBJ=$(SRC:.c=.o):利用变量替换功能,把SRC中所有.c后缀的文件名替换为.o后缀,得到对应的目标文件列表,赋值给变量OBJ。
- CC=gcc:定义变量CC,指定使用gcc作为编译器。
- RM=rm -f:定义变量RM,表示删除文件的命令,-f选项表示逼迫删除,不扣问。
编译规则部门
- $(BIN)
(OBJ):这是一个依赖规则,表明可执行文件$(BIN)(即test)依赖于所有的目标文件$(OBJ)(由.c文件编译天生的.o文件)。
- $(CC) $^ -o $@:当满足上一条依赖规则需要重新天生目标时,执行此命令。$(CC)是前面定义的gcc编译器;$^是主动化变量,代表所有依赖文件,即$(OBJ)中的目标文件;-o是gcc选项,用于指定输出文件名;$@是主动化变量,代表当前规则的目标文件,即$(BIN)(test)。该命令整体意思是用gcc将所有目标文件链接成可执行文件test。
- echo "链接 $^ 成 $@":执行链接命令后,输出提示信息,阐明正在将哪些依赖文件链接成哪个目标文件。
模式规则部门
- %.o:%.c:这是一个模式规则,%是通配符,表示所有.o文件依赖于对应的.c文件,好比main.o依赖于main.c 。
- $(CC) -c $<:针对上述模式规则,当.c文件发生变化需要重新编译时执行此命令。$(CC)是gcc编译器;-c选项表示只进行编译,不链接;$<是主动化变量,代表当前规则的第一个依赖文件,也就是对应的.c源文件。该命令会将.c文件编译成.o目标文件。
- echo "编译... $< 成 $@":编译完成后,输出提示信息,显示正在将哪个.c文件编译成哪个.o文件。
伪目标和清理规则部门
- .PHONY:clean:声明clean为伪目标。这意味着纵然当前目录下存在名为clean的文件,make也会执行clean规则,而不是认为它已经是最新的而不执行。
- clean::clean规则的开始。
- @$(RM) $(OBJ) $(BIN):执行此命令,@表示执行命令时不显示命令本身;$(RM)是前面定义的删除命令;该命令会删除所有目标文件$(OBJ)和可执行文件$(BIN)(test) ,起到清理编译天生文件的作用。
Linux小练习 ———倒计时
- printf("%-2d\r", a);:使用printf函数输出变量a的值。%-2d是格式化字符串,-表示左对齐,2表示输出宽度为 2 个字符;\r是回车符,在 Linux 终端中,它会将光标移到当前行的开头。
- fflush(stdout);:刷新尺度输出缓冲区。在 Linux 体系中,为了提高 I/O 服从,输出操作每每不是立即执行的,而是先将数据存储在缓冲区中。调用fflush(stdout)可以逼迫将缓冲区中的数据立即输出到终端。
小练习——进度条
1. process.h 和 process.c 文件
宏定义
- #define NUM 101
- #define STYLE '='
复制代码
- NUM 定义了进度条字符数组的长度,101 用于存储进度条字符和字符串竣事符 '\0'。
- STYLE 定义了进度条使用的字符,这里使用 = 来表示进度。
process_v1 函数
- void process_v1()
- {
- char buffer[NUM];
- memset(buffer, 0, sizeof(buffer));
- const char *lable="|/-\";
- int len = strlen(lable);
- int cnt = 0;
- while(cnt <= 100)
- {
- printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]);
- fflush(stdout);
- buffer[cnt]= STYLE;
- cnt++;
- usleep(50000);
- }
- printf("\n");
- }
复制代码
- 初始化部门:
- char buffer[NUM];:定义一个字符数组 buffer 用于存储进度条的字符。
- memset(buffer, 0, sizeof(buffer));:将 buffer 数组的所有元素初始化为 0,即空字符 '\0'。
- const char *lable="|/-\\";:定义一个常量字符串 lable,用于显示旋转的进度指示符。
- int len = strlen(lable);:计算 lable 字符串的长度。
- int cnt = 0;:初始化计数器 cnt 为 0,表示进度从 0% 开始。
- 循环部门:
- while(cnt <= 100):循环条件,当进度小于等于 100% 时继承循环。
- printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]);:输出进度条信息,%-100s 表示左对齐输出长度为 100 的字符串,%d%% 输出当前进度百分比,%c 输出旋转指示符。\r 用于将光标移动到行首,实现覆盖输出。
- fflush(stdout);:逼迫刷新尺度输出缓冲区,确保进度条信息立即显示。
- buffer[cnt]= STYLE;:将 buffer 数组中当前进度位置的字符设置为 STYLE(即 =)。
- cnt++;:计数器加 1,表示进度增加 1%。
- usleep(50000);:停息 50000 微秒(即 50 毫秒),模拟进度更新的耽误。
- 竣事部门:
- printf("\n");:进度达到 100% 后,输出换行符,竣事进度条显示。
FlushProcess 函数
- void FlushProcess(double total, double current)
- {
- char buffer[NUM];
- memset(buffer, 0, sizeof(buffer));
- const char *lable="|/-\";
- int len = strlen(lable);
- static int cnt = 0;
- int num = (int)(current*100/total);
- int i = 0;
- for(; i < num; i++)
- {
- buffer[i] = STYLE;
- }
- double rate = current/total;
- cnt %= len;
- printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]);
- cnt++;
- fflush(stdout);
- }
复制代码
- 初始化部门:
- 与 process_v1 函数类似,定义并初始化 buffer 数组和 lable 字符串。
- static int cnt = 0;:定义一个静态变量 cnt,用于控制旋转指示符的显示。静态变量在函数调用竣事后会保存其值。
- int num = (int)(current*100/total);:根据当前完成量 current 和总量 total 计算当前进度的百分比对应的字符数量。
- 添补进度条部门:
- for(; i < num; i++):循环将 buffer 数组中前 num 个元素设置为 STYLE(即 =)。
- 计算进度和输出部门:
- double rate = current/total;:计算当前进度的比例。
- cnt %= len;:对 cnt 取模,确保 cnt 的值在 0 到 len - 1 之间。
- printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]);:输出进度条信息,%.1f%% 表示输出一位小数的百分比。
- cnt++;:cnt 加 1,用于更新旋转指示符。
- fflush(stdout);:逼迫刷新尺度输出缓冲区。
2. main.c 文件
全局变量
- double total = 1024.0;
- double speed = 1.0;
复制代码
- total:定义文件的总大小为 1024.0,单位可以明白为 MB。
- speed:定义下载速率为 1.0,单位可以明白为 MB/s。
DownLoad 函数
- void DownLoad()
- {
- double current = 0;
- while(current <= total)
- {
- FlushProcess(total, current);
- usleep(3000);
- current += speed;
- }
- printf("\ndownload %.2lfMB Done\n", current);
- }
复制代码
- 初始化部门:
- double current = 0;:初始化当前下载量 current 为 0。
- 循环部门:
- while(current <= total):循环条件,当当前下载量小于等于总大小时继承循环。
- FlushProcess(total, current);:调用 FlushProcess 函数更新进度条显示。
- usleep(3000);:停息 3000 微秒(即 3 毫秒),模拟下载数据的耽误。
- current += speed;:当前下载量增加 speed 的值。
- 竣事部门:
- printf("\ndownload %.2lfMB Done\n", current);:下载完成后,输出下载完成的信息。
main 函数
- int main()
- {
- DownLoad();
- DownLoad();
- DownLoad();
- DownLoad();
- DownLoad();
- DownLoad();
- DownLoad();
- DownLoad();
- return 0;
- }
复制代码 多次调用 DownLoad 函数,模拟多次文件下载过程。
完备代码
process.h
- #pragma once
- #include <stdio.h>
- // 声明进度条函数
- void process_v1();
- void FlushProcess(double total, double current);
复制代码 process.c
- #include "process.h"#include <string.h>#include <unistd.h>#define NUM 101
- #define STYLE '='
- // 版本1:简单的进度条实现void process_v1() { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable = "|/-\"; int len = strlen(lable); int cnt = 0; while (cnt <= 100) { printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt % len]); fflush(stdout); buffer[cnt] = STYLE; cnt++; usleep(50000); } printf("\n");}// 版本2:根据总进度和当前进度更新进度条void FlushProcess(double total, double current) { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable = "|/-\"; int len = strlen(lable); static int cnt = 0; int num = (int)(current * 100 / total); int i = 0; for (; i < num; i++) { buffer[i] = STYLE; } double rate = current / total; cnt %= len; printf("[%-100s][%.1f%%][%c]\r", buffer, rate * 100, lable[cnt]); cnt++; fflush(stdout);}
复制代码 main.c
- #include "process.h"#include <stdio.h>#include <unistd.h>double total = 1024.0;
- double speed = 1.0;
- // 模拟下载函数,调用进度条更新函数void DownLoad() { double current = 0; while (current <= total) { FlushProcess(total, current); usleep(3000); current += speed; } printf("\ndownload %.2lfMB Done\n", current);}int main() { // 多次调用下载函数 for (int i = 0; i < 8; i++) { DownLoad(); } return 0;}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |