C 语言学习笔记(预处理和库文件)

article/2025/6/7 1:56:09

内容提要

  • 预处理
  • 库文件

预处理

  1. 预处理
  2. 编译
  3. 汇编
  4. 链接

在这里插入图片描述

什么是预处理

预处理就是在源文件(.c文件)编译之前,所进行的一部分预备操作,这部分操作是由预处理器(预处理程序)自动完成。当源文件在编译时,编译器会自动调用预处理程序来完成预处理执行的操作,预处理执行解析完成才能进入下一步的编译过程。

查看预处理结果:

gcc 源文件 -E -o 程序名

预处理

宏定义
不带参数的定义
  • 语法

    #define 宏名称 宏值(替换文本)
    
  • 预处理机制:此时的预处理只做数据的替换,不做类型检查

  • 注意:宏定义不会占用内存空间,因为在编译前已经将宏名称替换成了宏值

  • 宏展开:在预处理阶段将宏名称替换成宏值的过程称之为“宏展开”。

  • 案例

    #include <stdio.h>#define PI 3.1415926int main()
    {float l,s,r;printf("请输入圆的半径:\n");scanf("%f",&r);// 计算周长l = 2.0 * PI * r;// 计算面积s = PI * r * r;printf("l=%10.4f\ns=%10.4f\n",l,s);return 0;
    }
    

    在这里插入图片描述

带参数的定义
  • 语法

    #define 宏名(参数列表) 替换表达式
    
  • 面试题

    #define multi(a,b) (a)*(b)
    #define multi(a,b)  a * b
    
  • 案例:

    #include <stdio.h>// 带参数的宏定义,宏名一般小写
    #define multi_1(a,b) (a) * (b)
    #define multi_2(a,b)  a * bint main()
    {int result1 = multi(7+2,3); // 27printf("%d\n", result1);int result1 = multi(7+2,3); // 13printf("%d\n", result2);return 0;
    }
    
宏定义的作用域
  • #define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。

  • 可以用#undef命令终止宏定义的作用域

    #include <stdio.h>#define PI 3.14 // PI的作用域3~12行
    #define DAY 29void func1(){float r = 4;float s = PI * r * r; // 预处理后:float s = 3.14 * r * r;int day = DAY;        // 预处理后:int day = 29;
    }#undef PI // 终止了PI的范围#define PI 3.1415926void func2()
    {float r = 4;float s = PI * r * r;  // 预处理后:float s = 3.1415926 * r *rint day = DAY;         
    }int main()
    {return 0;
    }
    
宏定义中引用已定义的宏名
  • 案例:

    #include <stdio.h>#define R 3.0     // 半径
    #define PI 3.14
    #define L 2 * PI * R  // 在宏定义中引用已定义的宏名
    #define S PI * R * R  // 面积#define P_WIDTH = 800
    #define P_HEIGHT = 480
    #define SIZE = P_WIDTH * P_HEIGHTint main()
    {printf("L=%f\nS=%f\n",L,S);return 0;
    }
    

预处理结果:

在这里插入图片描述

条件编译

定义:根据设定的条件选择编译的语句代码。

预处理机制将满足条件的语句进行保留,将不满足条件的语句进行删除,交给下一步编译。

