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

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

目录

         一、学习目标

二、宏定义

预处理

宏的概念

带参宏

无值宏定义

三、条件编译

条件编译

条件编译的使用场景

四、头文件

头文件的作用

头文件的内容

头文件的基础语句:

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 : bint 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 DEMOprintf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#elseprintf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#endif// 判断宏 DEMO 是否为真  如果为真则启用地29行,否则判断TEST是否为真来决定启用 31或33行
#if DEMOprintf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#elif TESTprintf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#elseprintf("%d__%s\n" , __LINE__ , __FUNCTION__ );
#endifreturn 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站。如有转载,请联系本人

相关文章:

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

目录 一、学习目标 二、宏定义 预处理 宏的概念 带参宏 无值宏定义 三、条件编译 条件编译 条件编译的使用场景 四、头文件 头文件的作用 头文件的内容 头文件的基础语句&#xff1a; GCC编译器的4个编译步骤&#xff1a; 总结 一、学习目标 掌握宏定义含义和用…...

第76讲:MySQL数据库中常用的命令行工具的基本使用

文章目录 1.mysql客户端命令工具2.mysqladmin管理数据库的客户端工具3.mysqlbinlog查看数据库中的二进制日志4.mysqlshow统计数据库中的信息5.mysqldump数据库备份工具6.mysqllimport还原备份的数据7.source命令还原SQL类型的备份文件 MySQL数据库提供了很多的命令行工具&#…...

初级数据结构(二)——链表

文中代码源文件已上传&#xff1a;数据结构源码 <-上一篇 初级数据结构&#xff08;一&#xff09;——顺序表 | NULL 下一篇-> 1、链表特征 与顺序表数据连续存放不同&#xff0c;链表中每个数据是分开存放的&#xff0c;而且存放的位置尤其零散&#…...

Kubernetes架构及核心部件

文章目录 1、Kubernetes集群概述1.1、概述1.2、通过声明式API即可 2、Kubernetes 集群架构2.1、Master 组件2.1.1、API Server2.1.2、集群状态存储2.1.3、控制器管理器2.1.4、调度器 2.2、Worker Node 组件2.2.1、kubelet2.2.2、容器运行时环境2.2.3、kube-proxy 2.3、图解架构…...

RAW和YUV的区别

RAW是指未经过任何压缩或处理的原始图像数据。在摄像头中&#xff0c;原始图像数据可以是来自图像传感器的未经处理的像素值。这些原始数据通常以一种Bayer模式的形式存在&#xff0c;其中每个像素仅包含一种颜色信息&#xff08;红色、绿色或蓝色&#xff09;&#xff0c;需要…...

Linux常见问题-获取日志方法总结(Ubuntu/Debian)

1 日志基本路径和基础查看方法 在 Ubuntu 或 Debian 11 系统中&#xff0c;可以通过不同的日志文件来获取系统日志和内核日志。日志常见路径如下&#xff1a; /var/log/syslog&#xff1a;包含系统的整体日志&#xff0c;包括各种系统事件和服务日志。/var/log/auth.log&…...

【机器视觉技术栈】03 - 镜头

镜头 定焦镜头变焦镜头远心镜头 FA镜头与远心镜头的区别&#xff1f; 焦距越小畸变程度越大&#xff0c;精度要求不高的场景可以使用焦距大的FA镜头做尺寸测量&#xff0c;但焦距越大带来的问题就是整个机械设备越大。精度高的场景使用远心镜头进行尺寸测量。 光学基础知识…...

判断一个Series序列的值是否为单调递减Series.is_monotonic_decreasing

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 判断一个Series序列中 各值是否单调递减 s.is_monotonic_decreasing [太阳]选择题 以下代码的输出结果中正确的是? import pandas as pd s1 pd.Series([3,2,1]) s2 pd.Series([3,2,4]) pri…...

CSPNet: A New Backbone that can Enhance Learning Capability of CNN(2019)

文章目录 -Abstract1 Introduction2 Related workformer work 3 Method3.1 Cross Stage Partial Network3.2 Exact Fusion Model 4 Experiments5 Conclusion 原文链接 源代码 - 梯度信息重用&#xff08;有别于冗余的梯度信息&#xff09;可以减少计算量和内存占用提高效率&am…...

