Makefile快速入门

介绍

这里以一个例子来演示利用Makfile进行多文件编译

一共有四个源程序:main.cpp,printhello.cpp,factorial.cpp,functions.h

首先是main.cpp内容

#include<iostream>
#include"functionals.h"

using namespace std;

int main()
{
    printhello();

    cout << "This is main:" << endl;
    cout << "The factorial of 5 is: " << factorial(5) << endl;
    return 0;
}

调用两个函数

printhello.cpp内容

#include<iostream>
#include"functionals.h"

using namespace std;

void printhello()
{
    int i;
    cout << "Hello World!" << endl;
}

定义了一个函数,输出hello

factorial.cpp内容

#include"functionals.h"

int factorial(int n)
{
    if(n == 0 || n == 1){
        return 1;
    }
    else{
        return n * factorial(n - 1);
    }
}

定义了一个函数,用于计算阶乘

functions.h内容

#ifndef _FUNCTIONALS_H_
#define _FUNCITIONALS_H_
void printhello();
int factorial(int n);
#endif

在这个文件里写了函数的声明

首先是可以在命令行直接用g++编译:

g++ -o main .\factorial.cpp .\printhello.cpp .\main.cpp
.\main.exe

Hello World!

This is main:

The factorial of 5 is: 120

但如果源文件很多,这样编译要花费很多时间

一个省时间的方式就是我们可以逐个编译,修改了哪个文件就编译哪个文件(只编译不链接),其他文件不编译

g++ .\main.cpp -c
g++ .\factorial.cpp -c
g++ .\printhello.cpp -c

这样生成了三个.o文件,然后用命令g++ *.o -o main链接在一起。

不知道为什么,Windows这样写会报错g++: error: *.o: Invalid argumentg++: error: *.o: Invalid argument,不能用通配符,所以我每个都写出来的g++ .\factorial.o .\printhello.o .\main.o -o main(Windows不能用通配符这种,需要单独写出,Linux可以)

那么这样会带来一个问题,当文件特别多的时候,每次手动去输入这个命令会特别麻烦,所以在命令行输入g++编译的这些命令,完全可以写到一个脚本文件里,这个脚本文件有个固定的格式,叫Makefile,注释用 #。

version 1

## version 1
hello: main.cpp printhello.cpp factorial.cpp
	g++ -o hello main.cpp printhello.cpp factorial.cpp

前面hello表示生产的可执行程序叫hello,冒号后面的文件表示:hello的生成依赖于后面那些文件

g++前面有个tab,不能用空格,表示使用后面这个命令生成上面的hello文件

使用的方法就是在命令行输入make,make命令会自动去找当前目录下Makefile文件,如果名字不是Makefile,可以用命令make -f Makefile

生成后,如果再make一次,会提示:make: 'hello' is up to datemake: “hello”已是最新。)表示hello已经很新了,就是hello比那三个cpp文件都新,是在三个文件后生成的,没必要再生成了,除非对其中一个文件做了修改,那么被修改的文件日期新于hello,这时候再make,它会查规则,发现它所依赖的文件在hello生成后被修改了,就会用第二行命令重新生成。(Windows不会检查,Linux会检查)

缺点:如果文件特别多,每次编译时间很长

version 2

CXX指定编译器

TARGET指定目标

TARGET这个变量依赖于OBJ这些文件,如果OBJ相比TARGET更新的话,那么就会用下面命令

make后,Makefile去读第一个碰到的目标TARGET,就是要生成的目标,因为hello不存在,就要去生成,生成要依赖OBJ文件,而OBJ文件又去找依赖的cpp文件

如果某个文件修改了它只会去编译更改了的cpp文件

# version 2
CXX = g++
TARGET = hello
OBJ = main.o printhello.o factorial.o

$(TARGET): $(OBJ)
	$(CXX) -o $(TARGET) $(OBJ)

main.o: main.cpp
	$(CXX) -c main.cpp

printhello.o: printhello.cpp
	$(CXX) -c printhello.cpp

factorial.o: factorial.cpp
	$(CXX) -c factorial.cpp

version 3

加了一个编译选项CXXFLAGS = -c -Wall(Warning all)就是所有的warning都显示出来

不同的一个地方是加了个$@,其实就是冒号前面的东西,就是$(TARGET)$^是冒号后面的东西,就是$(OBJ)这样又少写了一些变量

刚才每个cpp都写了规则生成.o,这里写了个统一的规则%.cpp就是每个cpp文件,$<是指所依赖的,^往上指是指所有的,<往左指是指第一个,在这里只有一个,所以一样。

clean那两行,有个目标叫clean,要生成的话,不管依赖是什么,删除所有.o文件和目标文件。

当文件夹下有一堆.o文件和目标文件,可以用make clean命令,make命令就回去Makefile中找clean目标,然后执行命令。在很多集成开发环境中有个clean,做的就是这件事。

那为什么要有个隐藏的PHONY呢?就是为了防止有个文件叫clean。因为当恰好有个叫clean的文件时,会产生歧义,文件已经有了,就不会产生这个文件了,会提示已经是最新了。这时候就要用.PHONY: clean.PHONY文件永远不会存在,它依赖clean,clean就会删除所有那些文件。

这个版本更加模块化,后面如果有新的文件,只要在OBJ那行写上即可,例如加上file.o

# version 3
CXX = g++
TARGET = hello
OBJ = main.o printhello.o factorial.o

CXXFLAGS = -c -Wall

$(TARGET): $(OBJ)
	$(CXX) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) $< -o $@

.PHONY: clean

clean:
	rm -f *.o $(TARGET)

会出现一条警告,因为加了-Wall,可以在CXXFLAGS加一些选项,修改起来很方便

version 4

前面还有个麻烦的,要写文件名,这个版本就不用写了

SRC那行是所有当前目录下cpp都放到SRC变量里,就像ls一样,把所有cpp扩展名的文件,满足这个条件的都放到里面。

做一个path路径的替换,把cpp全换成o,就得到了OBJ列表。

# version 4
CXX = g++
TARGET = hello
SRC = $(wildcard *.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))

CXXFLAGS = -c -Wall

$(TARGET): $(OBJ)
	$(CXX) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) $< -o $@

.PHONY: clean

clean:
	rm -f *.o $(TARGET)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值