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

【C语言】 —— 预处理详解(下)

【C语言】 —— 预处理详解(下)

  • 前言
  • 七、# 和 \##
    • 7.1 # 运算符
    • 7.2 ## 运算符
  • 八、命名约定
  • 九、# u n d e f undef undef
  • 十、命令行定义
  • 十一、条件编译
    • 11.1、单分支的条件编译
    • 11.2、多分支的条件编译
    • 11.3、判断是否被定义
    • 11.4、嵌套指令
  • 十二、头文件的包含
    • 12.1 头文件被包含的方式
      • (1) 本地文件被包含的方式
      • (2)库文件包含
    • 12.2 嵌套文件包含
  • 十三、 其他预处理指令

前言

  在上期【C语言】 —— 预处理详解(下)的学习中,我们详细介绍了预处理中宏的相关知识,相信大家都收获不少。别急还有,本期让我们继续学习预处理方面的其他知识吧。

七、# 和 ##

7.1 # 运算符

  • # 运算符将宏的一个参数转换成字符串字面量。它进允许出现在带参数的宏的替换列表中
  • # 运算符所执行的操作可理解为“字符串化”

  什么意思呢?
  我们先来做一个铺垫:

int mian()
{printf("hello"   "world\n");printf("helloworld\n");return 0;
}

  上述两句代码有什么区别呢?我们一起来看看:

在这里插入图片描述

  可以看到,两个字符串和一个字符串的效果是一样的。C语言会把两个字符串天然连成一个字符串,中间加空格也没用。

  现在有这么一个场景:

int main()
{int a = 1;printf("The value of a is %d\n", a);int b = 20;printf("The value of b is %d\n", b);float f = 8.5f;printf("The value of f is %f\n", f);return 0;
}

  我们发现三句代码的逻辑都是非常相像的,但又有些许不同
  
  那我们想既然他们这么相像,能不能把他们封装成一个函数,以方便使用呢?
  但是函数是做不到的这个功能的
  那怎么办呢?
  我们可以尝试用宏来解决呢

#define Print(n, format) printf("The value of n is " format "\n", n)int main()
{int a = 1;Print(a, "%d");//printf("The value of a is %d\n", a);int b = 20;Print(b, "%d");//printf("The value of b is %d\n", b);float f = 8.5f;Print(f, "%f");//printf("The value of f is %f\n", f);return 0;
}

  
运行结果:

在这里插入图片描述

  我们发现 n n n 一直没变,那应该怎么修改呢?
  这时就应该用到我们的 # 运算符了:# 将宏的一个参数转换成字符串字面量,即 n n n 变成 “ n ” “n” n

  这时,我们再运用拼接大法就成了

#define Print(n, format) printf("The value of " #n " is " format "\n", n)

  
  不懂?看下面的解释就懂啦
在这里插入图片描述

  

7.2 ## 运算符

  ## 可以把位于他两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符## 被称为记号粘合。这样的连接必须产生一个合法的表示符,否则其结果就是未定义的。
  
  前面我们曾说,写一个求两个数的较大值的函数,对于不同的数据类型就需要写不同的函数。

int int_max(int x, int y)
{return x > y ? x : y;
}
float float_max(float x, float y)
{return x > y ? x : y;
}

  

在这里插入图片描述

  这样难免太过繁琐,而且两函数之间有很多相似的地方。
  那有没有办法能快速地创建出这样的函数呢?就像一个函数的模具一样,套一下一个函数就出来了
  
  我们可以这样来写一个

#define GENERIC_MAX(type) \type type##_max(type x, type y)\{\return x > y ? x : y;\}

  

#define GENERIC_MAX(type) \type type##_max(type x, type y)\{\return x > y ? x : y;\}GENERIC_MAX(int);    //相当于定义了一个函数int_max
GENERIC_MAX(float);  //相当于定义了一个函数float_maxint main()
{int r1 = int_max(3, 5);printf("%d\n", r1);float r2 = float_max(2.3f, 7.6f);printf("%f\n", r2);return 0;
}

  
运行结果:

在这里插入图片描述

  
  我们也可以在 g c c gcc gcc 环境下观察预处理后的 .i 文件,有个更直观地了解

