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

article/2025/6/26 5:11:33

【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语言的学习路上一起进步!


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

相关文章

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

一、引言&#xff1a;理解内存管理的核心价值 在系统级编程领域&#xff0c;内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言&#xff0c;赋予开发者直接操作内存的能力&#xff0c;却也要求开发者深入理解内存布局与生命周期管理。本文将从内…

VS Studio2022安装教程(保姆级教程)

1.下载 官网下载&#xff1a; Visual Studio 2022 IDE - 适用于软件开发人员的编程工具 (microsoft.com)https://visualstudio.microsoft.com/zh-hans/vs/ 1.点击下载Community2022(社区版),等待下载完成之后,运行安装包&#xff08;VisualstudioSetup.exe&#xff09; 2.等待…

【CSAPP】【attacklab】实验三 Attack lab 详解

前置知识 call 指令 ret 指令 寄存器rip 栈 小端模式 栈向下生长&#xff0c;栈顶在低地址&#xff0c;栈底在高地址 指令寄存器&#xff08;RIP&#xff09;包含下一条将要被执行的指令的逻辑地址。 通常情况下&#xff0c;每取出一条指令后&#xff0c;RIP会自增指向下一…

【C/C++】字符函数和字符串函数

文章目录 前言字符函数和字符串函数1.字符分类函数2.字符转换函数3.strlen的使用和模拟实现3.1 代码演示3.2 strlen返回值3.3 strlen的模拟实现 4.strcpy的使用和模拟实现4.1 代码演示4.2 模拟实现 5.strcat的使用和模拟实现5.1 代码演示5.2 模拟实现 6.strcmp的使用和模拟实现…

C/C++之内存管理

1. 内存分布 我们定义的变量对于电脑来说也叫数据&#xff0c;同时电脑也会把这些数据分为不同的类型&#xff0c;分别是局部数据&#xff0c;静态数据&#xff0c;全局数据&#xff0c;常量数据和动态申请数据。 在 C 中&#xff0c;各类数据存储位置如下&#xff1a; • 局…

C++从入门到实战(十一)详细讲解C/C++语言中内存分布与C与C++内存管理对比

C从入门到实战&#xff08;十一&#xff09;详细讲解C/C语言中内存分布与C与C内存管理对比 前言一、C/C语言中内存分布1.内核空间2.栈3.堆4.数据段5.代码段 二、例题带练巩固C/C语言中内存分布的知识题目讲解题目答案 三、C语言动态内存分配&#xff08;知识回顾&#xff09;3.…

Educational Codeforces Round 175 (C.二分 D.树形结构、dp)

文章目录 2025.3.3C. Limited Repainting(二分)题意思路代码 D. Tree Jumps(树形结构、dp)题意思路代码 2025.3.3 Educational Codeforces Round 175 (Rated for Div. 2) C. Limited Repainting(二分) 题意 给出一个字符串a由“R”B“组成&#xff0c;不同位置对应一个惩罚…

老太突然倒地吓坏路人民警紧急相救 家属感激救命之恩

“谢谢你们帮我父亲‘捡’回一条命,再晚一会儿后果不堪设想!”5月21日,市民刘女士接到民警电话时再次表达了对紧急救援的感激之情。她的父亲76岁,患有阿尔兹海默病,下午扛着锄头出门后一直未归,家人找了3个多小时都没找到。5月19日傍晚6时许,大冶市公安局还地桥派出所接…

53岁男子诱骗侵害幼女被判刑 深挖彻查揭露更多罪行

5月29日,江苏省人民检察院召开新闻发布会,介绍了近年来加强未成年人网络司法保护的工作情况及典型案例。如皋市检察院副检察长卢海琴介绍了一起典型案例,通过深挖彻查,案件从1名被告人追到3名被告人、从1个罪名查到5个罪名、从1起强奸事实挖到19起犯罪事实、从1名被害人增加…

谷歌DeepMind最强手语翻译模型登场 打破沟通障碍

谷歌DeepMind团队于5月27日宣布推出SignGemma,这是其迄今为止最强大的手语翻译模型,能够将手语转化为口语文本。该开源模型计划在今年晚些时候加入Gemma模型家族。SignGemma支持多语言功能,但目前主要针对美国手语(ASL)和英语进行了深度优化,开发者可以自由使用并改进它。…

【C++】STL详解-----(二)vetor的使用

文章目录 vector的介绍vector的使用&#xff1a;元素访问empty vector的增删查改push_back和pop_backinsert和erase vector迭代器失效问题迭代器失效解决方法 vector的介绍 vector是可变大小数组的容器vector采用连续空间存储的方式&#xff0c;同时也表示可以采用下标访问vec…

string类

1. 为什么学习string类&#xff1f; string叫串&#xff0c;它是一个管理字符串的类&#xff0c;现实中为什么要出一个管理字符串的类呢&#xff1f;现实中我们有很多类型&#xff0c;比如int、double、char等&#xff0c;但发现这个世界的一些复杂东西都是通过字符串表示的…

潍坊烟花秀压轴项目缺席遭吐槽 设备故障引争议

多名网友在社交平台发帖表示,5月30日晚他们参加了潍坊世界风筝乐园的烟花秀表演,单人票198元,双人票298元。然而,之前宣传的压轴项目“七彩祥云”并未出现,引发观众不满。潍坊世界风筝乐园工作人员解释称,由于设备故障,“七彩祥云”环节被迫取消,并且当晚和接下来两天的…

江苏城市足球联赛为何这么火 赛事带动文旅热潮

最近,2025年江苏省城市足球联赛“苏超”火了。从“比赛第一,友谊第十四”到各地纷纷推出“跟着赛事游江苏”的文旅优惠,以足球为媒,以赛事为桥,江苏展现了独特的魅力。自5月10日揭幕以来,“苏超”迅速走红,成为江苏省乃至全国关注的热点。你以为“苏超”只是踢踢比赛?殊…

汪涵体验问界M9五座零重力座椅 舒适到不想起

汪涵体验了问界M9的零重力座椅,表情十分享受。这款座椅采用零压感知人体工学专利设计,腰部零压角为121,腿部零压角为136,能够使全身压力均匀分布,带来零压悬浮感。汪涵坐在上面久久不愿起身,甚至在车展主持时也选择躺着进行,这种舒适度让人非常心动。责任编辑:0882

德国为何要解除对援乌武器射程限制 西方军援策略重大转变

2025年5月28日,德国新当选总理弗里德里希默茨在柏林宣布,乌克兰的西方盟友将取消向基辅供应武器的射程限制。这一政策调整涵盖美国、英国、法国和德国等主要援乌国家,标志着西方对乌军援策略的重大转变,随即引发国际社会对俄乌冲突走向的强烈关注。在WDR组织的论坛上,默茨…

Nginx基础篇(Nginx目录结构分析、Nginx的启用方式和停止方式、Nginx配置文件nginx.conf文件的结构、Nginx基础配置实战)

文章目录 1. Nginx目录结构分析1.1 conf目录1.2 html目录1.3 logs目录1.4 sbin目录 2. Nginx的启用方式和停止方式2.1 信号控制2.1.1 信号2.1.2 调用命令 2.2 命令行控制2.2.1 基础操作类2.2.2 配置测试类2.2.3 进程控制类2.2.4 路径与文件类2.2.5 高级配置类 3. Nginx配置文件…

印军高层被追问是否与巴方会面 印空军将领打马虎眼

印巴停火协议签署后,两国都在宣称自己取得了胜利。然而,印度军方的表现却让人失望。5月11日,印度空军中将巴蒂召开记者会,面对记者们的追问,他声称印度空军在这次冲突中表现出色,并坚称打下了好几架巴基斯坦飞机。但当被问及具体数字时,他却表示“不想冒险猜测”,并解释…

大V:辽宁舰率海军最强编队驶入西太 展现惊人实力

自去年10月在南海与山东舰稍微展示了一下双航母的实力后,辽宁舰到今年5月中旬都没有什么动作,大家的目光都放在了更出色的山东舰和福建舰上。然而,辽宁舰在5月下旬南下西太平洋,展示了强大的实力。护航编队包括两艘055型驱逐舰、五艘052D型驱逐舰和三艘054A型护卫舰,再加上…

印度刚喊话巴基斯坦,转头联合蒙古军演?背后算盘藏不住了!

印度最近的一系列举动引起了广泛关注。不久前,印度还在与巴基斯坦紧张对峙,并放狠话威胁对方,紧接着又派了一个大型商业代表团访问台湾。更令人意外的是,印度现在绕过中国,直接与蒙古国进行联合军事演习。这种行为就像是小孩子打架,正面打不过就绕到背后捅手指头。然而,…