前面学习了c语言的基本语法特性,本节进行更深入的学习。
预处理程序。 编译指令: 预处理, 宏定义,
建立自己的数据类型:结构体,联合体,动态数据结构
c语言表达式工具 逻辑运算符:
& | ^ ~ << >>
函数的递归调用方法
什么是预处理
vim helloworld.c1
helloworld.c:
#include int main()
{ printf("hello,world!\n"); return 0;
}1234567
编译的目的:
从c语言
.c
源文件变成可执行文件
gcc helloworld.c -o helloworld.out./helloworld.out12
编译的四个步骤:
.c
文件->.i
文件->.s
文件->.o
文件->可执行文件(可运行)
下面我们来查看预处理中要做的事情:
gcc -o helloworld.i helloworld.c -E1
-E
表示只让gcc执行预处理。
// 查看helloworld.i文件cat helloworld.i12
vim跳到整个文档底部,命令: :$
可以看到代码的底端是我们的main函数
对比一下.i
文件和.c
文件的区别
首先:它们都是c的语法。其次.c文件main函数上面是
#include
而.i
文件中这行代码不见了,变成了上面这些东西。
所以预处理所做的第一件事情就是展开头文件
将#include
中stdio.h
展开,将未注释的内容直接写入.i文件。
在预处理步骤中,除了展开头文件,还要进行宏替换。
宏是什么
c语言常量分为直接常量和符号常量:
#define 标识符 常量值 (注意:没有分号)1
helloMacro.c源代码:
#include #define R 10int main()
{ int a =R; printf("a=%d\n"); printf("hello,world!\n"); return 0;
}12345678910
gcc -o helloMacro.i helloMacro.c -E1
预处理过之后的代码
# 4 "helloworld.c"int main()
{ int a =10; printf("a=%d\n"); printf("hello,world!\n"); return 0;
}12345678
可以看到10是直接当做一个字符串来替换原本的宏定义R。
宏的本质是发生在预处理阶段单纯的字符串替换(宏替换), 在预处理阶段,宏不考虑语法;
示例代码2:vim helloMacro2.c
#include #define R 10#define M int main(M){ printf("hello,world!\n"); return 0;
}12345678
gcc helloMacro2.c -o helloMacro2.out./helloMacro2.out12
预处理是没有问题的,可以成功的编译执行。宏不考虑C语言的语法。它很单纯,字符串替换。
宏用于大量反复使用的常量、数组buffer的大小,为了便于修改定义成宏。
通常定义数组我们这样写:
int a[10];int b[10];12
定义两个相同大小的数组,这里我们就可以改为下面代码。
#define R 10int a[R];int b[R];123
一次修改,可以修改两份。
宏也是可以传递参数的,可以做一些函数可以做的事情
宏函数
vim helloMacroFunction.c
源代码:
#include #define R 10#define M int main(#define N(n) n*10M){ int a = R; int b = N(a); printf("b = %d\n",b); printf("a =%d\n",a); printf("hello,world!\n"); return 0;
}123456789101112131415
gcc helloMacroFunction.c -o helloMacroFunction.out./helloMacroFunction.out12
这里的处理过程: 首先将参数a替换到上面的宏中,上面就变成了
N(a) a*10
,之后再用a*10
替换下面的N(a)
int b = N(a); //变成了 int b =a*10;1
gcc -o helloMacroFunction.i helloMacroFunction.c -E1
预处理之后:
# 8 "hello.c"int main(){ int a = 10; int b =a*10; printf("b = %d\n",b); printf("a =%d\n",a); printf("hello,world!\n"); return 0;
}123456789
先不考虑宏实现,先来写一个正常的求和函数。
vim helloAdd.c1
#include #define R 20#define M int main(#define N(n) n*10int add(int a,int b){ return a b;
}M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); return 0;
}1234567891011121314151617181920212223
gcc helloAdd.c -o helloAdd.out./helloAdd.out12
使用宏函数实现求和。
vim helloAddMacro.c1
#include #define R 20#define M int main(#define N(n) n*10#define ADD(a,b) a bint add(int a,int b){ return a b;
}M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); int d =ADD(a,b); printf("d =%d\n",d); return 0;
}1234567891011121314151617181920212223242526
gcc helloAddMacro.c -o helloAddMacro.out./helloAddMacro.out12
可以看到使用宏函数和普通函数的求和效果是一致的。结果与简单的字符串替换一致。
ADD(a,b)
被替换成 a b
因此式子变成int d = a b;
gcc -o helloAddMacro.i helloAddMacro.c -E
vim helloAddMacro.i12
版本3,宏定义中优先级问题。
#include #define R 20#define M int main(#define N(n) n*10#define ADD(a,b) a bint add(int a,int b){ return a b;
}M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); int d =ADD(a,b); printf("d =%d\n",d); int e =ADD(a,b) * ADD(a,b); printf("e =%d\n",e); return 0;
}1234567891011121314151617181920212223242526272829
预测一下e的输出为: a b*a b
ab先乘起来,a=20,b=200,ab=4000,然后加上a,b:得到结果(4220)
gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence.out./helloAddMacroPrecedence.out12
运算是等我们编译完了,执行的时候才会运行的。预处理阶段不会进行运算操作。
宏定义时由于本质是字符串的替换
真正运算的时候,会按照运算符号的优先级来进行
解决方案:
#define ADD(a,b) (a b)1
gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence2.out./helloAddMacroPrecedence2.out12
加个括号,保证优先级更高一点。
宏函数和正常函数的优势?
正常的add函数需要返回值类型,需要传递进来的参数有类型要求。
讲传入的a,b 类型进行改变,如变为两个浮点型数,程序就会自动类型转换。
但是宏函数就没有这种要求可以不用考虑输入值的类型,这与普通的函数定义不同。
int c =add(10.5,20.4);printf("c =%d\n",c);float d =ADD(10.5,20.4);printf("d =%f\n",d);12345
gcc helloAddMacroPrecedenceCompare.c -o helloAddMacroPrecedenceCompare.out./helloAddMacroPrecedenceCompare.out12
普通函数例如
int add(int a,int b)
除了在开头要声明值的类型,还要设置返回值,因此在定义过程与调用过程相对复杂。若能用宏定义实现的情况应优先考虑宏定义.
宏是不考虑数据类型,不考虑c语言的语法的。只是简单的字符串的处理。
预处理阶段,除了宏之外,还提供了一个叫做mtianyan:条件编译的功能。
可以按照不同的条件,编译不同的程序部分,从而产生不同的目标代码文件。对于程序的移植和调试都是很有用的。
下集预告: 和宏比较相近的功能,typedef
Linux C预处理之typedef
严格来讲,typedef
和预处理是没