一、准备阶段
1、目录结构
2、文件内容
math.h
#ifndef _MATH_H_
#define _MATH_H_
/**
* 计算阶乘
* @param n [需要计算阶乘的数]
* @return [阶乘]
*/
int factorial(int n);
#endif
math.c
#include <math/math.h>
/**
* 计算阶乘
* @param n [需要计算阶乘的数]
* @return [阶乘]
*/
int factorial(int n){
if(n == 1 || n == 0){
return 1;
}
return n*factorial(n - 1);
}
grapnic.h
#ifndef _GRAPNIC_H_
#define _GRAPNIC_H_
/**
* 打印阶乘数表
* @param n [最大要计算的数]
*/
void print(int n);
#endif
grapnic.c
#include <stdio.h>
#include <math/math.h>
#include <grapnic/grapnic.h>
/**
* 打印阶乘数表
* @param n [最大要计算的数]
*/
void print(int n){
//循环打印
for (int i = 0; i <= n; ++i){
//按照格式打印
printf("!%d = %d\n", i, factorial(i));
//每行4列,骨换行
if (i % 5 == 0){
puts("**************************\n");
}
}
}
main.c
#include <grapnic/grapnic.h>
int main(int argc, const char *argv[]){
print(20);
return 0;
}
二、编译概览
由图可知,编译过程分为四个阶段,分别为编译预处理、编译、汇编和链接。每个阶段都有自己的作用,其中编译预处理和链接这两个过程比较复杂。
三、编译预处理
编译预处理器本质上做了两件事情,即宏替换和文件包含。不会做语法检查
1、条件编译
条件编译的本质是根据条件来选择需要保留的代码块,语法如下:
含义:如果表达式的值为真,则保留代码块1,否则保留代码块2
#if 表示式
代码块1
[#else
代码块2]
#endif
含义:如果宏被定义过,则保留代码块1,否则保留代码块2
#ifdef 宏名
代码块1
[#else
代码块2]
#endif
含义:如果宏没有被定义,则保留代码块1,否则保留代码块2
#ifndef 宏名
代码块1
[#else
代码块2]
#endif
2、宏替换
宏替换的本质就是将宏名替换为指定的字符串。从宏定义开始到宏取消,这段宏名都会被替换。
含义:定义无参数的宏
#define 宏名 字符串
含义:定义有参数的宏
#define 宏名(参数列表) 字符串
含义:取消定义过的宏
#undef 宏名
3、文件包含
文件包含的本质就是把指定的文件复制到文件包含指令所在的位置。
含义:在系统指定路径下寻址相应的头文件
#include <file>
含义:首先在当前文件下寻找头文件,再到系统指定路径下寻址相应的头文件
#include "file"
注意:编译时,利用-I dir
来指定要搜索的路径。
四、编译
编译就是把预处理好的文件翻译成汇编源文件,会做语法检查。
仔细观察,可以发现,在编译过程中,并不需要函数、变量的定义,只需要函数、变量的声明。因此声明的作用便是告知编译器数据的宽度、全局符号名。
五、汇编
汇编就是把汇编源文件中的汇编指令翻译成机器码,并按照规定的格式输出。
六、链接
将需要用到的二进制文件和库文件链接到一起,并将符号地址替换为虚拟地址,并按照指定的格式输出。