C语言-预处理与库

预处理、动态库、静态库

1. 声明与定义分离

一个源文件对应一个头文件

注意:

  • 头文件名以 .h 作为后缀
  • 头文件名要与对应的原文件名 一致

例:

源文件:01_code.c

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

c 复制代码
extern int num01;
extern int num02;
extern void add(int, int);
extern void mul(int, int);

测试文件:test.c

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

  • 输出:

    c 复制代码
    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 等预处理操作进行处理,并 不会进行语法检查,这个阶段有语法错误也不会报错,第二个阶段即 编译阶段才进行语法检查

例:

c 复制代码
#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、只能在 当前文件中 使用

例:

c 复制代码
#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、带参宏带参函数的区别

  • 宏:在预编译时 对其进行 替换,如果一个文件中多次使用宏,那意味着要替换多次,此时就需占用内存,所以占据的内存多

    • 产生的预编译时期
    • 占内存多
    • 速度快
  • 函数:在程序运行时在代码区存储一份,每次调用该函数都需在代码区寻找,将其放入栈内存中(压栈),当函数执行完毕后,从栈中移除(弹栈)

    • 产生在运行时
    • 占内存少

例:

c 复制代码
#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 +10
    printf("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:判断存在

优点:节省内存,只加载需要的部分

c 复制代码
#include <stdio.h>

int main(int argc, char const *argv[])
{
    #ifdef  XXX
        printf("有定义宏名为XXX的宏\n");
    #else
        printf("没定义宏XXX\n");
    #endif

    return 0;
}


编译时定义宏:

例2:判断不存在,和头文件配合使用,防止多次引用头文件

c 复制代码
#include <stdio.h>
#include "04_test.h"
#include "04_test.h"
int main(int argc, char const *argv[])
{
    #ifndef YYY
        printf("1111\n");
    #else
        printf("2222\n");
    #endif
    return 0;
}

头文件:04_test.h

c 复制代码
#ifndef TEST
#define TEST
extern int num;
//...
#endif

#ifndef 使用含义:

1、第一次引用头文件,没有定义TEST宏,然后定义,再写头文件内容;

2、假如再次引用头文件时,第一次已经定义过TEST宏了,所以直接结束,啥也不干。

源码写法:

例3:判断是否成立

c 复制代码
#include <stdio.h>
int main(int argc, char const *argv[])
{
    #if ScORE > 85
        printf("A\n");
    #elif ScORE > 70
        printf("B\n");
    #elif ScORE >= 60
        printf("c\n");
    #else
        printf("D\n");
    #endif
    return 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

    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

    c 复制代码
    extern void add(int a, int b);
    extern void mul(int a, int b);
  • 制作

3.3.2 使用

情况1:使用静态库的文件与静态库 在同一文件夹下

命令:

复制代码
gcc 源文件名 静态库名称 -o 生成的可执行文件名

测试文件:test01.c

c 复制代码
#include <stdio.h>
#include "myfun.h"   //可以不写,但是会报警告
int main(int argc, char const *argv[])
{
    add(10, 3);
    return 0;
}

编译:

情况2:使用静态库的文件与静态库 不在同一文件夹下

注意:

  • 为了让静态库文件与其对应的头文件和使用静态库文件不在同一文件夹下,所以

    • 创建includes与libs文件夹

    • includes文件用于存储头文件

    • libs文件夹存储静态库文件

      shell 复制代码
      mkdir includes
      mkdir libs
      mv myfun.h includes/
      mv libmyfun.a libs/
  • 参数

    shell 复制代码
    -L 引用的静态库所在的路径
    -l 静态库名, 去掉lib与.a
    -I 头文件所在路径
  • 命令

    复制代码
    gcc 源文件名 -L 静态库所在的路径 -l 静态库名 -I 头文件所在路径 -o 生成的可执行文件名

情况3:静态库文件与对应的头文件 在系统文件夹下

  • 系统库路径:

    复制代码
    /usr/include 		存储头文件
    /usr/lib 或 /lib 	存储库文件
  • 注意:

    shell 复制代码
    # 为了让静态库文件与其对应的头文件和系统文件夹下,所以需要移动
    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 生成的可执行文件名
相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜7 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa7 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio