C语言进阶之路之顶峰相见篇

news2024/12/17 14:59:30

目录

         一、学习目标

二、宏定义

预处理

宏的概念

带参宏

无值宏定义

三、条件编译

条件编译

条件编译的使用场景

四、头文件

头文件的作用

头文件的内容

头文件的基础语句:

GCC编译器的4个编译步骤:

总结


一、学习目标

  • 掌握宏定义含义和用法
  • 理解条件编译的场景和用法
  • 清楚头文件的概念和用法

二、宏定义

预处理

        在C语言程序源码中,凡是以井号(#)开头的语句被称为预处理语句,这些语句严格意义上并不属于C语言语法的范畴,它们在编译的第一个阶段统一由所谓预处理器(cc1)来处理。所谓预处理,顾名思义,指的是真正的C程序编译之前预先进行的一些处理步骤,这些预处理指令包括:

  1. 头文件:#include
  2. 定义宏:#define
  3. 取消宏:#undef
  4. 条件编译:#if、#ifdef、#ifndef、#else、#elif、#endif
  5. 显示错误:#error
  6. 修改当前文件名和行号:#line
  7. 向编译器传送特定指令:#progma
  • 基本语法
    • 一个逻辑行只能出现一条预处理指令,多个物理行需要用反斜杠( \ 转义字符)连接成一个逻辑行
    • 预处理是整个编译全过程的第一步:预处理 - 编译 - 汇编 - 链接
    • 可以通过如下编译选项来指定来限定编译器只进行预处理操作 -E
gcc example.c -o example.i -E

宏的概念

宏(macro)实际上就是一段特定的字串,在源码中用以替换为指定的表达式。例如:

#define PI 3.14

        此处,PI 就是宏(宏一般习惯用大写字母表达,以区分于变量和函数,但这并不是语法规定,只是一种习惯),是一段特定的字串,这个字串在源码中出现时,将被替换为3.14。例如:

int main()
{
    printf("圆周率: %f\n", PI);   
    // 此语句将被替换为:printf("圆周率: %f\n", 3.14);
}
  • 宏的作用
    • 使得程序更具可读性字串单词一般比纯数字更容易让人理解其含义
    • 使得程序修改更易行:修改宏定义,即修改了所有该宏替换的表达式。
    • 提高程序的运行效率(宏函数):程序的执行不再需要函数切换开销,而是就地展开。

无参宏

无参宏意味着使用宏的时候,无需指定任何参数,比如:

#define PI          3.14
#define SCREEN_SIZE 800*480*4 
int main()
{
    // 在代码中,可以随时使用以上无参宏,来替代其所代表的表达式:
    printf("圆周率: %f\n", PI); 
    mmap(NULL, SCREEN_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, ...);
}

注意到,上述代码中,除了有自定义的宏,还有系统预定义的宏:

// 自定义宏:
#define PI          3.14
#define SCREEN_SIZE 800*480*4 

// 系统预定义宏
#define NULL ((void *)0)
#define PROT_READ    0x1    /* Page can be read.  */
#define PROT_WRITE    0x2    /* Page can be written.  */
#define MAP_SHARED    0x01    /* Share changes.  */
#define  NULL           ((void *)0)

宏的最基本特征是进行直接文本替换(不做任何的运算以及判断),以上代码被替换之后的结果是:

int main()
{
    printf("圆周率: %f\n", 3.14); 
    mmap(((void *)0), 800*480*4, 0x1|0x2, 0x01, ...);
}

带参宏

        带参宏意味着宏定义可以携带“参数”,从形式上看跟函数很像(因此带参宏被称为宏函数),例如:

#define MAX(a, b)   a>b ? a : b
#define MIN(a, b)   a<b ? a : b

        以上的MAX(a,b) 和 MIN(a,b) 都是带参宏,不管是否带参,宏都遵循最初的规则,即宏是一段待替换的文本,例如在以下代码中,宏在预处理阶段都将被替换掉:

int main()
{
    int x = 100, y = 200;
    printf("最大值:%d\n", MAX(x, y));
    printf("最小值:%d\n", MIN(x, y));
    // 以上代码等价于:
    // printf("最大值:%d\n", x>y ? x : y);
    // printf("最小值:%d\n", x<y ? x : y);
}
  • 带参宏的特点:
    1. 直接文本替换,不做任何语法判断,更不做任何中间运算。
    2. 宏在编译的第一个阶段就被替换掉,运行中不存在宏。
    3. 宏将在所有出现它的地方展开,这一方面浪费了内存空间,另一方面有节约了切换时间。

带参宏的副作用

        由于宏仅仅做文本替换,中间不涉及任何语法检查、类型匹配、数值运算,因此用起来相对函数要麻烦很多。例如:

#define MAX(a, b) a>b ? a : b

int main()
{
    int x = 100, y = 200;
    printf("最大值:%d\n", MAX(x, y==200?888:999));
}

        直观上看,无论 y 的取值是多少,表达式 y==200?888:999 的值一定比 x 要大,但由于宏定义仅仅是文本替换,中间不涉及任何运算,因此等价于:

printf("最大值:%d\n", x>y==200?888:999 ? x : y==200?888:999);

        可见,带参宏的参数不能像函数参数那样视为一个整体,整个宏定义也不能视为一个单一的数据,事实上,不管是宏参数还是宏本身,都应被视为一个字串,或者一个表达式,或者一段文本,因此最基本的原则是

  • 将宏定义中所有能用括号括起来的部分,都括起来,比如:
#define MAX(a, b) ((a)>(b) ? (a) : (b))

无值宏定义

定义无参宏的时候,不一定需要带值,无值的宏定义经常在条件编译中作为判断条件出现(实现条件编译),例如:

#define BIG_ENDIAN
#define __cplusplus

三、条件编译

条件编译

  • 概念:有条件的编译,通过控制某些宏的值,来决定编译哪段代码
  • 形式:
    • 形式1:判断表达式 MACRO 是否为真,据此决定其所包含的代码段是否要编译
    • 注意:#if形式条件编译需要有值宏
#define A 0
#define B 1
#define C 2

#if A
    ... // 如果 MACRO 为真,那么该段代码将被编译,否则被丢弃
#endif
// 二路分支
#if A
    ... 
#elif B
    ...
#endif

示例:


#define  DEBUG  0 
#define  DEMO  1 
#define  TEST  0 

int main(int argc, char const *argv[])
{

    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
    

    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );

// 判断宏 DEBUG是否为真
#if DEBUG 
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#endif

// 判断宏 DEMO 是否为真  来决定使用的是  21行或23行
#if DEMO
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#else
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#endif


// 判断宏 DEMO 是否为真  如果为真则启用地29行,否则判断TEST是否为真来决定启用 31或33行
#if DEMO
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#elif TEST
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#else
    printf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#endif


    return 0;
}

