本文章的背景:
因为最近想每天上Leetcode 刷每日一题,同时又想练习一下vim的使用(立志成为一个vimer),因此打算在云服务器上做算法编程。
刷了几天题后,发现每次使用gcc 来编译并执行我的算法稍微有点麻烦。而且,经过几天的编程,我也积攒了一些通用的api,为了方便在之后的刷题过程中能够丝滑地使用这些api方法,我新建了一个目录 “api” 来存放它们.
好了,现在因为涉及到多文件编译,更没有理由不使用makefile来解放自己了。于是就有了接下来的尝试。
任务背景:
每日一题管理目录结构如下:
顶层目录: everyday
顶层目录下的文件与子目录:
- 2023xxxx.c (每日一题算法实现)
- api目录
- srcs目录(存放往日的算法实现.c文件)
- outs目录(存放往日算法的可执行文件.out)
api目录下的文件与子目录:
- incs目录(存放实用api方法的.h文件)
- srcs目录(存放实用api方法的.c文件)
为什么手动 gcc 来编译会很困扰?
情景介绍:
每日一题源文件20231229.c 中调用了api方法 “scanf_int_array()” 因此 include 了 api 文件 “input_datas.h” 。如下:
#include<stdio.h>
#include<stdlib.h>
#include"input_datas.h"
int buyChoco(int* prices, int pricesSize, int money);
int main()
{
int * int_array = NULL;
int arraySize = 0;
//scanf_int_array方法 来自 input_datas.h
//scanf_int_array 封装了获取一个一维整数数组过程
int_array = scanf_int_array();
arraySize = int_array[0];
int ans = buyChoco(int_array+1,arraySize,10);
printf("剩余金额 = %d\n",ans);
free(int_array);
return 0;
}
int buyChoco(int* prices, int pricesSize, int money)
{
/*
** 这里具体实现不重要...
*/
}
此时如果要让 20231229.c 正常编译生成可执行文件,其编译指令应该这样写:
gcc -I api/incs -o 20231229.out 20231229.c api/srcs/*.c
# -I api/incs 告诉编译器要去 api/incs 目录下搜索头文件。不指定的话会找不到方法 scanf_int_array 的
# 依赖的.c 文件也要同时编译
这条指令已经有点长了,而且大部分内容是固定的,不会随日期的改变而改变。如果一直手动gcc,不得不说会做很多无用功。
makefile 的实现
makefile 可以代替我们执行一系列的编译指令,并生成目标文件。makefile的语法并不复杂,但是要了解一下makefile的基本规则。这方面可以阅读《gnu makefile手册》。(英文版,慎入!)
先澄清下我们想要的最终效果:
我们希望 在往后编译每日一题的.c文件时,我只需要输入不固定的部分——算法.c文件名,其他部分可以通过简单的make 指令代替。
这也意味着,我们在执行make指令时,必然是有一个入参变量的。为了方便,我们取名为 T (target的缩写)
这上述的情景中,我最终书写的makefile规则如下:
1 INCDIR=api/incs #api头文件位置
2 SRCDIR=api/srcs #api源文件位置
3 TARGET=$(subst .c,.out,$(T)) #subst 可以将字符串 $(T) 的一部分替换为另一字串,这里是把.c 替换为了 .out,相当于改了后缀名
4 all:$(TARGET)
5 $(TARGET):$(T) $(SRCDIR)/*.c
6 @echo "目标文件为:$(TARGET)"
7 gcc -I $(INCDIR) -o $@ $^ # $@ 表示目标部分, $^ 表示所有的依赖部分
8 @echo "编译完成"
9
10 .PHONY:clean
11 clean:
12 rm -f ./*.out
13 @echo "删除.out文件"
此后,我只需要输入如下指令:
make T=20231229.c
就可以完成对 每日一题的编译,生成 .out文件。
结尾
文章附件中有 GNU make中文手册v3.80.rar
承认自己的无知,乃是开启智慧的大门。慢慢学习,慢慢进步吧。