一、认识make和Makefile
1、会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
2、一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
3、makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
4、make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
5、make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
二、初见make和Makefile
2.1、makefile的创建
这里我创建一个Makefile文件,直接使用 vim makefile,主义makefile的m可以大写也可以小写,我们在写makefile文件时,别打错字母,因为系统只认识makefile和Makefile;
注意: Makefile 文件中,命令行前面是用 tab 键来缩进的,不能用空格空格键
如果命令行是用 tab 键来缩进,那命令行就会变颜色
makefile文件格式:
目标文件:依赖文件 命令
- 目标文件一般就是最终生成可执行文件,而生成目标文件需要去找他所需的所有依赖文件,如果依赖文件也需要其他的文件,就一直找,直到不需要依赖文件,就好比递归,需要一层一层递归,直到最后不需要为止
- 这里拿 add.c add.h main.c 来举例,我现在需要把 add.c 和 main.c编译出可执行文件
2.2、make的使用
这里我们就把简单的Makefile文件创建好了,我们来在来创建我们要实现的add.c文件。
我们保存并退出,在命令行输入 make 编译我们的add.c
执行完该Makefile文件后,显示了文件中的执行命令,并且文件中多出了 add.o , main.o 还有我们想要的myadd可执行文件
我们写的这个Makefile中还有一句标签为
clean:
rm -f *.o myadd
而这里的clean只是一个标签名,我们可以使用make工具来操作这行命令,例如
我们使用 make clean时就会
执行之后,所有 .o 文件和myadd可执行文件也被删除了
三、Makefile中的变量
3.1、自定义变量
Makefile 中允许使用等号自定义变量
Makefile中的变量定义和 shell 变量的定义非常相似,不同的是 Makefile中的变量的名称可以是任何不包含 :, #,= 和空字符的字符序列,并且等号两边可以有空格(shell 定义变量的等号两个不允许出现空格)
变量名=值 --等号两边不能有空格
3.2、变量的调用
在变量标识符前加美元符 $ 来引用,例如
如果你不想看到执行命令时,把makefile中的命令再打印一遍可以在makefile中的命令前加一个@
3.3、变量的赋值
Makefile一共提供了四个赋值运算符 (=、:=、?=、+=)
1).普通赋值 =
如果按照c语言的逻辑,var2应该等于12,但是这里最后打印var2的值为100,所以普通赋值表示赋值最新的变量值
2).立即赋值 :=
立即赋值则是直接赋值,不会在改变
3).询问赋值 ?=
如果该变量前面已经赋过值了,此次就不能再赋值了,如果没有就赋值,只有在该变量为空时才设置值
4.追加赋值 +=
类似字符串连接,将值追加到变量末尾,在追加的时候,自动添加空格
4.4、特殊变量
$^ --表示所有依赖文件
$@ --表示目标文件
$< --表示第一个依赖文件
% --通配符,表示所有,和linux中的 * 一样
利用这些特殊变量和自定义变量,我们就可以将Makefile文件写的更加简洁
四、进度条的实现
我们使用make和makedile工具实现一个进度条的功能
4.1、预备知识
- 换行和回车的区别
- \r:回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖,如果之前的内容比下一次来的的内容长,就不会被完全覆盖
- \n:换行,换到当前位置的下一行,而不会回到行首
1#include <stdio.h>
2 int main()
3 {
4 printf("hello world\n");
5 return 0;
6 }
当然上面的实现是可以打印出 hello world
#include <stdio.h>
int main()
{
printf("hello world\r");
return 0;
}
第二个是打印不出来hello world
不难发现 \n 可以打印出来,而 \r,不能打印出来,因为显示器模式是行刷新缓冲区是按行缓冲的,没有\n,就不能立即刷新。 \r 回到行首后,会进行覆盖写,shell 提示符会覆盖掉之前写的 “hello world”,如果我们在 “hello world” 不加 \r,则不会进行覆盖写,shell 提示符会顺着 “hello world” 往后写。
缓冲区的理解行缓冲是缓冲区刷新策略的一种,在行缓冲模式下,当输入和输出中遇到 ‘\n’ 换行时,就会刷新缓冲区,下面我们认识头文件<unistd.h>的三个函数
sleep : Linux 下的休眠函数,单位是秒
u s l e e p usleepusleep:和sleep 一样,单位ms(即10-6 m)
f f l u s h fflushfflush :刷新缓冲区
1 #include <stdio.h>
2 #include <unistd.h>
3 int main()
4 {
5 printf("hello world");
6 sleep(3);
7 return 0;
8 }
我们知道函数代码语句是从上到下依次进行的,而我们看到的却是先休眠三秒,然后再打印出"hello world",原因是因为数据保存在缓冲区中,没有主动刷新。当程序退出后,保存在缓冲区中的数据被自动刷新出来了,如果我们想提前刷新,便可以调用f f l u s h fflushfflush函数来刷新缓冲区。
1 #include <stdio.h>
2 #include <unistd.h>
3 int main()
4 {
5 printf("hello world");
6 fflush(stdout);
7 printf("\n");
8 sleep(3);
9 return 0;
10}
4.2进度条的实现
我们需要以下文件
main.c文件实现:宏定义,参数设置,函数调用和头函数包含
当然我们实现进度条都是要在一个场景下实现的,这里我模拟实现了一个下载的场景
1 #include"processbar.h"
2 #include<time.h>
3 #include<stdio.h>
4 #include<unistd.h>
5
6 #define FILESIZE 1024*1024*1024
7 //模拟一种场景,表示一种下载的任务
8 void download(callback_t cb)
9 {
10 srand(time(NULL)^1023);
11 int total=FILESIZE;
12 while(total)
13 {
14 usleep(10000); //下载动作
15 int one = rand()%(1024*1024*10);
16 total-=one;
17 if(total<0)
18 {
19 total=0;
20 }
21 int download=FILESIZE-total;
22 double rate=(download*1.0/(FILESIZE))*100.0; //0 23.4 45.6 67.5
23 cb(rate);
24 }
25 }
26 int main()
27 {
28 // process();
29 download(process_flush);
30 return 0;
31 }
~
~
processbar.h文件实现
1 #pragma once
2 #include<stdio.h>
3 #include <stdlib.h>
4 #define NUM 103
5 #define Body '='
6 #define Head '>'
7 typedef void (*callback_t)(double);
8
9
10 //version1
11 void process();
12
13 //version2
14 void process_flush(double rate);
~
processbar.c文件实现
1 #include"processbar.h"
2 #include<string.h>
3 #include<unistd.h>
4
5 const char*lable="|/-\\";
6 void process()
7 {
8 char buffer[NUM];
9 memset(buffer,'\0',sizeof(buffer));
10 int cnt=0;
11 int n=strlen(lable);
12 buffer[0]=Head;
13 while(cnt<=100)
14 {
15 printf("[%-100s][%3d%%][%c]\r",buffer,cnt,lable[cnt%n]);
16 fflush(stdout);
17 buffer[cnt++]=Body;
18 if(cnt<100)
19 {
20 buffer[cnt]=Head;
21 usleep(500000);
22 }
23 }
24 printf("\n");
25 }
26
27 //version2
28 //进度是多少,进度条是不知道的。,另外,进度应该是依附于一个东西的,比如下载
29 char buffer[NUM]={0};
30 void process_flush(double rate)
31 {
32 static int cnt =0;
33 int n=strlen(lable);
34 if(rate<=1.0)
35 {
36 buffer[0]=Head;
37 }
38 printf("[\033[4;32;44m%-100s\033[0m][%.1f%%][%c]\r",buffer,rate,lable[cnt%n]);
39 fflush(stdout);
40
41 buffer[(int)rate]=Body;
42 if((int)rate+1<100)
43 {
44 buffer[(int)(rate+1)]=Head;
45 }
46 if(rate>=100.0)
47 {
48 printf("\n");
49 }
50 cnt++;
51 cnt%=n;
52 }
makefile文件的实现
processbar:main.o processbar.o gcc -o $@ $^ main.o:main.c gcc -c main.c processbar.o:processbar.c gcc -c processbar.c .PHONY:clean clean: rm -f main.o processbar.o processbar
效果展示: