五、make/Makefile
1.了解make/Makefile
(1)make
make是一个解释makefile中指令的命令工具。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中。
(2)Makefile
Makefile是Linux项目自动化构建工具,Makefile规定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。编译的安排就叫做构建。构建规则都写在Makefile文件里面,要学会如何Make命令,就必须学会如何编写Makefile文件。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率,makefile成为了一种在工程方面的编译方法。
make是一条命令,makefile是一个文件,两者搭配使用,形成可执行程序,完成项目自动化构建。
2.如何写一个Makefile
与源文件同级,创建一个Makefile文件:
将编译InsertSort.c的命令写入Makefile中。其中,第一行叫做依赖关系,第二行叫做依赖方法:
1. InsertSort:InsertSort.c #依赖关系 2. gcc InsertSort.c -o InsertSort -std=c99 #依赖方法
第一行开始的InsertSort叫做目标,InsertSort.c叫做前置条件,gcc InsertSort.c -o InsertSort -std=c99是命令,命令前有Tab键:
执行make命令后,会自动执行Makefile文件中的命令:
但是此时想再make一下,会发现不让make了:
因为可执行文件已经是最新了。Makefile默认的目标是文件,如果目标不是真实存在的文件,而是一个命令呢,那么这个目标就是伪目标,伪目标没有依赖关系,只有依赖方法,如下clean就是一个伪目标,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令:
1. InsertSort:InsertSort.c 2. gcc InsertSort.c -o InsertSort -std=c99 3. .PHONY:clean #伪目标 4. clean: 5. rm -f InsertSort
其中:
由于make扫描Makefile文件时,默认只会形成一个目标依赖关系,一般是第一个,因此,现在执行make,并不能达到删除InsertSort可执行文件的目的,因为make会生成第一个目标也就是生成InsertSort可执行程序:
此时要执行非第一个目标时,需要make加上目标:
当把两个目标的顺序交换一下,会发现,执行make时就先执行clean命令:
这下直接make时,就不是编译生成可执行文件了, 而是执行clean命令,如果要生成可执行程序,必须执行make 加上目标:
也可以用$@表示目标文件,$^表示上一行的冒号后面的依赖文件列表:
六、进度条小程序
1.回车和换行
- 回车:行不变,列回到当前行的最开始
- 换行:列不变,行切换到下一行
例如,如下代码,当执行打印后,需要等待3秒才结束程序:
enter.c
1. #include<stdio.h> 2. 3. int main() 4. { 5. printf("33333333\n"); 6. sleep(3); 7. 8. return 0; 9. }
Makefile
1. enter:enter.c 2. gcc -o $@ $^ 3. .PHONY:clean 4. clean: 5. rm -f enter
那么打印内容会在屏幕上出现3秒以后程序才结束:
如果不加\n,那么一开始就不会打印字符串,等待3秒后,才会打印出字符串:
这两个程序运行结果并不代表sleep先于printf执行,其实只是printf已经执行,但是数据没有被立即刷新到显示器上而已。当没有\n时,字符串会暂时保存到用户C语言级别的缓冲区当中,显示器设备的刷新策略就是行刷新(\n),即立即刷新。
如果想在不带\n的情况下,让数据立即刷新出来, 可以调用fflush接口,不过fflush的参数是文件流:
那么对于这个程序,应该给fflush传什么参数呢?
由于在C语言中的文件操作,系统会默认地帮我们把对应的文件以C语言的方式打开,并且会默认打开3个输入输出流:
为什么程序会默认打开着3个输入输出流呢?因为程序为了帮我们计算数据,就有数据源和数据结果,C语言为了避免我们麻烦,默认给我们打开了这3个输入输出流,供我们读写。
对于刚刚的程序,想让代码立即刷新出来,我们借助fflush函数,因为printf是向stdout打印,那么刷新也是向stdout刷新,所以将stdout作为fflush的参数:
再执行make clean;make,会发现先刷新字符串,但是再等了3秒程序才结束:
对于\r,仅回车,行不变,列回到当前行的最开始,由于没有\n,所以每次循环的打印结果不会被显示出来:
1. #include<stdio.h> 2. 3. int main() 4. { 5. int count = 5; 6. while(count) 7. { 8. printf("%d\r",count); 9. count--; 10. sleep(1); 11. } 12. 13. return 0; 14. }
那么为了让打印结果显示出来,可以使用fflush:
但是结果就变成在这个位置倒计时:
假如将count初始化为10,执行结果就更离谱:
这是因为显示器是字符设备,凡是显示到显示器上面的内容都是字符,凡是从键盘读取的内容也都是字符。所以上面第一次打印的count值为10并不是数字10,而是字符'1'和字符'0'。再对count--时,count的值只有一个字符,比如9,所以只能覆盖到一个字符,因此字符'0'永远都不会动。要解决这个问题,可以输出2个字符,这样就会把字符'0'给覆盖了:
执行结果:
2. 实现进度条小程序
假如实现进度条小程序,在同一行打印'#',每次刷新就多打印一个'#',以下代码是实现不了的,因为显示器是行刷新,没有\n就刷新不出来进度条:
1. #include<stdio.h> 2. #include<string.h> 3. #include<unistd.h> 4. 5. #define NUM 100 6. 7. int main() 8. { 9. char bar[NUM+1]; 10. memset(bar,'\0',sizeof(bar)); 11. 12. int i = 0; 13. while(i <= 200) 14. { 15. printf("%s",bar);//没有\n,在显示器上显示不出来‘#’ 16. bar[i] = '#'; 17. i++; 18. sleep(1); 19. } 20. 21. return 0; 22. }
那么加了\n以后呢?虽然'#'可以打印出来,但是打印却变成了不在同一行显示'#',这不符合我们的要求:
所以考虑到使用fflush:
但是发现运行结果,是每隔1秒自动多打印n个'#':
这不符合我们的需求,我们需要每次增加一个'#'。可以加上\r
现在每次刷新增加一个#":
这下达到了我们的要求,但是有没有发现好像刷新的太慢了,那就把刷新间隔缩短一些:
这下快一些了 :
但是i的上限是200,程序要执行好久才能看到最终的执行结果,而且还刷屏,不好观察,将i的上限改为20:
执行结果发现,执行完毕时提示符[delia@VM-8-17-centos progressBar]$ 和进度条在同一行呢:
能不能把提示符去掉?可以在程序执行结束时,换行:
现在程序执行结束就换行了:
如果想让进度条被[ ]包起来呢?
发现进度条从右向左显示:
加上'-'就从左向右显示了:
执行结果如下:
想加上数字百分比呢?
执行结果如下:
想在结尾增加光标旋转呢?
执行结果如下:
假如想更换进度条颜色呢?比如换成绿色:
执行结果如下:
好啦,进度条做好啦。完整代码段:
1. #include<stdio.h> 2. #include<string.h> 3. #include<unistd.h> 4. 5. #define NUM 100 6. 7. int main() 8. { 9. char bar[NUM+1]; 10. memset(bar,'\0',sizeof(bar)); 11. 12. const char *token = "|/-\\"; 13. 14. int i = 0; 15. while(i <= 100) 16. { 17. printf("\033[32m[%-100s][%d%%] [%c]\r",bar,i,token[i%4]); 18. fflush(stdout); 19. bar[i] = '#'; 20. i++; 21. usleep(50000); 22. } 23. 24. printf("\n"); 25. return 0; 26. }