在这里插入图片描述

  当然,这样生成的函数也是不方便调试的

  那这里 ## 起到什么作用呢?

  加了 ##,编译器才会认为他们是一个符号
  让我们来看看不加 ## 的效果:

在这里插入图片描述

  

八、命名约定

  一般来讲,函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者
  
我们平时的一个习惯是:

  • 把宏名全部大写
  • 函数名不要全部大写

  当然,这些命名规则并不是绝对的
  比如 o f f s e t offset offset 这个宏就写成了全小写
  
o f f s e t offset offset 是用来计算结构体成员相对于结构体起始位置的偏移量的
  

九、# u n d e f undef undef

  # u n d e f undef undef 指令用来移出一个宏定义

在这里插入图片描述
  上述代码,在 169 行使用 # u n d e f undef undef 移除了宏 MAX。在移除之前的 168 行调用时没问题的,但移除之后的 170 行调用就会报错

  

十、命令行定义

  许多 C 的编译器(不包括VS)提供了一种能力,允许在命令行中定义符号。用于启动编译过程
  
  例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性就有用处。(假定某个程序中声明了一个某长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要的数组能够大些)

在这里插入图片描述

在这里插入图片描述

  命令行定义式在预处理阶段处理的,在预处理阶段时,上述代码中 s z sz sz 的值已经确定了

  

十一、条件编译

  在编译一个程序的时候如果将一条(一组语句)编译或者放弃是很方便的。因为我们可以用条件编译指令
  
  条件编译指令就是这段代码我想让你编译就编译,不想让你编译你就不要编译了。我们可以给他设定一个条件,条件为真,这段代码就参与编译,条件为假,这段代码就不要编译了。
  
比如说:
  一些调试性的代码,删除可惜,保留又碍事,所以我们可以选择性编译
  

在这里插入图片描述

  
常用的条件编译指令:

11.1、单分支的条件编译

#if 常量表达式//···
#endif

在这里插入图片描述

  

11.2、多分支的条件编译

#if  常量表达式//···
#elif  常量表达式//···
#else//···
#endif

  哪条语句为真,就执行哪条语句

#define M 1
int main()
{
#if M == 0printf("hello\n");
#elif M == 1printf("world\n");
#elif M == 2printf("csdn\n");
#endifprintf("886\n");return 0;
}

  

11.3、判断是否被定义

#if defined(symbol)
#ifdef symbol//上面两个的反面
if !defined(symbol)
#ifndef symbol

在这里插入图片描述

  

11.4、嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif

  

十二、头文件的包含

12.1 头文件被包含的方式

(1) 本地文件被包含的方式

# include "filename"

  查找策略:先在源文件所在的工程目录下查找,如果头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件
  如果再找不到就编译错误
  
L i n u x Linux Linux 环境的标准头文件路径(头文件放在哪):

 /usr/include

VS 环境的标准头文件路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

  

(2)库文件包含

#include <filename.h>

  查找头文件直接去标准路径下去查找,如果找不到就提示编译错误
  
  这样是不是可以说,对于库文件也可以使用 “ ” 的形式包含
  答案是肯定的,但是这样做查找的效率比较低,当然这样也不容易区分是库文件还是本地文件
  

12.2 嵌套文件包含

  学习了前面的(编译和链接),我们知道头文件的包含在预处理阶段就是直接将该文件的代码拷贝到包含头文件的地方
  
  如果一个头文件被包含了 10 次,那就实际被编译了 10 次,如果重复包含,对编译的压力就比较大

t e s t . c test.c test.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
return 0;
}

t e s t . h test.h test.h

void test();
struct Stu
{
int id;
char name[20];
};

  但在一个工程中,一个文件难免被包含多次,那么如何解决这个问题呢?
  答案:条件编译

#ifndef __TEST_H__
#define __TEST_H__//头文件的内容
#endif

  
怎么理解呢?

  • 当第一次包含头文件时,要不要编译呢?先进行判断
  • __TEST_H__这个符号并没有被定义进行编译
  • 紧接着定义__TEST_H__符号
  • 之后再次包含该头文件,发现__TEST_H__已被定义不再对之后包含的该头文件进行编译

不过上面这种写法比较麻烦,还有另外一种写法:

#pragma once

  效果与上面的方式是一样的
  这样就可以避免头文件的重复引入
  

十三、 其他预处理指令

#error
#pragma
#line
···
#pragma pack()//在结构体部分介绍

有兴趣的小伙伴可以阅读 《C语言深度解剖》
  
  
  
  
  


  好啦,本期关于预处理的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

相关文章:

【C语言】 —— 预处理详解(下)

【C语言】 —— 预处理详解&#xff08;下&#xff09; 前言七、# 和 \##7.1 # 运算符7.2 ## 运算符 八、命名约定九、# u n d e f undef undef十、命令行定义十一、条件编译11.1、单分支的条件编译11.2、多分支的条件编译11.3、判断是否被定义11.4、嵌套指令 十二、头文件的包…...

Jupyter Notebook简介

Jupyter Notebook是一个开源的Web应用程序&#xff0c;允许你创建和共享包含实时代码、方程、可视化和解释性文本的文档。它广泛用于数据清理和转换、数值模拟、统计建模、机器学习等领域。 Jupyter Notebook的优势包括&#xff1a; 1. **交互式计算**&#xff1a;可以在网页…...

ChatGPT 5.0:一年后的猜想

对于ChatGPT 5.0在未来一年半后的展望与看法&#xff0c;我们可以从以下几个方面进行详细探讨&#xff1a; 一、技术提升与功能拓展 语言翻译能力&#xff1a; ChatGPT 5.0在语言翻译方面有望实现更大突破。据推测&#xff0c;新版本将利用更先进的自然语言处理技术和深度学习…...

Java套红:指定位置合并文档-NiceXWPFDocument

需求&#xff1a;做个公文系统&#xff0c;需要将正文文档在某个节点点击套红按钮&#xff0c;实现文档套红 试了很多方法&#xff0c;大多数网上能查到但是实际代码不能找到关键方法&#xff0c;可能是跟包的版本有关系&#xff0c;下面记录能用的这个。 一&#xff1a;添加依…...

【操作系统】进程管理——进程的同步与互斥(个人笔记)

学习日期&#xff1a;2024.7.8 内容摘要&#xff1a;进程同步/互斥的概念和意义&#xff0c;基于软/硬件的实现方法 进程同步与互斥的概念和意义 为什么要有进程同步机制&#xff1f; 回顾&#xff1a;在《进程管理》第一章中&#xff0c;我们学习了进程具有异步性的特征&am…...

Qt:13.多元素控件(QLinstWidget-用于显示项目列表的窗口部件、QTableWidget- 用于显示二维数据表)

目录 一、QLinstWidget-用于显示项目列表的窗口部件&#xff1a; 1.1QLinstWidget介绍&#xff1a; 1.2属性介绍&#xff1a; 1.3常用方法介绍&#xff1a; 1.4信号介绍&#xff1a; 1.5实例演示&#xff1a; 二、QTableWidget- 用于显示二维数据表&#xff1a; 2.1QTabl…...

恢复出厂设置手机变成砖

上周&#xff0c;许多Google Pixel 6&#xff08;6、6a、6 Pro&#xff09;手机用户在恢复出厂设置后都面临着设备冻结的问题。 用户说他们在下载过程中遇到了丢失 tune2fs 文件的错误 。 这会导致屏幕显示以下消息&#xff1a;“Android 系统无法启动。您的数据可能会被损坏…...

解决IntelliJ IDEA中克隆GitHub项目不显示目录结构的问题

前言 当您从GitHub等代码托管平台克隆项目到IntelliJ IDEA&#xff0c;却遇到项目目录结构未能正确加载的情况时&#xff0c;不必太过困扰&#xff0c;本文将为您提供一系列解决方案&#xff0c;帮助您快速找回丢失的目录视图。 1. 调整Project View设置 操作步骤&#xff1…...

Git错误分析

错误案例1&#xff1a; 原因&#xff1a;TortoiseGit多次安装导致&#xff0c;会记录首次安装路径&#xff0c;若安装路径改变&#xff0c;需要配置最后安装的路径。...

pom.xml中重要标签介绍