语法

  • 语法1

    根据找到标记,来决定是否参与编译(标记存在为真,不存在为假

    #ifdef 标记
    ...语句代码1
    #else
    ...语句代码2
    #endif
    
  • 语法2

    根据是否找到标记,来决定是否参与编译(标记不存在为真,存在为假

    #ifndef 标记
    ...语句代码1
    #else
    ...语句代码2
    #endif
    
  • 语法3

    根据表达式的结果,来决定是否参与编译(表达式成立为真,不成立为假

    -------单分支
    #if 表达式
    ...语句代码1
    #endif   -------双分支
    #if 表达式
    ...语句代码1
    #else
    ...语句代码2
    #endif-------多分支
    #if 表达式
    ...语句代码1
    #elif 表达式n
    ...语句代码n
    #else
    ...语句代码n+1
    #endif
    

    案例

#include <stdio.h>// 定义一个条件编译的标记
#define LETTER 0  // 默认是大写int main()
{// 测试用的字母字符串char str[20] = "C Language";char c;int i = 0;// 遍历获取每一个字符while((c = str[i]) != '\0'){
#if LETTERif(c >= 'a' && c <= 'z'){c -= 32;}
#elseif(c >= 'A' && c <= 'z'){c += 32;}
#endifprintf("%c", c);i++;}printf("\n");return 0;
}
文件包含
概念

所谓“文件包含”处理是指一个源文件可以将另一个源文件的全部内容包含进来。这是用于多文件开发。通常,一个常规的C语言程序包含多个源文件(*.c)当某些公共资源需要在各个源文件中使用时,为了避免多次编写相同的代码,我们一般会进行代码的抽取(*.h),然后在各个源码文件中直接包含即可。

在这里插入图片描述

注意:*.h中的函数声明必须要在*.c中对应的函数定义。(函数一旦声明,就一定要定义

头文件(.h)的内容

头文件中所存放的内容就是各个源文件的彼此可见的公共资源,包括:

  • 全局变量的声明
  • 普通函数的声明
  • 静态函数的定义
  • 宏定义
  • 结构体、共用体的定义
  • 枚举常量列表的定义
  • 其他头文件包含

示例代码

myhead.h

extern int global;    // 全局变量的声明
extern void func1();  // 普通函数的声明
static void func2()   // 静态函数的声明,写在.h中,引用此文件的.c文件直接调用,写在.c文件,只能这个.c文件访问
{...
}#define max(a,b) ((a) > (b) ? (a) : (b))  // 定义
struct node            // 结构体定义
{...
};union attr             // 共用体定义
{...
};enum SEX               // 枚举常量列表定义
{...
};#include <stdio.h>  // 引入系统头文件
#include "myhead.h" // 引入自定义文件

特别说明

1.全部变量、普通函数的定义一般出现在某个源文件(*.c)中,其他源文件想要使用都需要进行声明,因此一般放在头文件中更方便

2.静态函数、宏定义、结构体、共用体、枚举的定义都只能在其他所在文件可见,因此如果多个源文件都需要的话,放到头文件时最方便的选择

预处理机制将文件中的内容替换文件包含指令

包含方式

1.#include <xxx.h>:系统会到标准库文件目录(Linux下/usr/include)查找包含的文件,建议对于系统库访问采用这种写法。

2.#include "xxx.h":在当前工程路径下(Linux下./)查找包含的文件,如果未找到,就去标准库文件目录下查找,建议对于自定义库采用这种写法。

案例
  • myheah.h

    #ifndef _MYHEAD_H
    #define _MYHEAD_H/*** 数组的累加和运算* @param int* int数组* @param int  数组大小*/ 
    extern int sum(const int*, int);#endif //_MYHEAD_H
    
  • myhead.c

    #include <stdio.h>
    #include "myhead.h"/*** 数组的累加和运算*/ 
    int sum(const int *arr, int len)
    {const int *p = arr;int sum = 0;for(; p < arr + len; p++){ sum += *p;}return sum;
    }
    
  • app.c

    #include <stdio.h>
    #include "myhead.h"
    int main(int argc,char *argv[])
    {int arr[] = {11,12,13,14,15};int result = sum(arr, sizeof(arr)/sizeof(arr[0]));printf("数组累加和的结果是%d\n", result);return 0;
    }
    
  • 多条编译命令

    gcc app.c myhead.c -o app
    
避免头文件重复包含的方法

其实就是头文件去重复。

由于头文件包含指令#include的本质是复制粘贴,并且一个头文件中可以嵌套包含其他头文件,因此很容易出现头文件被重复包含的情况。此时就需要我们进行去重,去重需要用到预处理提供的去重相关指令。

语法

#define __xxx_H  // 一般为 头文件名大写+下划线+H
#define __xxx_H
..
#endif

案例

#ifndef MYHEAD_H
#define MYHEAD_H/*** 数组的累加和运算* @param int* int数组* @param int  数组大小*/ 
extern int sum(const int*, int);#endif //MYHEAD_H

库文件

什么是库文件

库文件本质上是经过编译后生成的可被计算机执行的二进制代码。但注意库文件不能独立运行,库文件需要加载到内存中才能执行。库文件大量存在于windows, linux, MacOS等软件平台上。

库文件的分类

  • 静态库
    • windows:xxx.lib
    • linux:libxxxx.a
  • 动态库(共享库)
    • windows:xxx.dll
    • linux:libxxxx.so.major.minor(libmy.so.1.1)

注意:不同的软件平台因编译器、链接器不同,所生成的文件是不兼容的。

静态库与动态库的区别

1.静态库链接时,将库中所有内容包含到最终的可执行程序中。

2.动态库链接时,将库中的符号信息包含到最终可执行文件中,在程序运行是,才将动态库中符号的具体实现加载到内存中。

静态库与动态库的优缺点

1.静态库

  • 优点:生成的可执行程序不再以来静态文件
  • 缺点:可执行程序体积较大

2.动态库

  • 优点:生成的可执行程序体积小;动态库可被多个应用程序共享
  • 缺点:可执行程序运行依然依赖动态库文件
静态库与动态库对比

在这里插入图片描述

库文件创建

Linux系统下库文件命名规范:libxxxx.a(静态库)libxxxx.so(动态库)

静态库文件的生成
  1. 将需要生成库文件对应的源文件(*.c)通过编译(不链接)生成*.o目标文件
  2. ar命令生成的*.o打包生成libxxxx.a

库的生成

在这里插入图片描述

库的使用

在这里插入图片描述

动态库文件的生成
  1. 利用源文件(*.c)通过编译(不链接)生成位置无关*.o目标文件
  2. 将目标文件链接为*.o文件

库的生成

在这里插入图片描述

库的使用

在这里插入图片描述

注意:如果在代码编译过程或者运行中链接了库文件,系统会到/lib/usr/lib目录下查找库文件,所以建议直接将库文件放在/lib或者/usr/lib,否则系统可能无法找到库文件,造成编译错误或者运行错误

在这里插入图片描述

扩展内容
  1. 查看应用程序(例如:app)依赖的动态库

    在这里插入图片描述

  2. 动态库使用方式

    • 编译时链接动态库,运行时系统自动加载动态库

    • 程序运行时,手动加载动态库

    • 实现:

      • 涉及内容
        • 头文件:#include <dlfcn.h>
        • 接口函数:dlopen、dlclose、dlsym
        • 依赖库:-ldl
      • 示例代码:
      #include <stdio.h>
      #include <dlfcn.h>
      int main(int argc,char *argv[])
      {// 1. 加载动态库 "/lib/libdlfun.so"// - RTLD_LAZY: 延迟绑定(使用时才解析符号,提高加载速度)// - 返回 handler 是动态库的句柄,失败时返回 NULLvoid* handler = dlopen("/lib/libdlfun.so", RTLD_LAZY); if (handler == NULL) {// 打印错误信息(dlerror() 返回最后一次 dl 相关错误的字符fprintf(stderr, "dlopen 失败: %s\n", dlerror());return -1;}// 2. 从动态库中查找符号 "sum"(函数名)//    - dlsym 返回 void*,需强制转换为函数指针类型  int sum(int *arr, int size);    //    - 这里假设 "sum" 是一个接受两个int*,int参数、返回 int 的函数int (*paddr)(int*, int) = (int (*)(int*, int))dlsym(handler, "sum");if (paddr == NULL){fprintf(stderr, "dlsym 失败: %s\n", dlerror());dlclose(handler);  // 关闭动态库(释放资源)return -1;}// 3. 调用动态库中的函数 "sum",计算{11,12,13,14,15}的累加和int arr[5] = {11,12,13,14,15};printf("sum=%d\n", paddr(arr, sizeof(arr)/sizeof(arr[0])));// 4. 关闭动态库(释放内存和资源)dlclose(handler);return 0;
      }
      
      • 编译命令:
      gcc demo06.c -ldl
      

http://www.hkcw.cn/article/NmlAgGIKbq.shtml

相关文章

谷歌地图高清卫星地图软件(Google Earth)v6.0.3.2197 中文版 - 前端工具导航

谷歌地图6.0Google Earth是一款谷歌地图高清卫星地图软件&#xff0c;能够实时监测并提供最准确的地图信息&#xff0c;地球上的任意一块地区都能够准确定位并放大查看&#xff0c;覆盖范围广&#xff0c;精度高&#xff0c;非常实用&#xff01; 谷歌卫星高清地图 下载链接&a…

全球治理指标数据(1996-2023)

1945 全球治理指标&#xff08;WGI&#xff09;(1996-2023&#xff09; 数据简介 全球治理指标&#xff08;WGI&#xff09;是一个由世界银行开发的综合性数据库&#xff0c;通过政治稳定、政府效能、监管质量、法治水平、腐败控制和公民话语权六个维度系统衡量全球各国的治理…

Blocked aria-hidden on an element because its descendant retained focus.

问题出在 Element UI 的 el-table 组件 全选功能上&#xff0c;这是一个常见的无障碍&#xff08;a11y&#xff09;问题。这个错误提示与网页 accessibility&#xff08;无障碍访问&#xff09;相关&#xff0c;涉及 aria-hidden 属性的不当使用。 问题原因分析 1. Element U…

2025 年人脸识别技术应用备案政策已落地

在 AI 技术深度渗透的当下&#xff0c;人脸识别作为重要的生物识别技术&#xff0c;已广泛应用于安防、金融、零售等多领域。但随之而来的个人信息安全风险也备受关注。2025 年 6 月 1 日起《人脸识别技术应用安全管理办法》正式实施&#xff0c;企业需重视人脸识别技术应用备案…

01电气设计-380V强电部分设计

目标&#xff1a;在电气设计过程中380V的强电部分&#xff0c;一般来自与工厂&#xff0c;一般为3相5线制的380V&#xff0c;下面的应用场景是当我的用电设备&#xff08;电机&#xff0c;冷水机&#xff0c;控制器&#xff0c;驱动器&#xff0c;激光器等等&#xff09;总功率…

文件批量重命名

mv只支持单个文件命名 批量重命名用rename 例子&#xff1a; #touch命令批量创建空文件&#xff0c;文件10-15 touch file{10..15}.txt批量重命名 # 批量重命名&#xff0c;file10-15重命名为test10-15 #这里file1? 匹配的是单个字符。比如10,11等 rename file1 test1 file1…

ES的开始

ES作用 在海量数据中&#xff0c;执行搜索功能&#xff0c;使用mysql&#xff0c;效率过低&#xff0c; 如果关键字输入不准确&#xff0c;一样可以搜索到想要的数据 讲搜索关键字&#xff0c;以红色字体展示 ES介绍 ES是基于java语言并且基于Lucene编写的搜索引擎框架&#x…

【论文解读】ReAct:从思考脱离行动, 到行动反馈思考

认识从实践开始&#xff0c;经过实践得到了理论的认识&#xff0c;还须再回到实践去。 ——《实践论》,毛泽东 1st author: About – Shunyu Yao – 姚顺雨 paper [2210.03629] ReAct: Synergizing Reasoning and Acting in Language ModelsReAct: Synergizing Reasoning and…

AXURE-动态面板

1.概述 动态面板原件&#xff0c;容器类的原件一个动态面板可以有多种状态 同一时刻只展示一个状态 默认展示第一个状态 主要用于多个状态的切换可拖动 1.1 创建 将原件库中的“动态面板”原件&#xff0c;直接拖动到工作区中&#xff0c;创建空白动态面板将页面中原件选中…

AI地面垃圾检测算法智能分析网关V4打造城市/公园/校园等场景环保卫生监管解决方案

一、方案背景​ 在城市管理与场所运营中&#xff0c;地面垃圾的及时清理是环境品质的重要指标。传统人工巡检效率低、成本高&#xff0c;存在明显滞后性&#xff0c;难以满足现代环境管理需求。随着人工智能与计算机视觉技术发展&#xff0c;智能化管理成为趋势。AI智能分析网…

帝国CMS QQ登录插件最新版 获取QQ头像和QQ昵称

帝国CMS QQ登录插件最新版 获取QQ头像和QQ昵称 QQ一键登录&#xff0c;免邮箱 随机密码 获取QQ头像 获取QQ昵称 直接下载上传到帝国CMS&#xff1a;/e/memberconnect UTF-8版本 GBK的自己转换 QQ登录后的默认密码 是随机的邮箱账号前面的随机6个字母和数字 【下图字母数…

Kafka 的优势是什么?

Kafka 作为分布式流处理平台的核心组件&#xff0c;其设计哲学围绕高吞吐、低延迟、高可扩展性展开&#xff0c;在实时数据管道和大数据生态中具有不可替代的地位。 一、超高吞吐量与低延迟 1. 磁盘顺序 I/O 优化 突破磁盘瓶颈&#xff1a;Kafka 将消息持久化到磁盘&#xff…

低谷才是出成绩

有些朋友说我现在是高光&#xff0c;其实不然 之所以有这样的误解&#xff0c;是我个人的简历上是不断增加名誉。这点属实&#xff0c;看看我的词条&#xff1a;https://www.modb.pro/wiki/4245的确如此。但是其实也有误会。事情可以反过来看。因为&#xff0c;如果做技术的在…

Bash shell四则运算

文章目录 四则运算1. ‌expr 命令‌2. ‌$(( )) 表达式&#xff08;推荐&#xff09;‌3. ‌$[ ] 表达式&#xff08;已弃用&#xff09;‌4. ‌let 命令‌小数运算i 和 i 区别 四则运算 算术运算&#xff1a; - * / %&#xff08;取模&#xff0c;求余数&#xff09; Bash sh…

Windows + CPU也能跑时序预测:TSLib框架快速上手与踩坑避雷

在时序预测领域,选择一个成熟的框架往往能让我们事半功倍。最近接手了一个紧急的时序预测项目,经过一番调研后,我选择了TSLib(Time-Series-Library)这个优秀的开源框架来快速搭建整个预测流程。 由于开发环境限制在Windows平台且没有GPU支持,整个部署过程还是遇到了一些…

多模态大语言模型arxiv论文略读(105)

UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文标题&#xff1a;UnifiedMLLM: Enabling Unified Representation for Multi-modal Multi-tasks With Large Language Model ➡️ 论文作者&#xff1a;Zhaowei…

set map数据结构

#include <set> #include <iostream> using namespace std;int main() {// 设置控制台输出编码为UTF-8system("chcp 65001");set<int> s1; // 创建一个整数集合// 插入元素s1.insert(5);s1.insert(3);s1.insert(7);s1.insert(1);s1.insert(9);//默…

云开发实现新闻列表小程序

新闻列表小程序需要两个页面即新闻列表页及新闻发布页&#xff0c;这两个页面需要以tabBar的形式展示&#xff0c;单击tab图标可以进行页面相互切换。本项目中是分别在cloudfunctions中创建一个名为“submit”的云函数&#xff0c;功能为向小程序端发布信息&#xff0c;在pages…

《C++ Core Guidelines解析》深入理解C++

前言 在计算机编程领域&#xff0c;C一直以其高效、灵活和强大而闻名。然而&#xff0c;C作为一种复杂的编程语言&#xff0c;如果没有正确的理解和使用&#xff0c;很容易导致软件质量的下降和性能问题的出现。幸运的是&#xff0c;一本名为《CCore Guidelines解析》的书籍为…

报错:Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes beca

问题描述&#xff1a;运行单元测试时&#xff0c;报这个警告&#xff1a;Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes beca 操作步骤如下&#xff1a; 将原来的&#xff1a;-ea 修改为 -ea -Xshare:off 重新启动单元测试&a…