本科毕业论文查重的依据

大家好&#xff0c;今天来聊聊本科毕业论文查重的依据&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 本科毕业论文查重依据&#xff1a;维护学术诚信的基石 摘要&#xff1a; 本科毕业论文是衡量学生学…...

如何利用Axure制作移动端产品原型

Axure是一款专业的快速原型设计工具&#xff0c;作为专业的原型设计工具&#xff0c;Axure 能够快速、高效地创建原型&#xff0c;同时支持多人协作设计和版本控制管理。它已经得到了许多大公司的采用&#xff0c;如IBM、微软、思科、eBay等&#xff0c;这些公司都利用Axure 进…...

Java中时间之间的转换

Java中常见的时间类有&#xff1a;Date、Calendar、SimpleDateFormat等。下面对不同时间类之间的转换进行介绍。 1、Date和Calendar之间的转换 Date和Calendar都可以表示时间&#xff0c;但是它们的使用方式不同。Date是一个表示特定时间点的类&#xff0c;而Calendar则是一个…...

【win32_005】调试信息打印到控制台----2种简单方法

方法1&#xff1a;使用win32 api函数 PCTSTR str1 TEXT("123456789");AllocConsole();HANDLE HConsole GetStdHandle(STD_OUTPUT_HANDLE);WriteConsole(HConsole, str1, 9, NULL, NULL);https://learn.microsoft.com/zh-cn/windows/console/writeconsole 方…...

PPT添加备注

0 Preface/Foreward 1 添加备注方法 添加备注方法&#xff1a;在page的最下端&#xff0c;有一个空白文本框&#xff0c;该文本框用来添加备注。...

Ubuntu20.04使用cephadm部署ceph集群

文章目录 Requirements环境安装Cephadm部署Ceph单机集群引导&#xff08;bootstrap&#xff09;建立新集群 管理OSD列出可用的OSD设备部署OSD删除OSD 管理主机列出主机信息添加主机到集群从集群中删除主机 部署Ceph集群 Cephadm通过在单个主机上创建一个Ceph单机集群&#xff0…...

激光打标机在智能手表上的应用:科技与时尚的完美结合

随着科技的飞速发展&#xff0c;智能手表已经成为我们日常生活中不可或缺的智能设备。而在智能手表制造中&#xff0c;激光打标机扮演着至关重要的角色。本文将详细介绍激光打标机在智能手表制造中的应用&#xff0c;以及其带来的优势和影响。 ​ 一、激光打标机在智能手表制…...

ROS-ROS通信机制-参数服务器

文章目录 一、基础理论知识二、C实现三、Python实现 一、基础理论知识 参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器&#xff0c;可以将数据存储在该容器中&#xff0c;被不同的节点调用&#xff0c;当然不同的节点…...

在github中通过action自动化部署 hugo academic theme,实现上传md文件更新博客内容

在github中通过action自动化部署 hugo academic theme 一、GitHub Action自动化部署Hugo博客方法 主要参考&#xff1a;【Hugo网站搭建】GitHub Action自动化部署Hugo博客 次要参考&#xff1a;使用 Github Action 自动部署 Hugo 博客 二、部署过程中遇到的问题和解决办法 …...

深入理解asyncio:异步编程的基础用法

引言&#xff1a; 随着计算机硬件的不断发展&#xff0c;对于异步编程的需求也越来越强烈。Python中的asyncio模块为开发者提供了一种强大而灵活的异步编程方式。本文将介绍asyncio的基础用法&#xff0c;包括async/await/run语句的使用、多个协程的并发执行、以及在协程中进行…...

Android 消息分发机制解读

前言 想必大家都知道Android系统有自己的一套消息分发机制&#xff0c;&#xff0c;从App启动那一刻起&#xff0c;App就创建了主线程的消息分发实例&#xff1a;Looper.sMainLooper,并开始无限循环&#xff0c;也就是App的心脏&#xff0c;一直跳动&#xff0c;负责协调分配来…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...