预处理、动态库、静态库
1. 声明与定义分离
一个源文件对应一个头文件
注意:
- 头文件名以
.h
作为后缀头文件名
要与对应的原文件名
一致
例:
源文件:01_code.c
#include <stdio.h>
int num01 = 10;
int num02 = 20;
void add(int a, int b)
{int sum = a + b;printf("%d + %d = %d\n", a, b, sum);
}void mul(int a, int b)
{int mul = a + b;printf("%d + %d = %d\n", a, b, mul);
}
头文件:01_code.h
extern int num01;
extern int num02;
extern void add(int, int);
extern void mul(int, int);
测试文件:test.c
#include <stdio.h>
#include "01_code.h"int main(int argc, char const *argv[])
{printf("num01 = %d\n", num01);printf("num02 = %d\n", num02);add(10, 20);mul(10, 20);return 0;
}
编译:
命令:
gcc test.c 01_code.c
./a.out
输出:
num01 = 10 num02 = 20 10 + 20 = 30 10 + 20 = 30
2. 预处理
2.1 c语言编译过程
gcc -E hello.c -o hello.i 1、预处理
gcc -S hello.i –o hello.s 2、编译
gcc -c hello.s -o hello.o 3、汇编
gcc hello.o -o hello_elf 4、链接
1、预编译
- 将
.c
中的头文件展开
、宏展开
生成
的文件是.i
文件2、编译
- 将预处理之后的
.i
文件生成.s
汇编文件3、汇编
- 将
.s
汇编文件 生成.o
文件4、链接
- 将
.o
文件 链接成目标文件(即可执行文件)
预编译包含
展开头文件
定义头文件
选择性编译注意:预编译的内容以 # 开头
2.2 include
作用:展开头文件
语法:
#include <>
用
尖括号
包含的头文件, 在 系统指定的路径下 找头文件#:表示预编译
#include ""
用
双引号
包含头文件,先在当前目录下找 头文件,找不到,再到系统指定的路径下找
注意:
1、include 经常用来包含头文件,可以包含
.c
文件,但是大家不要包含.c
因为 include 包含的文件会在预编译被展开,如果一个.c 被包含多次,展开多次,会导致函数重复定义。所以不要包含.c 文件2、预处理只是对 include 等预处理操作进行处理,并
不会进行语法检查
,这个阶段有语法错误也不会报错,第二个阶段即编译阶段才进行语法检查
。
例:
#include "01_code.h"//等价于 下面, 即在源文件中展开下面代码extern int num01;
extern int num02;
extern void add(int, int);
extern void mul(int, int);
2.2 宏:define
作用:在预处理 处理定义 类似于
变量
或函数
的东西。即:宏是在预编译的时候进行替换 。
2.2.1 不带参宏
语法:
#define 宏名 值 //宏定义#undef 宏名 //取消宏定义
注意:
1、如果定义该类型的宏(不带参的宏),
值可以省略
2、无需分号结束
3、在 宏定义后,取消定义前 可以使用
4、只能在
当前文件中
使用
例:
#include <stdio.h>
#define PI 3.14
int main(int argc, char const *argv[])
{printf("pi = %f\n", PI);
#undef PI //取消宏定义return 0;
}
2.2.2 带参宏
语法:
#define 宏名(形参) 体
注意:
1、形参没有数据类型
2、
带参宏
与带参函数
的区别
宏:在
预编译时
对其进行 替换,如果一个文件中多次使用宏
,那意味着要替换多次
,此时就需占用内存,所以占据的内存多
- 产生的预编译时期
- 占内存多
- 速度快
函数:在程序运行时在代码区存储一份,每次调用该函数都需在代码区寻找,将其放入栈内存中(压栈),当函数执行完毕后,从栈中移除(弹栈)
- 产生在运行时
- 占内存少
例:
#include <stdio.h>
#define ADD(a, b) a+b
#define MUL(a, b) a*b
#define MUL02(a, b) (a)*(b)
int main(int argc, char const *argv[])
{int sum = ADD(20, 30);printf("sum=%d\n", sum);int mul = MUL(20, 30);printf("mul=%d\n", mul);int mul02 = MUL(20+10, 30+10); //20 + 10 * 30 +10printf("mul=%d\n", mul02);int mul03 = MUL02(20+10, 30+10); //(20 + 10) * (30 + 10)printf("mul=%d\n", mul03);return 0;
}
2.2.3 小结
宏就是在
预编译时期
对其进行替换
不带参宏替换的是一个值
带参宏替换的是一段代码
2.3 选择性编译
作用:选择代码是否被编译
语法:
例1:判断存在
优点:节省内存,只加载需要的部分
#include <stdio.h>int main(int argc, char const *argv[])
{#ifdef XXXprintf("有定义宏名为XXX的宏\n");#elseprintf("没定义宏XXX\n");#endifreturn 0;
}
编译时定义宏:
例2:判断不存在,和头文件配合使用,防止多次引用头文件
#include <stdio.h>
#include "04_test.h"
#include "04_test.h"
int main(int argc, char const *argv[])
{#ifndef YYYprintf("1111\n");#elseprintf("2222\n");#endifreturn 0;
}
头文件:04_test.h
#ifndef TEST
#define TEST
extern int num;
//...
#endif
#ifndef 使用含义:
1、
第一次
引用头文件,没有定义TEST宏
,然后定义,再写头文件内容;2、假如
再次引用
头文件时,第一次已经定义过TEST宏
了,所以直接结束
,啥也不干。
源码写法:
例3:判断是否成立
#include <stdio.h>
int main(int argc, char const *argv[])
{#if ScORE > 85printf("A\n");#elif ScORE > 70printf("B\n");#elif ScORE >= 60printf("c\n");#elseprintf("D\n");#endifreturn 0;
}
3. 库
概念:库也叫代码库,可以把一个些目标文件合并在一起方便使用。
3.1 分类
静态库
动态库
静态库、动态库的区别:
注意:
- 程序中引入的文件在动态库与静态库同时存在两份
- 静态编译程序引入静态库中的该文件
- 动态编译程序引入动态库中的该文件
3.2 编译命令
动态编译:
gcc 源文件名 -o 生成的可执行文件名
静态编译:
gcc -static 源文件名 -o 生成的可执行文件名
3.3 静态库
3.3.1 制作
gcc -c 源文件名.c -o 生成的二进制文件名.o
ar rc lib静态库名称.a 生成的二进制文件名.o
注意:
静态库起名
的时候必须 以lib 开头
以.a
结尾
步骤:
-
新建文件夹:
06_code
-
源文件:
myfun.c
#include <stdio.h>void add(int a, int b) {printf("my_sum = %d\n", (a+b)); }void mul(int a, int b) {printf("my_mul = %d\n", (a*b)); }
-
头文件:
myfun.h
extern void add(int a, int b); extern void mul(int a, int b);
-
制作
3.3.2 使用
情况1:使用静态库的文件与静态库 在同一文件夹下
命令:
gcc 源文件名 静态库名称 -o 生成的可执行文件名
测试文件:test01.c
#include <stdio.h>
#include "myfun.h" //可以不写,但是会报警告
int main(int argc, char const *argv[])
{add(10, 3);return 0;
}
编译:
情况2:使用静态库的文件与静态库 不在同一文件夹下
注意:
为了让静态库文件与其对应的头文件和使用静态库文件不在同一文件夹下,所以
创建includes与libs文件夹
includes文件用于存储头文件
libs文件夹存储静态库文件
mkdir includes mkdir libs mv myfun.h includes/ mv libmyfun.a libs/
参数
-L 引用的静态库所在的路径 -l 静态库名, 去掉lib与.a -I 头文件所在路径
命令
gcc 源文件名 -L 静态库所在的路径 -l 静态库名 -I 头文件所在路径 -o 生成的可执行文件名
情况3:静态库文件与对应的头文件 在系统文件夹下
系统库路径:
/usr/include 存储头文件 /usr/lib 或 /lib 存储库文件
注意:
# 为了让静态库文件与其对应的头文件和系统文件夹下,所以需要移动 sudo mv includes/myfun.h /usr/include sudo mv libs/libmyfun.a /usr/lib
命令:
gcc 源文件名 -l 静态库名 -o 生成的可执行文件名
3.4 动态库
3.4.1 制作
命令:
gcc -shared 源文件名 -o 生成的动态库文件名.so
3.4.2 使用
情况1:使用动态库的文件与动态库在同一文件夹下
命令:
gcc 源文件名 动态库名称 -o 生成的可执行文件名
情况2:使用动态库的文件与动态库不在同一文件夹下
命令:
gcc 源文件名 -L 动态库所在路径 -l 动态库名称 -I 头文件所在路径
注意:
- 动态库名需要去掉前面的
lib
与后面.so
情况3:静态库文件与对应的头文件在系统文件夹下
命令:
gcc 源文件名 -l 静态库名 -o 生成的可执行文件名