当前位置: 首页 > news >正文

C语言基础20

内容提要

  • 预处理

  • 库文件

预处理

C语言编译步骤

  1. 预处理

  2. 编译

  3. 汇编

  4. 链接

什么是预处理

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

查看预处理结果:

 gcc 源文件 -E -o 程序名[.后缀]

预处理功能

宏定义(可以使用宏定义完成无符号常量的创建)
  • 不带参数的定义

    • 语法:

       #define 宏名称 常量数据
    • 预处理机制:此时的预处理只做数据替换,不做类型检查

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

    • 宏展开:在预编译时将宏名替换成字符串的过程称之为“宏展开”(将宏名替换成常量数据的过程)

    • 案例:

       #define PI 3.1415926
       ​
       int main()
       {
           float l,s,r,v;
           
           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

      实现:

       // 定义一个带参数的宏,带参数的宏名为小写
       #define multi_1(a,b) (a) * (b)
       #define multi_2(a,b) a * b
       ​
       int main()
       {
           int result1 = multi_1(7+2,3); // (a)*(b) = (7+2)*3 = 27
           printf("%d\n",result1); // 27
           
           int result2 = multi_2(7+2,3); // a * b = 7+2 * 3 = 13
           printf("%d\n",result2); // 13
           
           return 0;
       }
  • 宏定义的作用域:

    • #define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束

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

    • 案例:

       #define PI 3.14  // PI的作用域 10行 ~ 18行
       #define DAY 28   // DAY的作用域 11行 ~ 文件末尾
       ​
       void func1()
       {
           float r = 4;
           float s = PI * r * r;
           int day = DAY;
       }
       #undef PI // 终止了PI的范围
       ​
       #define PI 3.1415926
       ​
       void func2()
       {
           float r = 4;
           float s = PI * r * r;
           int day = DAY;
       }
       ​
       int main(int argc, char *argv[])
       {
           func1();
           func2();
           
           return 0;
       }
  • 在宏定义中引用已定义的宏名

    • 案例:

       #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_HEIGHT
       ​
       int main(int argc, char *argv[])
       {
           printf("L=%f\nS=%f\n",L,S);
           
           return 0;
       }
文件包含

概念

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

注意:*.h中的函数声明必须要在*.c中有对应的函数定义(函数的实现),否则没有意义。(函数一旦声明,就一定要定义)

头文件(.h)的内容

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

  • 全局变量的声明

  • 普通函数的声明

  • 静态函数的定义

  • 宏定义

  • 结构体、共用体的定义

  • 枚举常量列表的定义

  • 其他头文件包含

示例代码:

 // head.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,*.cpp)中,其他源文件想要使用都需要进行声明,因此一般放在头文件中更方便

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

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

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

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

案例

myhead.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
条件编译

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

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

语法:

  • 语法一:

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

     #ifdef 标识   // 判断标识符定义与否,定义为真,未定义为假(找到标识符为真①,找不到为假②)
     ... ①
     #else
     ... ②
     #endif
  • 语法二:

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

     #ifndef 标识   // 判断标识符定义与否,未定义为真,定义为假(找不到标识符为真①,找不到为假②)
     ... ①
     #else
     ... ②
     #endif
  • 语法三:

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

     #if 表达式   // 判断表达式结果,成立为1,不成立为0
     ... ①
     #else
     ... ②
     #endif

案例:

 #include <stdio.h>
 ​
 #define LETTER 0 // 默认是大写
 ​
 int main(int argc,char *argv[])
 {
     // 测试用的字母字符串
     char str[20] = "C Language";
     char c;
     int i = 0;
     
     // 遍历获取每一个字符
     while((c = str[i])!='\0')
     {
 #if LETTER
         if(c >= 'a' && c <= 'z')
         {
             c -= 32;
         }
 #else
         if(c >= 'A' && c <= 'Z')
         {
             c += 32;
         }
 #endif
         printf("%c",c);
         i++;
     }
     printf("\n");
     
     return 0;
 }

避免头文件重复包含的方法

其实就是头文件去重复

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

语法:

 #ifndef _XXXX_H  // 一般为 下划线+头文件名大写+下划线+H
 #define _XXXX_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

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

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

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

静态库与动态库的优缺点
  1. 静态库

    • 优点:生成的可执行程序不再依赖静态库文件

    • 缺点:可执行程序体积较大

  2. 动态库

    • 优点:生成的可执行程序体积小;动态库可被多个语言程序共享

    • 缺点:可执行程序运行依赖动态库文件

库文件的创建

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

静态库文件的生成
  1. 将需要生成库文件对应的源文件(*.c)通过编译(不链接)生成(*.o)目标文件

  2. ar命令将生成的*.o打包生成libxxxx.a

库的生成:

库的使用:

动态库文件的生成
  1. 利用源文件(*.c)通过编译(不链接)生成*.o目标文件

  2. 将目标文件链接为*.so文件

库的生成:

库的使用:

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

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

  2. 动态库使用方式:

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

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

    • 实现:

      • 涉及内容:

        • 头文件:#include <dlfcn.h>

        • 接口函数:dlopendlclosedlsym

        • 依赖库:-ldl

        • 句柄handler:资源的标识

      • 示例代码:

         #include <stdio.h>
         #include <dlfcn.h>
         ​
         int main(int argc,char *argv[])
         {
             // 1. 加载动态库 "/lib/libdlfun.so"
             //    - RTLD_LAZY: 延迟绑定(使用时才解析符号,提高加载速度)
             //    - 返回 handler 是动态库的句柄,失败时返回 NULL
             void* 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

相关文章:

  • 基于SpringBoot的“智慧医疗采购系统”的设计与实现(源码+数据库+文档+PPT)
  • 【题解】AtCoder AT_abc400_c 2^a b^2
  • d202547
  • AF3 OpenFoldMultimerDataModule类解读
  • 【零基础入门unity游戏开发——动画篇】Animation动画窗口,创建编辑动画
  • uniapp微信小程序地图marker自定义气泡 customCallout偶尔显示不全解决办法
  • 本地大模型构建个人知识库(Ragflow)
  • Oracle序列介绍
  • Web开发:常用 HTML 表单标签介绍
  • 数据类型与判断
  • 【后端开发面试题】每日 3 题(三十)
  • CentralCache
  • 登录窗口布局
  • 具身智能零碎知识点(一):深入解析Transformer位置编码
  • oracle 包的管理
  • ffmpeg提取字幕
  • 八大排序——c++版
  • 如何使用 Coze 的 HTTP 请求节点实现高效数据交互
  • 《深度揭秘:借助MySQL实现AI模型训练全程追溯》
  • 数据驱动金融韧性升级,开启数据交换“新范式”:构建“实时、国产化强适配”的数据交换与共享平台
  • asp技术网站开发案例/seo网站关键词优化哪家好
  • wordpress调用网站标题/网络销售管理条例
  • 专业网站建设微信商城开发/外贸网站模板
  • 淘宝客必须做网站/2023年8月份新冠症状
  • 网站建设费用选网络专业/谷歌搜索引擎营销
  • flash网站整站下载/seo自学教程推荐