所向披靡のmakefile

在VS里敲代码,只需要Fn+F5就可以直接运行勒,在Linux下敲代码却要即敲命令还要用编辑器还要用编译器,那在Linux下有没有能帮我们进行自动化组建的工具呢?

当然有,超级巨星:makefile!!!!

大致认识

首先创建一个叫makefile的文件(首字母大小写都可以哦)

touch makefile

21348046d1db455c9b9ca28a2721269f.png

用vim编辑一下这个文件:

mytest:test.c
   gcc -o mytest test.c

这个的含义是,构建一个叫mytest的文件,这个文件依赖的是test.c 

 d30fc6c477574ed39fceaec7a2b2a0f9.png

 make一下就自动编译好啦:

2d721f6e88c644288371f747b07df086.png 继续编辑Makefile,清理生成的临时文件:

.PHONY:clean 
clean:
rm -f mytest

417b99b62d8f4719901ec50e14b9cfd5.png

这样就清理掉了:

b45d00e8297a46fd94abb6dbb421e44b.png

依赖文件列表的文件按照空格为分割:

test.c test1.c test2.c

依赖文件前必须是Tab键 

关系就是,最终的文件按照依赖文件列表用依赖方法来形成可执行程序(make会根据makefile的内容,完成编译、清理工作)

我们应该怎么理解依赖关系和依赖方法呢?

来听个小故事:墨墨酱是一名大学生,到了月末,墨墨酱摸摸自己的口袋,居然只剩十块钱勒(实在不行啃树皮吃吧,芝士就是力量),于是墨墨酱开始紧急避险,拨打电话号码,电话接通后,刚说了一声“爸”,电话就匆匆挂(墨墨酱在告知她上学需要依赖父母,虽然要钱没要成,这个过程叫表明依赖关系),但是只表明依赖关系是肯定要不到钱钱的!还要表明你的诉求:想要生活费(依赖方法)

运行原理

make是一个命令,makefile是一个文件

make的运行规则是:从上到下扫描,默认形成第一个目标文件(在默认情况下只执行一对依赖关系和依赖方法)

.PHONY后面修饰的方法总是被执行(在Linux中,如果编译的已经是最新文件,就会拒绝你的命令,因为已经是最新的了,没必要再编一遍)

e4a2a25f74804c3bbd8e356c345b18d6.png

我们希望清理总是被执行,所以上面那样写  

那么为什么makefile对最新的可执行程序,默认不想重新形成呢?

因为有很多程序运行起来肯定不止一个源文件,如果只改动了某个源文件的一小部分,就要把所有的程序重新编译,则效率很低(所以为了提高编译效率就这样做啦)

那makefile是怎么做到的呢?

为什么makefile知道我的程序被编译了呢?

因为会对比时间呀

还要从文件的属性谈起,还记得那三个时间吗?

f873e3c857034fa7a6086e1b9cba974d.png

常规开发过程:

编写源代码--->编译(对比可执行文件的最近修改时间和源文件的最近修改时间谁更新)

touch同名文件可修改对应时间,有的时间修改的规则不一样,可能重新构建一下就能正常运行了

2da578545d184ae29beefcbb13a0493a.png

在makefile里会自动进行目标文件替换,来认识一下makefile里面的内置符号:

$:取内容

@:目标文件   

^:依赖文件列表

在makefile里注释用#

0aebe2e11eb3417482866812db91c316.png

每组依赖关系匹配着对应的依赖方法 ,上面的东西写全了之后是这样的: 

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

763c1a767fca49bc96b2c95a803e6b51.png

添加上清理的东西:

.PHONY:clean
clean:
    rm -f code.i code.s code.o code.exe

85287f57e11b4c4baf61762f10a0f5dc.png

自己构建的makefile就是好用:

002538e5c07840deb4bb761ba9e22fa3.png以上内容总结一下就是:makefile/make会自动根据文件中的依赖关系,进行自动推导(入栈入栈入栈,出栈出栈出栈),帮助我们执行所有相关的依赖方法 

tips:makefile支持乱序,但要把最终要形成的文件放在最前面

在makefile中支持定义变量,格式是这样的:

变量名=变量内容

 于是上面的内容改写一下:

bin=code.exe
src=code.c

$(bin):$(src)
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f $(bin)

fda6ce5350db4f6695e079171c226bec.png

这个就跟宏定义差不多,在改文件的时候可以一键替换,而且以后很多地方会用到比较方便

如果不想让它打印命令就可以在前面加个@

就像这样:

bin = test.exe
src = test.c
$(bin) :$(src)
        @gcc -o $@ $^

.PHONY:clean
clean :
        @rm -f $(bin)

40e5927a7e6e490ca117010fcceeaf5a.png

如果想要其打印提示信息的话就是:

bin = test.exe
src = test.c
$(bin) :$(src)
        @gcc -o $@ $^
        @echo "compiled $(src) to $(bin)..."
.PHONY:clean
clean :
        @rm -f $(bin)
        @echo "clean project"

901a82cdf19b4f5ba86cc0935a52c014.png

未知进度条 

然后来隆重介绍下Linux的进度条小程序,首先介绍两个前置知识(超绝小技巧):

回车和换行

回车和换行是什么关系呢?

我们在C中学习的\n是什么意思捏?当然是回车换行!

但是你看,(超绝老式键盘已经在提示你了)它是这样的:

3690fb6760fd46e2b52ed2f6bc715da3.jpeg

它长得像个楼梯:这代表先换行再回车呀,这是两个动作

红色的是超绝换行,蓝色的是在打字,打字之后可以拨回来,那就是超绝回车

只想回车是\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;
}

猜猜这两段输出都是什么? 

 7b5acd9400bb4fde9d03cb48ca6295f5.jpeg

让我来公布下正确答案:

带\n的那个肯定是先输出再睡呀

不带的那个是先睡后输出

efdba24790fa40e2a70f9823480c90db.jpeg

那这样的带不带\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一下:

111abd88cdce4286bbc66ba2ab882c0a.png fflush是把特定文件流中的数据刷新

int fflush(FILE *stream);

这句里面的超绝FILE*是什么意思?

很简单:Linux的设计理念:一切皆文件,那我拿显示器当文件不过分吧?

其实C程序在启动的时候会默认启动三个输入输出流,他们分别是:

ae1c00a5a7d34edcb91d8c5cc41ab7ce.png

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

e39eff9acfca4a64aff6b690045087bc.png

我们理想中的进度条是这样的:

能显示百分比,有进度

在实现的时候我们就用#来代表加载程度,末尾添加\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.c
	gcc -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;	//100M
	double current=0.0;
	double bandwidth = 1024*1024*1.0;	//带宽:1M
	
	printf("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.c
	gcc -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;       //带宽:1M

void 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.c
        gcc -o $@ $^

.PHONY:clean
clean:
        rm -f processbar

超绝进度条告一段落,下次见~

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值