在VS里敲代码,只需要Fn+F5就可以直接运行勒,在Linux下敲代码却要即敲命令还要用编辑器还要用编译器,那在Linux下有没有能帮我们进行自动化组建的工具呢?
当然有,超级巨星:makefile!!!!
大致认识
首先创建一个叫makefile的文件(首字母大小写都可以哦)
touch makefile
用vim编辑一下这个文件:
mytest:test.cgcc -o mytest test.c
这个的含义是,构建一个叫mytest的文件,这个文件依赖的是test.c
make一下就自动编译好啦:
继续编辑Makefile,清理生成的临时文件:
.PHONY:clean
clean:
rm -f mytest
这样就清理掉了:
依赖文件列表的文件按照空格为分割:
test.c test1.c test2.c
依赖文件前必须是Tab键
关系就是,最终的文件按照依赖文件列表用依赖方法来形成可执行程序(make会根据makefile的内容,完成编译、清理工作)
我们应该怎么理解依赖关系和依赖方法呢?
来听个小故事:墨墨酱是一名大学生,到了月末,墨墨酱摸摸自己的口袋,居然只剩十块钱勒(实在不行啃树皮吃吧,芝士就是力量),于是墨墨酱开始紧急避险,拨打电话号码,电话接通后,刚说了一声“爸”,电话就匆匆挂(墨墨酱在告知她上学需要依赖父母,虽然要钱没要成,这个过程叫表明依赖关系),但是只表明依赖关系是肯定要不到钱钱的!还要表明你的诉求:想要生活费(依赖方法)
运行原理
make是一个命令,makefile是一个文件
make的运行规则是:从上到下扫描,默认形成第一个目标文件(在默认情况下只执行一对依赖关系和依赖方法)
.PHONY后面修饰的方法总是被执行(在Linux中,如果编译的已经是最新文件,就会拒绝你的命令,因为已经是最新的了,没必要再编一遍)
我们希望清理总是被执行,所以上面那样写
那么为什么makefile对最新的可执行程序,默认不想重新形成呢?
因为有很多程序运行起来肯定不止一个源文件,如果只改动了某个源文件的一小部分,就要把所有的程序重新编译,则效率很低(所以为了提高编译效率就这样做啦)
那makefile是怎么做到的呢?
为什么makefile知道我的程序被编译了呢?
因为会对比时间呀
还要从文件的属性谈起,还记得那三个时间吗?
常规开发过程:
编写源代码--->编译(对比可执行文件的最近修改时间和源文件的最近修改时间谁更新)
touch同名文件可修改对应时间,有的时间修改的规则不一样,可能重新构建一下就能正常运行了
在makefile里会自动进行目标文件替换,来认识一下makefile里面的内置符号:
$:取内容
@:目标文件
^:依赖文件列表
在makefile里注释用#
每组依赖关系匹配着对应的依赖方法 ,上面的东西写全了之后是这样的:
code.exe:code.ogcc code.o -o code.exe
code.o:code.sgcc -c code.s -o code.o
code.s:code.igcc -S code.i -o code.s
code.i:code.cgcc -E code.c -o code.i
添加上清理的东西:
.PHONY:clean
clean:rm -f code.i code.s code.o code.exe
自己构建的makefile就是好用:
以上内容总结一下就是:makefile/make会自动根据文件中的依赖关系,进行自动推导(入栈入栈入栈,出栈出栈出栈),帮助我们执行所有相关的依赖方法
tips:makefile支持乱序,但要把最终要形成的文件放在最前面
在makefile中支持定义变量,格式是这样的:
变量名=变量内容
于是上面的内容改写一下:
bin=code.exe
src=code.c$(bin):$(src)gcc -o $@ $^
.PHONY:clean
clean:rm -f $(bin)
这个就跟宏定义差不多,在改文件的时候可以一键替换,而且以后很多地方会用到比较方便
如果不想让它打印命令就可以在前面加个@
就像这样:
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"
未知进度条
然后来隆重介绍下Linux的进度条小程序,首先介绍两个前置知识(超绝小技巧):
回车和换行
回车和换行是什么关系呢?
我们在C中学习的\n是什么意思捏?当然是回车换行!
但是你看,(超绝老式键盘已经在提示你了)它是这样的:
它长得像个楼梯:这代表先换行再回车呀,这是两个动作
红色的是超绝换行,蓝色的是在打字,打字之后可以拨回来,那就是超绝回车
只想回车是\r,当\r存在时(也就是\r\n的时候,\n才表示换行)
缓冲区
缓冲区是什么在哪里怎么用?
缓冲区其实就是一块内存空间
printf中的内容拷贝到缓冲区,再将相应的数据刷新到显示器上
程序要结束时一般要自动强制冲刷缓冲区,而\n也可以刷新缓冲区(行刷新),缓冲区满了也会进行刷新
请看码:
#include <stdio.h>
#include<unistd.h>
int main()
{printf("hello world!!!\n");sleep(3);return 0;
}
#include <stdio.h>
#include<unistd.h>
int main()
{printf("hello world!!!");sleep(3);return 0;
}
猜猜这两段输出都是什么?
让我来公布下正确答案:
带\n的那个肯定是先输出再睡呀
不带的那个是先睡后输出
那这样的带不带\n的差别是什么呢,是不是函数运行的先后不同呢?又是哪个函数先运行呢?
首先明确一点:程序永远都是从上往下执行
那也就相当于执行sleep的时候早就将printf执行完了
既然printf都给我打印字符串了,那它放在哪了呢?
凭什么不让我看???
这个消失的它(bushi)是被放在了缓冲区,程序要结束时一般要自动强制冲刷缓冲区,所以在第二个例子中是先睡觉后打印(由于缓冲区没有被刷新,所以先睡,程序结束之后在显示器上显示出来)而在第一个例子中则是有\n缓冲区会自动刷新,所以是先输出再睡
至于为什么要有缓冲区这样一个设定捏?
我给你输啥你就显示啥得了呗,非要这样拖着我一下,嗯?
缓冲区的存在是为了提高效率,向外刷新的次数越少,那效率就会越高(这就跟快递小哥送快递一样,肯定是一坨一坨送快呀,总不能一颗一颗送吧)
要为外卖行业添砖JAVA啊!看我超绝锻炼车技!我要让你们都吃上超绝热乎饭!
这就设计的很绝呀,在考虑效率的同事顾及用户的需求,直接从记忆成本变成理解成本了
“老师我想回去覆盖”
“害砸你实现倒计时啦!”
#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是什么?
布吉岛,man一下:
fflush是把特定文件流中的数据刷新
int fflush(FILE *stream);
这句里面的超绝FILE*是什么意思?
很简单:Linux的设计理念:一切皆文件,那我拿显示器当文件不过分吧?
其实C程序在启动的时候会默认启动三个输入输出流,他们分别是:
extern FILE* stdin; //键盘
extern FILE* stdout; //显示器
extern FILE* stdin; //显示器
一般打印出的消息通过stdout打印到显示器上,那为什么程序启动默认帮我打开这三个流呢(标准输入,标准输出,标准错误),首先,打开文件是编译器和系统帮忙做的,在源代码编译时添加了一部分代码,打开键盘显示器上的代码,计算机的作用是计算,要让用户把自己的数据输入进来,经过计算后显示到显示器上让人看到结果(方便用户输入输出)
为什么要进行行刷新呢?
肯定是因为方便啊,就这样
如果这样捏?
#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;
}
由于在显示器(字符设备)上显示的东西都是字符,所以由于超绝字符显示,就有一只超级棒的0挥之不去了,显示出来是10,90,80,70......
如果想要其正常显示,就可以这样:
#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;
}
stdin 超绝键盘
stdout,stderr 超绝显示器
同样的数据给给给,等于两份
不回显:键盘和你交互了,没把数据给显示器
超绝进度条
搞一些东西:
mkdir proccess
cd proccess
touch Processbar.c
touch Processbar.h
touch Main.c
touch makefile
我们理想中的进度条是这样的:
能显示百分比,有进度
在实现的时候我们就用#来代表加载程度,末尾添加\0
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.cgcc -o $@ $^.PHONY:clean
clean:rm -f processbar
以上就是进度条的简单实现了,但我们需要结合使用场景考虑,肯定不能是直接就单纯进度条,比如介绍一个下载的场景:
下载进度条
Processbar.h
#pragma once
#include<stdio.h>
void ProcBar(double total,double current);
Processbar.c
#include"Processbar.h"
#include<string.h>
#include<unistd.h>
#define length 101
#define Style '#'//version 2
const char* lable="|/-\\";
void ProcBar(double total,double current)
{char bar[length];memset(bar,'\0',sizeof(bar));int len=strlen(lable);int cnt=0;double rate=(current*100.0)/total;int loop_count =(int)rate; while(cnt<=loop_count){printf("[%-100s][%.1lf%%][%c]\r",bar,rate,lable[cnt%len]);fflush(stdout);bar[cnt++]=Style;//usleep(20000);}return;
}
Main.c
#include"Processbar.h"
#include <unistd.h>
//download
void download()
{double filesize = 100*1024*1024*1.0; //100Mdouble current=0.0;double bandwidth = 1024*1024*1.0; //带宽:1Mprintf("download begin,current:%lf\n",current); while(current <= filesize){ProcBar(filesize,current); //根据具体数据判断进度条长度//从网络中获取数据current += bandwidth;usleep(100000); }printf("\ndownload done,filesize: %lf\n",filesize);
}
int main()
{download();//ProcBar(100.0,56.9);//ProcBar(100.0,1.0);//ProcBar(100.0,99.9);//ProcBar(100.0,100);return 0;
}
makefile
processbar:Main.c Processbar.cgcc -o $@ $^.PHONY:clean
clean:rm -f processbar
多份文件进度条
Processbar.h
#pragma once
#include<stdio.h>
typedef void(*callback_t)(double,double);
void ProcBar(double total,double current);
Processbar.c
#include"Processbar.h"
#include<string.h>
#include<unistd.h>
#define length 101
#define style '#'//version 2
const char* lable="|/-\\";
void ProcBar(double total,double current)
{char bar[length];memset(bar,'\0',sizeof(bar));int len=strlen(lable);int cnt=0;double rate=(current*100.0)/total;int loop_count =(int)rate;while(cnt<=loop_count){// printf("[%-100s][%.1lf%%][%c]\r",bar,rate,lable[cnt%len]);// fflush(stdout);bar[cnt++]=Style;//usleep(20000);
}printf("[%-100s][%.1lf%%][%c]\r",bar,rate,lable[cnt%len]);fflush(stdout);return;
}
Main.c
#include"Processbar.h"
#include <unistd.h>
//download
double bandwidth = 1024*1024*1.0; //带宽:1Mvoid download(double filesize,callback_t cb)
{double current=0.0;printf("download begin,current:%lf\n",current);while(current <= filesize){cb(filesize,current); //根据具体数据判断进度条长度//从网络中获取数据usleep(100000);current += bandwidth;}printf("\ndownload done,filesize: %lf\n",filesize);
}
int main()
{download(100*1024*1024,ProcBar);download(1*1024*1024,ProcBar);download(200*1024*1024,ProcBar);download(400*1024*1024,ProcBar);download(1000*1024*1024,ProcBar);download(10*1024*1024,ProcBar);download(50*1024*1024,ProcBar);//ProcBar(100.0,56.9);//ProcBar(100.0,1.0);//ProcBar(100.0,99.9);//ProcBar(100.0,100);return 0;
}
makefile
processbar:Main.c Processbar.cgcc -o $@ $^.PHONY:clean
clean:rm -f processbar
超绝进度条告一段落,下次见~