目录
5.1 c语言编译过程
1、预编译:将.c中的头文件展开、宏展开,生成.i文件
2、编译:.i文件生成.s汇编文件
3、汇编:.s汇编文件生成.o目标文件
4、链接:.o文件生成.exe文件
.c文件---->预处理产生.i文件---->编译产生.s文件---->汇编产生.o文件
---->链接产生. exe文件
5.2 include
#include<> 用尖括号包含头文件,编译器只在系统指定的路径下找头文件
#include"" 用双引号包含头文件,编译器先在当前目录下找头文件,找不到再去系统指定的路径下找。
注意:
1、#include可以包含.c文件,但不建议包含。include包含的文件在预编译时会展开,如果一个.c被包含多次会导致函数被重复定义;
2、预处理只是对include等预处理操作进行处理,并不会检查语法,即便有错误也不会报错,在编译环节才进行语法检查。
5.3 define
用define定义宏,宏是在预编译的时候进行替换,分为带参的宏和不带参的宏。
5.3.1 不带参宏
#define PI 3.14
建议用大写字母定义宏的名字,区别于函数名,3.14是要替换宏的内容,在预编译的时候如果代码中出现了PI,就用3.14去替换。
使用宏时,只要修改宏定义,其他地方在预编译的时候就会自动替换,方便快捷。
#include<stdio.h>
#define PI 6.66
int main()
{
double f;
printf("PI = %lf,", PI);
f = PI;
printf("f = %lf\n", f);
return 0;
}
在预编译时,PI都会被自动替换为6.66,函数体变为:
int main()
{
double f;
printf("PI = %lf,", 6.66);
f = PI;
printf("f = %lf\n", 6.66);
return 0;
}
运行的结果是:PI = 6.660000,f = 6.660000
改变宏定义,PI替换为新的值:
#include<stdio.h>
#define PI 7.0
int main()
{
double f;
printf("PI = %lf,", PI);
f = PI;
printf("f = %lf\n", f);
return 0;
}
运行的结果是:PI = 7.000000,f = 7.000000
注意:宏的作用范围是从定义的地方直到本文件的末尾,如果想要在某处终止宏,使用#undef PI终止。
常用的使用方法:
1、在.c文件中定义宏,那么在这个文件中就可以用宏;
2、在头文件中定义宏,某个.c文件需要用到这个宏,只需要包含头文件即可。
5.3.2 带参宏
#define S(a,b) a*b
带参宏的形参a,b是没有类型名的(函数的形参要带类型名)。
#include<stdio.h>
#define S(a,b) a*b //S(a,b) = a*b,带参宏
int main()
{
int num1,num2;
num1 = S(2,4);
printf("num = %d,", num1);
num2 = S(7,9);
printf("num = %d", num2);
return 0;
}
预处理后,函数体变为:
int main()
{
int num1,num2;
num1 = 2*4;
printf("num = %d,", num1);
num2 = 7*9;
printf("num = %d", num2);
return 0;
}
可见,S(2,4)被替换为了2*4,a对应2,b对应4;S(7,9)被替换为了7*9,a对应7,b对应9。
运行的结果是:num = 8,num = 63。
注意:宏的替换只是简单的替换,不包括表达式的计算
int main()
{
int num;
num = S(2 + 4, 7);
return 0;
}
预处理后函数体变为:
int main()
{
int num;
num = 2 + 4 * 7;
return 0;
}
这就是宏的副作用,容易导致计算出错,在定义宏的时候添加括号(括号的优先级最高)可以解决。
#include<stdio.h>
#define S(a,b) (a)*(b) //S(a,b) = (a)*(b)
int main()
{
int num;
num = S(4+2,7);
return 0;
}
这时替换的效果为:num = (4+2)*(7) ;
5.3.3 带参宏和带参函数的区别
带参宏被调用多少次都要被展开多少次,执行代码时没有函数调用的过程,不需要压栈弹栈。带参宏浪费了空间,但节省了时间。
带参函数定义一次,在调用时去代码段取指令,要压栈弹栈,有调用的过程。带参函数浪费了时间,但节省了空间。
带参函数的形参是有类型的,而带参宏的形参没有类型名。
5.4选择性编译
1、ifdef语句
#ifdef PI
代码段一
#else
代码段二
#endif
如果在当前.c文件中,在ifdef上面已经定义过宏PI,就编译代码段一,否则编译代码段二。
与if else的区别:if else 语句都会被编译,通过条件选择执行代码,而选择性编译只会编译一块代码,另一块相当于被注释了。
int main()
{
#ifdef PI
printf("代码段一\n");
#else
printf("代码段二\n");
#endif // PI
return 0;
}
预处理后的函数体:
int main()
{
printf("代码段二\n");
return 0;
}
只剩下了代码段二的部分,而代码段一的部分没有被编译
如果在前面定义了宏PI
#define PI
int main()
{
#ifdef PI
printf("代码段一\n");
#else
printf("代码段二\n");
#endif // PI
return 0;
}
预处理后的函数体:
int main()
{
printf("代码段一\n");
return 0;
}
只剩下了代码段一的部分,而代码段二的部分没有被编译
2、ifndef语句
#ifndef PI
代码段一
#else
代码段二
#endif
如果在当前.c文件中,在ifndef上面没有定义过宏PI,就编译代码段一,否则编译代码段二
int main()
{
#ifndef PI
printf("Hello World\n");
#else
printf("Hello China\n");
#endif // PI
return 0;
}
运行的结果:Hello World
前面没有定义过宏PI,所以执行代码段一,即打印Hello World
这种方法与第一种ifdef互补,常用于防止头文件被重复包含。
//fun.h
#ifndef __FUN_H__
#define __FUN_H__
extern int sum(int x, int y);
#endif // !PI
//main.c
#include<stdio.h>
#include"fun.h"
int main()
{
return 0;
}
ifndef实现了防止头文件被重复包含的作用:如果没有定义过宏__FUN_H__,那就定义这个宏,并声明函数;相反如果定义过这个宏(即函数被声明过了),就不做处理。
写多个.h文件时一定要注意重复包含的问题。
3、if语句
#if 表达式
程序段一
(#else
程序段二)
#endif
如果表达式为真,就预编译程序段一,否则预编译程序段二。代码段二可以不写,即如果表达式为假,都不参加预编译。
一般用于软件裁剪:
#define AAA 0
int main()
{
#if AAA
printf("程序一");
#else
printf("程序二");
#endif
return 0;
}
通过定义AAA的值,来决定哪个代码块预编译,相当于一个开关。
当AAA为1,执行程序一,当AAA为0,预编译程序二,或者直接不写#else部分,即都不预编译。
5.5静态库(了解)
1、动态编译
动态编译使用的是动态库进行编译,默认的使用动态编译方法
2、静态编译
使用静态库进行编译
3、区别
(1)使用的库文件格式不一样
(2)静态编译要把静态库文件打包编译到可执行程序中;而动态编译不会把动态库文件打包编译到可执行程序中,只是编译链接关系。
因此静态编译的程序文件会很大,但在运行的时候不依赖库文件,库已经被打包进了可执行程序中了。而动态编译由于只编译链接关系,没有打包库文件,在运行时需要依赖外部的库文件,好处是文件小。
制作一个静态库
在mylib.c文件中定义了两个函数max和min,在mylib.h文件中声明了这两个函数,为了防止重复头文件重复包含,使用#ifndef选择性编译
gcc -c mylib.c -o mylib.o
ar rc libmoplib.a mylib.o
静态库起名必须以lib开头,以.0结尾。
//mylib.c
int max(int x, int y)
{
return (x > y) ? x : y;
}
int min(int x, int y)
{
return(x < y) ? x : y;
}
//mtlib.h
#ifndef __MYLIB_H__
#define __MYLIB_H__
extern int max(int x, int y);
extern int min(int x, int y);
#endif // !__MYLIB_H__
5.6动态库(了解)
制作动态链接库
gcc -shared mylib.c -o libmoplib.a.so