C语言编译步骤
- 预处理
- 编译
- 汇编
- 链接
什么是预处理
预处理就是在源文件(如.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理程序自动完成,当源文件在编译时,编译器会自动调用预处理指令的解析,预处理指令解析完成才能进入下一步的编译过程
为了能够方便看到编译细节,使用以下命令
gcc 源文件 -E -o 程序名[.后缀]
预处理的功能
宏定义
作用域
- fine命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常,#define命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。
- 可以用#undef命令终止宏定义的作用域。
- 宏定义时,可以引用已定义的宏名,可以层层置换。
- 对程序中用双撇号括起来的字符串内的字符,即使与宏名相同,也不进行置换。
- 宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只作字符替换,不分配内存空间。
不带参数的宏定义
- 语法:#define 宏名 常量数据的宏定义
- 预处理:此时的预处理制作数据替换,不做类型检查
- 注意:
- 定义的宏不占内存空间,还没有到编译环节的时候,就已经被替换成了宏中的常量数据
- 在预编译时将宏名替换成字符串的过程称为"++宏展开++"。
案例:
cs
#include <stdio.h>
#define PI 3.1415926
void main()
{
float l,s,r,v;
printf("input radius:");
scanf("%f",&r);
l=2.0*PI*r;
s=PI*r*r;
v=4.0/3*PI*r*r*r;
printf("l=%10.4f\ns=%10.4f\nv=%10.4f\n",l,s,v);
}
带参数的宏定义
- 作用:不是进行简单的字符串替换,还要进行参数替换。
- 语法:#define 宏名(参数列表)参数表达式
例题:
#define multi(a,b) a * b
cs
/**
* 宏定义-带参数
*/
#include <stdio.h>
#define MULTI(a,b) a * b
int main()
{
int result = MULTI(7+2,3);
printf("%d\n",result);// 27 9
return 0;
}
- 对带参数的宏展开只是将语句中的宏名后面括号内的实参字符串代替#define 命令行中的形参。
- 在宏定义时,在宏名与带参数的括弧之间不应加空格,否则将空格以后的字符都作为替代字符串的一部分。
带参数的宏和函数的区别:
- 函数调用时,先求出实参表达式的值,然后代入形参。而使用带参的宏只是进行简单的字符替换。
- 函数调用是在程序运行时处理的,为形参分配临时的内存单元。而宏展开则是在编译前进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有"返回值"的概念。
- 对函数中的实参和形参类型要求一致。而宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定的字符串即可。宏定义时,字符串可以是任何类型的数据
文件包含
概念:
所谓"文件包含"处理是指一个源文件可以将另外一个源文件的全部内容包含进来。
预处理:
此时的预处理将文件中的内容替换,文件包含指令
包含方式:
- 第一种:#include <xxxx.h>
- 系统会到标志库头文件目录(Linux下/usr/include)查找包含的文件
- 第二种:#include "xxxx.h"
- 在当前的工程路径下查找,如果未找到,任然会在标准库头文件目录查找
案例:
algorithm.h
cs
/**
* 实现数组元素的累加计算
*/
int sum(const int *p,int len)
{
int sum = 0;
register int i = 0;
for(;i < len; i++)
{
sum += *(p+i);
}
return sum;
}
app.c
cs
// #include <stdio.h>
// 引入自定义的头文件
#include "algorithm.h"
// 如果有n多个外部函数,难道都要一个个的使用extern进行声明?
// 引入外部函数声明
// extern int sum(const int*,int);
int main()
{
int arr[5] = {12,33,14,55,34};
int res = sum(arr,5);
printf("数组累和结果是:%d\n",res);
return 0;
}
编译命令:
gcc algorithm.c app.c -o app // 又包含关系的c文件要一起编译
说明:
(1) 一个#include命令只能指定一个被包含文件,如果要包含n个文件,要用n个#include命令。
(2) 在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。
(4) 在#include命令中,文件名可以用双撇号或尖括号括起来。
(5) 被包含文件(file2.h)与其所在的文件(即用#include命令的源文件file2.c),在预编译后已成为同一个文件(而不是两个文件)。因此,如果file2.h中有全局静态变量,它也在file1.h文件中有效,不必用extern声明。
注意:
在编译时并不是分别对两个文件分别进行编译,然后再将它们的目标程序连接的,而是在经过编译预处理后将头文件format.h包含到主文件中,得到一个新的源程序,然后对这个文件进行编译,得到一个目标(.obj)文件。被包含的文件成为新的源文件的一部分,而单独生成目标文件。
条件编译
概念:
所谓"条件编译",是对部分内容指定编译的条件,使其只在满足一定条件才进行编译。(根据设定定的条件选择待编译的语句代码)
预处理:
将满足条件的语句进行保留,不满足条件的语句进行删除,交给下一步编译
语法:
判断标识符定义或者未定义
- 语法一:
#ifdef 标识
...
#else
...
#endif
判断标识符定义或者未定义
- 语法二:
#ifndef 标识
...
#else
...
#endif
根据表达式返回的结果:0不成立,1成立
- 语法三:
#if 表达式
...
#else
...
#endif
案例:
cs
/**
* 预处理-条件编译
*/
#define MINUS 1
int main(void)
{
#ifndef MINUS // 判断标准是:标识有没有被定义
int a = 7 - 4;
#else
int a = 7 * 4;
#endif
printf("计算结果:%d\n",a);
return 0;
}
cs
/**
* 预处理-条件编译:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输
出。
*/
#include <stdio.h>
// 定义一个标识
#define LETTER 0
void main()
{
// 测试用的字母字符数组
char str[20] = "C Language";
char c;
int i;
i = 0;
while((c=str[i])!='\0')
{
i++;
#if LETTER
if(c >='a' && c <='z')
{
c -= 32;
}
#else
if(c >='A' && c <='Z')
{
c += 32;
}
#endif
printf("%c",c);
}
printf("\n");
}
避免头文件重复包含的方法
语法:
cs
#ifndef __XXXX_H
#define __XXXX_H
...
#endif
案例:
algorithm.h
cs
/**
* 自定义头文件,专门用于存放被外部访问的函数的声明
*/
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
// 数组的累加和计算
extern int sum(const int *p,int len);
#endif