如何在编译时定义或指定宏的值

gcc  xxx.c   -DDEBUG=1  #  编译xxx.c时设置宏DEBUG的值为1 
  • 形式
    • 形式2:判断宏 MACRO 是否已被定义,据此决定其所包含的代码段是否要编译
// 单独判断
#ifdef MACRO
    ...
#endif

// 二路分支
#ifdef MACRO
    ...
#else
   ...
#endif
  • 形式
    • 形式3:判断宏MACRO是否未被定义,据此决定其所包含的代码段是否要编
// 单独判断
#ifndef MACRO
    ...
#endif

// 二路分支
#ifndef MACRO
    ...
#else
   ...
#endif
  • 总结
    • #ifdef 此种形式,判定的是宏是否已被定义,这不要求宏有值
    • #if 、#elif 这些形式,判定的是宏的是否为,这要求宏必须有值
    • 在命令行中进行编译程序时可以直接使用-D选项来定义宏,此时宏的值如果没有特意给那么该宏的值默认为真。
gcc   xxx.c  -o xxx  -DDEBUG  #  当前命令行中定义了一个宏 DEBUG并该宏没有赋值那么他的值默认为真

条件编译的使用场景

        控制调试语句:在程序中,用条件编译将调试语句包裹起来,通过gcc编译选项随意控制调试代码的启停状态。例如:

gcc example.c -o example -DMACRO

        以上语句中,-D意味着 Define,MACRO 是程序中用来控制调试语句的一个宏,如此一来就可以在完全不需要修改源代码的情况下,通过外部编译指令选项非常方便地控制调试信息的启停。

        选择代码片段:在一些大型项目中(例如 Linux 内核),某个相同功能的模块往往有不同的实现,需要用户根据具体的情况来“配置”,这个所谓的配置的过程,就是对代码中不同的宏的选择的过程。

#define A 0  // 网卡1
#define B 1  // 网卡2  √
#define C 0  // 网卡3

// 多路分支
#if A
    ... 
#elif B
    ...
#elif C
    ...
#endif       

