引言:
我们在Windows下编程时使用vs这种集成开发环境,里面什么编译运行调试清理等等服务都被一连串打包好了。在Linux下怎么实现呢?使用我们伟大的makefile!
makefile是Linux下的一个工具,通过文本编辑器vim对文件内容编辑来操作该工具。
平常我们创建一个C语言文件,我们需要编辑它,然后编译,生成可执行文件,然后如果生成的结果错了我们还要重新编辑C语言文件,生成一个新的可执行文件等等,如果我们的工程同时有很多个C语言项目,那不是有很多重复的步骤吗?
我们用vim在一个文件里把我们需要的指令啊全部写进去,变成一个指令集。就像写代码会把函数的调用写在一个包里一样,我们只要输入一个指令就可以少写很多指令。我们的每一个项目都有不同的要求,所以我们要写不同的指令集来对应不同的项目
Target(目标) : prerequisites(依赖)
Command(命令)
eg:
app:main.o fun.o
gcc main.o fun.o -o app
main.o:main.c
gcc -c main.c -o main.o -I ./inc
fun.o:fun.c
gcc -c fun.c -o fun.o -I ./inc
#1.第一行即eg中的“app”为终极目标,下面的所有目标都是为了生成这个终极目标而编写
#2.第一行的依赖是指你的目标文件是和“依赖”有关系的,一个文件可以和很多个文件有依赖关系,用空格来分开,如:test.c test1.c test2.c
#3.当时间不对时,需要将时间调整正确之后才能使用 make 命令。
#4.makefile根据时间信息判断是否执行编译(目标文件与最终生成文件进行时间对比)。
#5.每个指令集中的目标,都可以是一个文件,也可以是一个标签,标签作为第一个会一直执行。标签不是实际的文件;(还没学到)
#6.每一个规则中的目标,不一定要有依赖。
#7.每一个规则,不一定非得有命令列表。
#8.每个规则中可以有多条命令规则,但是前面都得需要加 Tab 键。
好吧这么多规矩很容易看不懂,一定要每一步都自己实践一下
运行过程:
首先我们创建一个文件,这个文件的文件名就叫makefile/Makefile
默认的情况下,make 命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是 GNU 的 make 识别的。有另外一些 make 只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的 make 都支持“makefile”和“Makefile”这两种默认文件名。
寻找顺序:“GNUmakefile” > “makefile” > “Makefile(推荐)”
但是你可以指定执行某一个的MakeFile,使用make的 “-f” 和 “–file” 参数即可。比如:make -f Make.Linux 或 make --file Make.AIX
首先我们要先有一个自己的C语言文件来实操
touch test.c
然后在里面写上东西
然后用vim来写我们刚创建的makefile文件,写下图里的东西
图上代码的第一行的意思是:一个文件叫mytest,他完全依赖于test.c文件
第二行是说编译test.c文件生成的可执行文件为mytest
好了我们的指令集写好了,怎么使用?
在命令行界面输入make这一个单词,就执行了里面的命令,如下图所示:
你看,mytest是不是出现了。再把这个加进去
.PHONY:clean
clean:
rm -f mytest
然后输入:make clean
就删除了刚刚生成的文件
make clean两个单词整体是一个命令(不要像我一样傻傻的以为make命令是执行所有命令的,那样的话make不久无法实现指令分组了吗。。)
这个.PHONY是一个声明。有时候你的文件名可能和指令名字重复,比如你的当前目录下如果有一个叫clean的文件,Linux就不会执行你这条命令(在它眼里clean已存在,没必要执行),。PHONY的意义就是声明:我这里的clean是个命令,不是文件哦
在Linux下,这类命令的专业名词叫伪目标
当你重复输入了make指令,执行里面<生成文件>的命令,但是文件已经生成了。我们makefile主打的就是省事,比如我们前面提到的生成执行文件再修改源码,我们的makefile会判断源码最近修改时间和生成执行文件的时间谁更新,如果经过对比后我们makefile发现我们的源码修改过了,就会自动在旧文件上覆盖新的;如果没有改变源文件,则就会出现上图的提示:你的“mytest"取决于时间(五毛翻译)
输入
stat 文件名//查看文件属性
来认识一下makefile里面的内置符号:
$:取内容
@:目标文件
^:依赖文件列表
就像占位符,makefile会自己把代替的变量带进去的
注释用:#
依赖关系可以有好几组,a依赖b,b依赖c......这样子,依赖文件前必须是Tab键
在makefile下只要全部写上去就好,makefile是会自己整合这个依赖关系的
makefile/make会自动根据文件中的依赖关系,进行自动推导(入栈入栈入栈,出栈出栈出栈),帮助我们执行所有相关的依赖方法
tips:makefile支持乱序,但要把最终要形成的文件放在最前面
code.exe:code.o//在最上面
gcc code.o -o code.exe
code.o:code.s
gcc -c code.s -o code.o
code.s:code.i
gcc -S code.i -o code.s
code.i:code.c
gcc -E code.c -o code.i
执行一下
添加上清理的指令
.PHONY:clean
clean:
rm -f code.i code.s code.o code.exe
就可以删除刚刚生成的文件了
在makefile中支持定义变量,格式是这样的
变量名=变量内容
上图的意思和下图的意思其实差不多捏
这个就跟宏定义差不多,在改文件的时候可以一键替换,而且以后很多地方会用到比较方便
如果不想让它打印命令就可以在前面加个@
就像这样:打印的时候显示器就不会把你的命令再打印一遍
bin = test.exe
src = test.c
$(bin) :$(src)
@gcc -o $@ $^
.PHONY:clean
clean :
@rm -f $(bin)
如果想要打印提示信息,就加上下面那句:
bin = test.exe
src = test.c
$(bin) :$(src)
@gcc -o $@ $^
@echo "compiled $(src) to $(bin)..."
.PHONY:clean
clean :
@rm -f $(bin)
@echo "clean project"
总结一下:make的运行规则是:从上到下扫描,默认形成第一个目标文件(在默认情况下只执行一对依赖关系和依赖方法)
依赖关系就是,最终的文件按照依赖文件列表用依赖方法来形成可执行程序(make会根据makefile的内容,完成编译、清理工作,关系是一种联系,方法就是一种两者间的诉求
再看这张图是不是大部分问题都解决了
Target(目标) : prerequisites(依赖)
Command(命令)
eg:
app:main.o fun.o
gcc main.o fun.o -o app
main.o:main.c
gcc -c main.c -o main.o -I ./inc
fun.o:fun.c
gcc -c fun.c -o fun.o -I ./inc
#1.第一行即eg中的“app”为终极目标,下面的所有目标都是为了生成这个终极目标而编写
#2.第一行的依赖是指你的目标文件是和“依赖”有关系的,一个文件可以和很多个文件有依赖关系,用空格来分开,如:test.c test1.c test2.c
#3.当时间不对时,需要将时间调整正确之后才能使用 make 命令。
#4.makefile根据时间信息判断是否执行编译(目标文件与最终生成文件进行时间对比)。
#5.每个指令集中的目标,都可以是一个文件,也可以是一个标签,标签作为第一个会一直执行。标签不是实际的文件;(还没学到)
#6.每一个规则中的目标,不一定要有依赖。
#7.每一个规则,不一定非得有命令列表。
#8.每个规则中可以有多条命令规则,但是前面都得需要加 Tab 键。
缓冲区
前情提要:老式打字机在打完一行的时候,红色的是换行,蓝色的是回车
写完以后想再从左边打字,就要把纸往上拉,换新的一行;然后把打字的滑块滑到最左边,叫回车
在计算机里只想回车是\r,当\r存在时(也就是\r\n的时候,\n才表示换行),这个只想回车就代表着你可以在同一行一直刷新
那么缓冲区是什么?
缓冲区其实就是一块内存空间,将缓存区内容刷新前的东西都放到缓存区,然后再刷新到显示器上
程序要结束时一般要强制刷新缓冲区,而\n也可以率先你缓冲区,可以进行行刷新,就是刷新一行
缓冲区满了也会进行刷新
eg:
#include <stdio.h>
#include<unistd.h>
int main()
{
printf("hello world!!!\n");
sleep(3);
return 0;
}
//效果:先打印hello world,再休眠
#include <stdio.h>
#include<unistd.h>
int main()
{
printf("hello world!!!");
sleep(3);
return 0;
}
//先休眠三秒,再打印
上面的原理很好理解,下面的是为什么呢?程序是怎么运行的呢?
程序永远是自上而下的执行的,也就是说执行sleep的时候,已经执行完printf了;那执行完了我怎么没看见?
因为上面的有\n,进行了行刷新,把缓冲区的hello world打印出来了
而下面的没有行刷新,直到程序结束,才开始刷新缓冲区,所以是先睡觉再打印。
程序为什么还要有缓冲区和刷新这两个概念?
缓冲区是为了提高效率,向外刷新的次数越少,效率就越高
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt = 9;
while (cnt >= 0)
{
printf("倒计时:%d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
return 0;
}
//一个可以倒计时的代码,应用刚才学的缓冲区概念
fflush
fflush()函数:更新缓存区。
头文件:#include<stdio.h>
函数定义:int fflush(FILE *stream);
函数说明:调用fflush()会将缓冲区中的内容写到stream所指的文件(如果文件是显示器,就可以写在显示器上)中去.若stream为NULL,则会将所有打开的文件进行数据更新。
其实C程序在启动的时候会默认启动三个输入输出流,他们分别是:
extern FILE* stdin; //键盘
extern FILE* stdout; //显示器,可以打印到显示器上
extern FILE* stderr; //标准错误,收集错误,也属于显示器
为什么一定要打开他们三个输入输出流?
首先,打开文件是编译器和系统帮忙做的,在源代码编译时添加了一部分代码,打开键盘显示器上的代码,计算机的作用是计算,要让用户把自己的数据输入进来,经过计算后显示到显示器上让人看到结果(方便用户输入输出)
不回显说明:键盘和你交互了,没把数据给显示器
字符设备
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt = 10;
while (cnt >= 0)
{
printf("倒计时:%d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
return 0;
}
按上面的代码,显示器就会显示为10,90,80,70......为什么会这么显示?
因为我们之前说显示器是字符设备,上面的东西默认打出来都是字符。在\r行刷新时,把"0"前面的东西刷新了,0没刷新,所以它作为一个字符一直放在那里。如果不是0是别的数字,也是会这么一直放在显示器上面的
这么写就可以正常显示
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt = 10;
while (cnt >= 0)
{
printf("倒计时:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
return 0;
}
进度条
先创建一个新目录,我们整点新活
mkdir proccess
cd proccess
touch Processbar.c
touch Processbar.h
touch Main.c
touch makefile
这是一个进度条,有图形化显示进度,有百分比,我们也写一个
Processbar.h
#pragma once //防止头文件重复展开
#include<stdio.h>
void ProcBar();
Processbar.c
#include"Processbar.h"
#include<string.h>
#include<unistd.h>
#define length 101
#define Style '#'
const char* lable="|/-\\";
void ProcBar()
{
char bar[length];
memset(bar,'\0',sizeof(bar));
int len=strlen(lable);
int cnt=0;
while(cnt<=100)
{
printf("[%-100s][%3d%%][%c]\r",bar,cnt,lable[cnt%len]);
fflush(stdout);
bar[cnt++]=Style;
usleep(20000);
}
printf("\n");
return;
}
Main.c
#include"Processbar.h"
int main()
{
ProcBar();
return 0;
}
makefile
processbar:Main.c Processbar.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f processbar
使用的时候记得把你的shell全屏打开,不然就会
这是正常的捏,是动态的
进度条是要跟随一些操作进行的,比如下载时候的进度条
好了先休息了