赞
踩
前言:
- 会不会编写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
- 一个工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
- Makefile的好处在于自动化编译,一旦编写好Makefile文件,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个用于解释Makefile中指令的命令工具,通常大多数的IDE都有这个命令,如:Delphi的make,Visual C++的nmake,Linux下GUN的make。 可见,Makefile成为了一种在工程方面的编译方法。
- make是一条命令,Makefile是一个文件,二者搭配使用以完成项目自动化构建。
首先以对 test.c
文件的编译为例来了解make与Makefile的基本使用:
Makefile的基本编写规则:
依赖关系
和 依赖方法
。如上图所示,我们以 目标文件:依赖文件
的格式来编写依赖关系,表示要生成目标文件,需要依赖有对应的依赖文件。在图例中即是:test文件(可执行文件)的生成依赖于test.o文件(二进制目标文件);test.o文件的生成依赖于test.s文件(汇编语言文件);test.s文件的生成依赖于test.i文件(经过预处理后的C原始程序);test.i文件的生成依赖于test.c文件(源文件)。而单有依赖关系是无法通过依赖文件生成目标文件的,因为依赖关系只告诉了目标文件从何而来,并没有说如何根据依赖文件生成目标文件,因此还需要依赖方法,所谓依赖方法,其实就是我们平常在命令行上输入的一条条指令,如示例中:我们通过命令 gcc test.o -o test
可以从test.o文件得到test文件,这也就是依赖关系 test:test.o
对应的依赖方法;以此类推,gcc -c test.s -o test.o
是依赖关系 test.o:test.s
对应的依赖方法;gcc -S test.i -o test.s
是依赖关系 test.s:test.i
对应的依赖方法;gcc -E test.c -o test.i
是依赖关系 test.i:test.c
对应的依赖方法。有了依赖关系和依赖方法,我们就可以生成对应的目标文件。 有两点值得注意的是:每次编写依赖方法时应在依赖关系下另起一行并在开头空一个 Tab
键的位置,这是固定的编写格式;依赖文件列表可以为空,当其为空时,则表示不生成该目标文件,只执行相应的命令。.PHONY
来声明伪目标,格式为:.PHONY:伪目标
。Makefile中的伪目标表示目标名称,并不代表真正的文件名,与实际存在的同名文件没有相互关系,因此伪目标不管同名文件是否存在都会执行对应的生成指令。伪目标的作用有两个:使目标对象无论如何都要重新生成;并不生成目标文件,而是为了执行一些指令。#
以表示行注释。make的工作规则:
GUNmakefile
、makefile
、Makefile
的文件。因此,如上图所示,执行make命令前需要先在项目所在当前目录下创建相应的文件(这里是Makefile)。touch
命令更新源文件最近修改时间后 (说明: touch
命令在文件不存在时,会创建文件,而如果文件已经存在,则将文件的修改时间更新至最新;当然这里还可以通过重新编辑保存文件的方式来更新文件最近修改时间) ,使得源文件的最近修改时间新于当前目录下的目标文件,再次执行make命令则显示成功,并且当前目录下的目标文件的最近修改时间进行了更新,又新于源文件了。那如果想让某目标下的命令不论时间如何每次都能执行呢?如上Makefile编写规则中说到,可以采用声明伪目标的方式,使得对应命令总是被执行,但此处并不建议将目标可执行文件声明为伪目标,因为每次编译都需要一定的消耗,如果当前目标已是最新,没必要再执行命令生成一样的目标。
关于项目清理:
工程是需要被清理的。如最开始图例中所示,我们使用了 .PHONY
声明了伪目标 clean
(顾名思义,该目标的意义在于清理),这个伪目标并没有所谓的依赖文件,显然是起到上述中的第二个作用,即不生成目标文件,总是执行相应的指令。可以看到, clean
对应的指令为 rm -f test.i test.s test.o test
,目的在于清除所有的目标文件,以便重新编译,也就是所谓的项目清理。由于伪目标 clean
没有被第一个文件直接或间接关联,因此默认情况下执行 make
命令, clean
目标下的命令不会自动执行,需要通过指定目标,即以 make clean
的命令格式使得 clean
目标下的命令被执行。而基于伪目标的特性,无论当前目录下是否有同名文件clean,或是否有已经生成的目标文件,只要执行 make clean
命令,clean对应的指令都会被执行(如下图所示)。
在编写进度条程序之前,得先谈谈两个概念:\n
和 \r
在我们编写C程序时常常会用到 \n
换行符来使内容另起一行输出,那 \r
又表示什么呢?其实在我们日常编辑中,可以看到,无论是输入还是输出,文字显示总是跟着光标的位置,光标在哪,则在哪输入输出。同样的,我们的程序在输出时也是跟随着光标的移动进行。\n
表示的是回车并换行,即在输出完当前内容后,将光标移动到下一行的行首再进行之后内容的输出,当然,光标的移动不是说突然就变到了那个位置,而是根据上下左右的方向一步步到达指定位置的,而光标移动到下一行行首的方式可以分为两种:一是先向左移动到当前行的行首,再向下移动到下一行;二是先向下移动到下一行,再向左移动到下一行行首。如图所示,键盘上的回车键等同于 \n
,其造型则表示出了光标的移动方式。而 \r
表示的是只回车不换行,即输出完当前内容后,将光标移动到当前的行的行首,因此,如果再碰到 \r
之后如果还有内容需要输出的话,会重新从当前行行首开始输出,覆盖上一次的输出内容。
\n
我们已经比较熟悉了,那下面以一个例子来看看 \r
对输出的影响:
\r
会使光标回到当前行行首,而后输出的内容会覆盖之前的内容,那是不是因为命令提示行输出覆盖太快所以没能看到内容显示呢?那我们试着在输出内容后进行延时。\r
后我们没能看到相关内容输出呢?这里不得不提到一个概念:缓冲区
。我们所编写的内容并不是直接输出到屏幕上(标准输出流)的,而是先输出到缓冲区中,在由缓冲区输出到屏幕。这里没能看到相关内容的输出是因为其仍保留在缓冲区中没有输出,只有当缓冲区刷新时,其中的保留的内容才会输出,而对于没有添加换行符和回车符或者添加的是换行符 \n
的情况,通常会自动刷新缓冲区,因此没有主动刷新也可以正常输出内容。那了解的原因所在后,我们在程序中主动刷新缓冲区,再看看输出结果:可以看到,增加刷新缓冲区后,相关内容正常显示了,也符合输出后光标回到当前行首,由命令提示行覆盖输出内容。我们不是要编写进度条程序吗?这与 \r
有什么关系呢?想来进度条大家都不陌生,就是以一行上显示的移动进程来表示某项工作的进度。也就是说,从0-100的进度需要再同一行上进行变化,而 \r
具有输出内容后将光标移动到当前行首的作用,这就契合了进度条的变化过程,基于此,以下模拟实现进度条:
proc.c
文件中,将对应的函数声明编写在头文件 proc.h
中,再创建测试文件 procTest.c
,在其中编写主函数并调用进度条函数。这里实现了两种不同形式的进度条:符号移动版和色块移动版。#include "proc.h" #define STYLE '=' //进度条移动符号 //符号形式移动版 void process(){ char bar[101];//进度条字符串,预留一个'\0'的位置 char status[4] = {'|', '/', '-', '\\'}; //表示运行状态,循环数组中的字符 memset(bar, '\0', sizeof(bar));//初始将进度条字符串中的内容全部置为'\0' int i = 0;//考虑执行标准问题,这里在外初始化 for(; i <= 100; i++){ //控制格式,循环输出进度条字符串,通过字符串的变化来表示进度条的变化 //\033[选项;选项;选项m表示对其后输出内容的颜色控制,0m默认无颜色 printf("[\033[0;36m%-100s\033[0m][%d%%][%c]\r", bar, i, status[i%4]); fflush(stdout);//刷新缓冲区,确保字符串内容正常输出 bar[i] = STYLE;//每次输出后修改一个字符为对应的进度条移动符号 if(i < 99) bar[i+1] = '>';//增加箭头显示,当进度达到100%,去掉箭头 usleep(100000);//延时0.1s显示 } printf("\n");//结束后换行输出命令提示行 } //色块形式移动版 void process_color(){ char bar[102];//进度条字符串,预留两个'\0'的位置 char status[4] = {'|', '/', '-', '\\'}; memset(bar, ' ', sizeof(bar));//初始将字符串中内容全部置' '(空字符) bar[101] = '\0';//保持最后一个字符总是为'\0' int i = 0; //控制中间的'\0'字符将整个字符数组分为两个字符串输出,确保两个字符串的长度加起来总是为100 //循环输出两个字符串,控制前一个字符串总是有颜色,后一个字符串总是无颜色 for(; i <= 100; i++){ bar[i] = '\0';//控制分隔字符串的'\0'移动 //输出带背景色的空字符串与不带背景色的空字符串 printf("[\033[0;30;46m%s\033[0m%s][%d%%][%c]\r", bar, bar+i+1, i, status[i%4]); fflush(stdout);//刷新缓冲区 bar[i] = ' ';//将前一个字符串中的内容均置为空字符' ' usleep(100000);//延时0.1s显示 } printf("\n");//结束后换行输出命令提示行 }
以上是我对make与Makefile工具使用的一些学习记录总结,如有错误,希望大家帮忙指正,也欢迎大家给予建议和讨论,谢谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。