四、头文件

        头文件的作用

        通常,一个常规的C语言程序会包含多个源码文件(.c,当某些公共资源需要在各个源码文件中使用时,为了避免多次编写相同的代码,一般的做法是将这些大家都需要用到的公共资源放入头文件(.h)当中,然后在各个源码文件中直接包含即可。

头文件的内容

  • 头文件中所存放的内容,就是各个源码文件(xxx.c)彼此可见的公共资源,包括:
  1. 全局变量声明
  2. 普通函数声明
  3. 静态函数的定义。 --- 使用static 修饰的函数
  4. 宏定义。
  5. 结构体、联合体声明
  6. 枚举常量列表声明
  7. 其他头文件。 --- 嵌套包含其他 的头文件

头文件的基础语句

#ifndef     MY_HEAD_H   //这是一个条件编译,判断MY_HEAD_H是否没有被定义 (如果没定义则条件成立)
#define     MY_HEAD_H   //定义一个宏  MY_HEAD_H


// 错误示范, 如果该头文件被多个.C所包含则有可能会导致重复定义的问题
int a = 123 ;
char b = 45 ;


#endif   //ifndef 的结束标记

注意

    • 头文件中的基础格式 #ifndef #define #endif 这3条语句用于防止同一个头文件被一个.c源文件多次重复包含。
    • 错误示范中 a与b的定义在多个原文件中被定义,因此在链接阶段会出现多重定义的问题。
  • 特别说明:
    1. 全局变量普通函数的定义一般出现在某个源文件(*.c *.cpp)中,其他的源文件想要使用都需要进行声明,因此声明语句一般放在头文件中更方便
    1. 静态函数、宏定义、结构体、联合体的声明都只能在其所在的文件可见,因此如果多个源文件都需要使用的话,放到头文件中定义是最方便,也是最安全的选择。

GCC编译器的4个编译步骤:

预处理

编译

汇编

该操作直接使用汇编器,把上一个步骤产生的.s汇编文件直接转换为对应的二进制的指令。

链接

总结

        本文细讲了C语进阶路上的终极BOSS关卡,这次的BOSS可以结合之前的所有技能和特点,C语的第一阶段到现在就结束了,将各关卡的小怪全部斩杀后可获得大量经验值。而下一阶段来细讲数据结构的小怪、BOSS,听说通过后有神秘奖励哦~最后祝各位都可爬上C语巅峰,斩尽拦路小妖。

        本文参考 粤嵌文哥 的部分课件,经过整理和修改后发布在C站。如有转载,请联系本人

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

持续集成交付CICD:通过API方式上传Nexus制品

目录 一、实验 1.通过API方式上传Nexus制品 二、问题 1.如何通过API方式上传PNG图片 2.如何通过API方式上传tar.gz 与 ZIP文件 3.如何通过API方式上传Jar file文件 4.如何通过API方式上传制品&#xff08;maven类型的制品&#xff09;文件 5.如何下载制品 一、实验 1.通…

我在Vscode学OpenCV 图像处理二(滤除噪声干扰)

图像处理二 滤除噪声干扰三、噪声3.1图像噪声3.2 滤波3.2.1均值滤波&#xff08;1&#xff09;锚点&#xff08;2&#xff09;中心点&#xff08;下面第3小点会详细解释&#xff09;&#xff08;3&#xff09;核的大小奇偶数的区别&#xff08;1&#xff09;举例奇偶的例子&…

tomcat篇---第四篇

系列文章目录 文章目录 系列文章目录前言一、为什么我们将tomcat称为Web容器或者Servlet容器 ?二、tomcat是如何处理Http请求流程的?三、tomcat结构目录有哪些?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

软件测试相关

软件测试是什么&#xff1f; 使用人工和自动手段来运行或测试某个系统的过程&#xff0c;其目的在于验证它是否满足规定的需求或弄清预期结果与实际结果的差别。 为什么做软件测试&#xff1f;目的是什么&#xff1f; 发现软件存在的代码或业务逻辑错误 检验产品是否符合用户需…

Docker本地部署Drupal内容管理框架并实现公网远程访问

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址7. 结语 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它…

免费分享一套Springboot+Vue前后端分离的在线商城系统,挺实用的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue前后端分离的在线商城系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue在线商城系统 毕业设计 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue在线商城系统 毕业设计 …

EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks(2020)

文章目录 -Abstract1. Introductiondiss former methodour method 2. Related Work3. Compound Model Scaling3.1. 问题公式化3.2. Scaling Dimensions3.3. Compound Scaling 4. EfficientNet Architecture5. Experiments6. Discussion7. Conclusion 原文链接 源代码 - 本文中…

SpringIOC之ConfigurationClassUtils

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

mysql面试题——日志

一&#xff1a;为什么需要REDO日志 缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟&#xff0c;checkpoint机制可以保证数据的最终落盘&#xff0c;然而由于checkpoint 并不是每次变更的时候就触发 的&#xff0c;而是master线程隔一段时间去处理的。所以最坏的情况就是事务提交后…

【基于Python的二手车数据可视化平台的设计与实现】

基于Python的二手车数据可视化平台的设计与实现 前言数据获取与处理网络爬虫数据存储 可视化平台的设计与实现Flask框架数据可视化 创新点结语 前言 随着社会的不断发展&#xff0c;二手车市场也逐渐成为一个备受关注的领域。为了更好地为二手车的买家和卖家提供信息&#xff…

【Vue】日常错误总结(持续更新)

日常遇到的小问题汇总, 内容小篇幅少的就全放这里了, 内容多的会在Vue专栏单独分享~ 目录 【Q】 el-form-item值为 null 或 undefined显示““ 【Q】dialog内组件数据刷新总是延迟慢一拍 问题背景描述 解决方案 代码简单模拟 JS 【Q】el-input 不能输入的解决办法 方法…

如何优雅地使用Mybatis逆向工程生成类

文/朱季谦 1.环境&#xff1a;SpringBoot 2.在pom.xml文件里引入相关依赖&#xff1a; 1 <plugin>2 <groupId>org.mybatis.generator</groupId>3 <artifactId>mybatis-generator-maven-plugin</artifactId>4 <version>1.3.6<…

(五)五种最新算法(SWO、COA、LSO、GRO、LO)求解无人机路径规划MATLAB

一、五种算法&#xff08;SWO、COA、LSO、GRO、LO&#xff09;简介 1、蜘蛛蜂优化算法SWO 蜘蛛蜂优化算法&#xff08;Spider wasp optimizer&#xff0c;SWO&#xff09;由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模型雌性蜘蛛蜂的狩猎、筑巢和交配行为&…

软件工程之需求分析

一、对需求的基本认识 1.需求分析简介 (1)什么是需求 用户需求&#xff1a;由用户提出。原始的用户需求通常是不能直接做成产品的&#xff0c;需要对其进行分析提炼&#xff0c;最终形成产品需求。 产品需求&#xff1a;产品经理针对用户需求提出的解决方案。 (2)为什么要…

数据仓库工具Hive

1. 请解释Hive是什么&#xff0c;它的主要用途是什么&#xff1f; Hive是一个基于Hadoop的数据仓库工具&#xff0c;主要用于处理和分析大规模结构化数据。它可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类似SQL的查询功能&#xff0c;将SQL语句转换为MapRedu…

排序:非递归的快排

目录 非递归的快排&#xff1a; 代码分析&#xff1a; 代码演示&#xff1a; 非递归的快排&#xff1a; 众所周知&#xff0c;递归变成非递归&#xff0c;而如果还想具有递归的功能&#xff0c;那么递归的那部分则需要变成循环来实现。 而再我们的排序中&#xff0c;我们可…

数字逻辑电路基础-组合逻辑电路之4位先行进位加法器

文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 前面介绍4位行波进位全加器(串行加法器)的原理及verilog实现,但是它是一种串行加法器,当位数多时,比如32位的二进制数相加,由于进位逐位从低位向高位传递,这会造成相当大的延迟。对于需要快速加法运算的…

LeNet

概念 代码 model import torch.nn as nn import torch.nn.functional as Fclass LeNet(nn.Module):def __init__(self):super(LeNet, self).__init__() # super()继承父类的构造函数self.conv1 nn.Conv2d(3, 16, 5)self.pool1 nn.MaxPool2d(2, 2)self.conv2 nn.Conv2d(16…

Three.js+pcl.js 实现Web端的点云处理+显示

1 功能实现 在前面我们实现了PCD的加载器的基础上&#xff0c;这次将加上 pcl.js —— 著名的PCL库的web版本&#xff0c;详情见https://pcl.js.org/&#xff0c;来处理我们加载上去的点云。 具体实现如下&#xff1a; 用户可以通过每个板块的右上角进行处理前 / 后的切换&am…

php使用vue.js实现省市区三级联动

参考gpt 有问题问gpt 实现效果 现省市区三级联动的方法可以使用PHP结合AJAX异步请求来实现。下面是一个简单的示例代码&#xff1a; HTML部分&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>省市区三级联动…