在 Maven 项目中&#xff0c;pom.xml 文件是项目对象模型&#xff08;POM&#xff09;的配置文件&#xff0c;它定义了项目的依赖关系、插件、构建配置等。以下是 pom.xml 文件中一些重要的标签及其作用&#xff1a; <modelVersion>&#xff1a; 定义 POM 模型的版本。当…...

大模型日报 2024-07-11

大模型日报 2024-07-11 大模型资讯 CVPR世界第二仅次Nature&#xff01;谷歌2024学术指标出炉&#xff0c;NeurIPS、ICLR跻身前十 谷歌2024学术指标公布&#xff0c;CVPR位居第二&#xff0c;超越Science仅次于Nature。CVPR、NeurIPS、ICLR三大顶会跻身TOP 10。 CVPR成全球第二…...

Redis基础教程(十六):Redis Stream

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…...

机器学习筑基篇,容器调用显卡计算资源,Ubuntu 24.04 快速安装 NVIDIA Container Toolkit!...

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] Ubuntu 24.04 安装 NVIDIA Container Toolkit 什么是 NVIDIA Container Toolkit? 描述:NVIDIA Container Toolkit(容器工具包)使用户能够构建和运行 GPU 加速的容器,该工具包括一个容器运行时库和实用程序,用于自动…...

全网第一个java链接阿里云redis并可操作

添加依赖 redis.clients jedis 5.1.2 然后通过 JedisPool pool new JedisPool(host3, 6379); Jedis jedis pool.getResource(); jedis.auth(“username”,“password”); jedis.set(“ab”,“ab”); System.out.println(jedis.get(“ab”)); 即可链接成功&#xff0c;成功…...

Mysql ORDER BY是否走索引?

在 MySQL 中&#xff0c;ORDER BY 子句是否使用索引取决于多种因素&#xff0c;包括查询的具体情况、索引的类型和结构、查询中的其他条件等。 使用索引的情况 单列索引和 ORDER BY&#xff1a; 当 ORDER BY 子句中的列有单列索引时&#xff0c;MySQL 可以利用该索引来加速排序…...

图纸文档管理新篇章:陕西航沣与三品软件合作 优化研发流程

近日&#xff0c;陕西航沣新材料有限公司与三品软件正式达成合作协议&#xff0c;共同打造高效、智能的图纸文档管理平台。此次合作旨在赋能陕西航沣在高性能碳纤维增强纸基摩擦材料领域的创新与发展&#xff0c;提升企业的核心竞争力。 客户简介 陕西航沣新材料有限公司&…...

链式队列的实现

//1:创建循环队列 //2:判空 //4:入队 //5:出队 //6:遍历 //7:队列长度 //8:销毁 main.c #include"loop.h" int main(int argc, const char *argv[]) {loop_p Lloop_create();//创建loop_empty(L);//判空loop_length(L);//长度loop_input(L,10);//入队loop_input(L,…...

深入剖析3D企业云展厅的价值,多维展示和精准营销的创新结合

一、3D企业云展厅的多维展示优势 1、全方位展示企业展品 3D企业云展厅通过3D建模技术为企业提供了全方位的展品展示平台。相比于传统平面的图片或视频&#xff0c;3D展厅能够让用户以任意角度观看展品&#xff0c;仿佛置身于真实展厅中。这种全方位展示不仅能够增加展品的真实…...

前台线程和后台线程(了解篇)

在多线程编程中&#xff0c;理解线程的不同类型对于编写高效、稳定的程序至关重要。特别地&#xff0c;前台线程&#xff08;Foreground Threads&#xff09;与后台线程&#xff08;Background Threads&#xff09;在行为上有着根本的区别&#xff0c;这些区别直接影响到程序的…...

最强文本编辑器 VIM 指令大全

Vim 是从 Vi 编辑器发展出来的一款极其强大的文本编辑器&#xff0c;它保留了 Vi 编辑器的所有功能&#xff0c;并添加了许多新特性。Vim 具有代码补全、语法高亮、错误跳转、批量化处理等编辑功能&#xff0c;还支持异常丰富的插件扩展&#xff0c;且整个编辑全程可通过键盘完…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

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

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

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…...