【C语言预处理详解(下)】--#和##运算符,命名约定,命令行定义 ,#undef,条件编译,头文件的包含,嵌套文件包含,其他预处理指令

news2025/6/6 20:12:28

目录

五.#和##运算符

 5.1--#运算符

5.2--##运算符

六.命名约定,#undef,命令行定义

6.1--命名约定

6.2--#undef 

6.3--命名行定义

七.条件编译 

常见的条件编译指令: 

1.普通的条件编译:

2.多个分支的条件编译(可以利用条件语句来辅助理解):

3.判断是否被定义: 

4.嵌套指令:

八.头文件的包含

8.1--头文件被包含的方式:

 8.1.1--本地文件包含

8.1.2--库文件包含

 8.2--嵌套文件包含

九.其他预处理指令 


🔥个人主页:@草莓熊Lotso的个人主页

🎬作者简介:C++研发方向学习者

📖个人专栏:《C语言》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。


五.#和##运算符

 5.1--#运算符

#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。

#运算符所执⾏的操作可以理解为”字符串化“

当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .

可以如下这样写:
# define PRINT(n) printf( "the value of " #n " is "  format  "\n" , n);

 具体代码演示:

#include<stdio.h>
#define PRINT(n, format)  printf("the value of "#n" is "format"\n", n)

int main()
{


	int a = 10;
	//printf("the value of a is %d\n", a);
	PRINT(a, "%d");

	float f = 3.14f;
	//printf("the value of f is %f\n", f);
	PRINT(f, "%f");


	return 0;
}

我们在这里主要分析一下变量a,我们定义好宏后,用上面的方式去调用它,我们把a替换到了宏的体内,就出现了#a,而#a转换为"a"这一字符串,然后format也被"%d"替换。

 我们还是看看PRINT(a, "%d");这一代码预处理之后的形式:

printf ( "the value of " "a" " is "  "%d" "\n" , a);

运行代码就得出来我们想要的结果。

5.2--##运算符

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称
为记号粘合
这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
这⾥我们想想,写⼀个函数求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); \
}

 我们来使用这个宏,定义不同函数试试:

GENERIC_MAX( int ) // 替换到宏体内后 int##_max ⽣成了新的符号 int_max 做函数名
GENERIC_MAX( float ) // 替换到宏体内后 float##_max ⽣成了新的符号 float_max 做函数名
int main ()
{
     // 调⽤函数
     int m = int_max( 8 , 9 );
     printf ( "%d\n" , m);
     float fm = float_max( 6 .5f , 8 .5f );
     printf ( "%f\n" , fm);
     return 0 ;
}

最终代码实现: 

#include<stdio.h>
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
    return (x>y?x:y); \
}

GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
	//调⽤函数
	int m = int_max(8, 9);
	printf("%d\n", m);
	float fm = float_max(6.5f, 8.5f);
	printf("%f\n", fm);

	return 0;
}

 这里简单的举个例子帮助大家理解一下##运算符的使用,还想要更深入了解的可以去查阅一下相关资料来学习。


六.命名约定,#undef,命令行定义

6.1--命名约定

⼀般来讲函数和宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
我们的习惯写法是:
  • 把宏名全部⼤写
  • 函数名不要全部⼤写

6.2--#undef 

--我们通常用这条指令去移除一个宏定义

# undef NAME
// 如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

我们来看两个实际例子直观的了解一下它的使用: 

例一:

#include <stdio.h>

#define M 100

int main()
{
	printf("%d\n", M);
#undef M

#define M 1000
	printf("%d\n", M);

	return 0;
}

 例二:

#include<stdio.h>
#define SQUARE(x) ((x) * (x))

int main() 
{
	int a = 10;
	int ret=SQUARE(a);
	printf("%d\n", ret);
#undef SQUARE(x)

#define SQUARE(x) ( ( x ) + ( x ) )
	int ret2 = SQUARE(a);
	printf("%d\n", ret2);
	
}

6.3--命名行定义

许多C 的编译器提供了⼀种能⼒,允许在命令⾏中定义符号。⽤于启动编译过程。
例如:当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候,这个特性有点⽤处。(假定某个程序中声明了⼀个某个⻓度的数组,如果机器内存有限,我们需要⼀个很⼩的数组,但是另外⼀个机器内存⼤些,我们需要⼀个数组能够⼤些。)
我们可以通过下面这种方式来实现,这里建议大家使用vs code配置相应的环境来进行操作:
#include <stdio.h>
int main()
{
	int array[ARRAY_SIZE];
	int i = 0;
	for (i = 0; i < ARRAY_SIZE; i++)
	{
		array[i] = i;
	}
	for (i = 0; i < ARRAY_SIZE; i++)
	{
		printf("%d ", array[i]);
	}
	printf("\n");
	return 0;
}

编译指令(linux环境演示):

//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c

七.条件编译 

--在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的,因为我们有条件编译指令。
比如: 调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译。
#include <stdio.h>
#define __DEBUG__
int main()

{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__//如果定义了就可以打印数组
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

常见的条件编译指令: 

1.普通的条件编译:

# if 常量表达式
             //...
# endif
// 常量表达式由预处理器求值。
如:
# define __DEBUG__ 1
# if __DEBUG__
                 //..
# endif

2.多个分支的条件编译(可以利用条件语句来辅助理解):

 #if 常量表达式

               //...
# elif 常量表达式
              //...
# else
             //...
# endif

3.判断是否被定义: 

//如果被定义了就继续
# if defined(symbol)
# ifdef symbol
//如果没被定义就继续
# if !defined(symbol)
# ifndef symbol

4.嵌套指令:

# if defined(OS_UNIX)
            # ifdef OPTION1
                      unix_version_option1();
            # endif
            # ifdef OPTION2
                       unix_version_option2();
            # endif
# elif defined(OS_MSDOS)
            # ifdef OPTION2
                       msdos_version_option2();
            # endif
# endif

八.头文件的包含

8.1--头文件被包含的方式:

 8.1.1--本地文件包含

1.  #include "filename"

查找策略:
  • 先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件。
  • 如果找不到就提示编译错误。
Linux环境的标准头⽂件的路径:
1.   /usr/include

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

 1.  C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include

 2.  // 这是 VS2013 的默认路径

我们这里注意一下,要按照自己的安装路径去找。

8.1.2--库文件包含

1.  #include <filename.h> 

查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤ " "  的形式包含?
答案是肯定可以的,但是这样做查找的效率就低一些,而且这样也不容易区分是库⽂件还是本地⽂件 了。

 8.2--嵌套文件包含

我们已经知道, #include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的地⽅⼀样。

这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。

⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤了。

我们来看看实际例子吧~

test.c:

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"

int main()
{
	return 0;
}

test.h: 


void test();

struct Stu
{
    char name[20];
	int age;
    float score;
	
};
如果直接这样写,test.c⽂件中将test.h包含8次,那么test.h⽂件的内容将会被拷⻉8份在test.c中。
如果test.h ⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家都能使⽤,⼜不做任何的处理,那么后果不堪设想。
那我们如何解决头⽂件被重复引⼊的问题呢? 答案就是使用条件编译。

这里给大家分享两种方法:

方法一:在每个文件的开头写如下内容

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

这个方式首先判断test.h有没有被定义,如果没有就定义一次,这样之后下次再重复出现test.h也没有用了,不符合这里的条件判断,所有可以有效的避免头文件的重复引入。 

方法二:新式的现代写法 

# pragma once

利用这种方式同样也可以有效的避免头文件的重复引入,更简洁一点。 

注意:这里给大家推荐一下《⾼质量C/C++编程指南》中附录的考试试卷的两个笔试题

  • 头⽂件中的 ifndef/define/endif是⼲什么⽤的?
  • #include <filename.h> 和 #include "filename.h" 有什么区别?

如果大家有兴趣的话可以在评论区留言后私信我,我把这本书分享给大家,这其中附录的考试试卷很有参考价值,比较重要。


九.其他预处理指令 

#error

#pragma

# line
...
这里就不做介绍了,感兴趣的可以⾃⼰去了解一下,建议参考《C语⾔深度解剖》学习。
# pragma pack() //在结构体部分介绍过,这个指令可以改变编译器的默认对齐数

往期回顾: 

【C语言预处理详解(上)】--预定义符号,#define定义常量,#define定义宏,带有副作用的宏参数,宏替换的规则,宏和函数的对比

【C语言编译与链接】--翻译环境和运行环境,预处理,编译,汇编,链接

【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写 【通关文件操作(下)】--文件的顺序读写(续),sprintf和sscanf函数,文件的随机读写,文件缓冲区,更新文件

结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了预处理详解中的剩余知识点,如#和##运算符,命名约定,命令行定义 ,#undef,条件编译,头文件的包含,嵌套文件包含,其他预处理指令等,到此C语言专栏的知识点分享也差不多结束了,后续笔者还会继续分享其它的内容,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家长久以来的支持。

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

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

相关文章

03.搭建K8S集群

K8S集群搭建的方式 目前主流的搭建k8s集群的方式有kubeadm、minikube、二进制包三种方式&#xff1a; kubeadm&#xff08;本案例搭建方式&#xff09; 是一个工具&#xff0c;用于快速搭建kubernetes集群&#xff0c;目前应该是比较方便和推荐的&#xff0c;简单易用 kubea…

RDMA简介3之四种子协议对比

RDMA协议共有四种子协议&#xff0c;分别为InfiniBand、iWARP、RoCE v1和RoCE v2协议。这四种协议使用统一的RDMA API&#xff0c;但在具体的网络层级实现上有所不同&#xff0c;如图1所示&#xff0c;接下来将分别介绍这四种子协议。 图1 RDMA四种子协议网络层级关系图 Infin…

【最新版】西陆洗车系统源码全开源+uniapp前端+搭建教程

一.系统介绍 一款基于ThinkPHPUniapp开发的多门店洗车系统&#xff0c;包含用户端&#xff08;小程序&#xff09;、门店员工端&#xff08;小程序&#xff09;、门店端&#xff08;PC&#xff09;、平台管理端&#xff08;PC&#xff09;。 门店分连锁门店和独立门店&#xf…

Linux开发工具(apt,vim,gcc)

目录 yum/apt包管理器 Linux编辑器 vim 1.见一见vim 2.vim的多模式 3.命令模式底行模式等 4.vim的配置 Linux编译器 gcc/g 1.预处理&#xff08;宏替换&#xff09; 2.编译&#xff08;生成汇编&#xff09; 3.汇编&#xff08;生成机器可识别代码&#xff09; 4.连…

鸿蒙Next开发真机调试签名申请流程

背景&#xff1a; 在学习鸿蒙next开发应用的初期总是会遇到一堆的问题&#xff0c;毕竟鸿蒙next开发不管是他的ArKTS语言还是他的开发工具DevEco Studio都还在起步阶段&#xff0c;就像当初的Android起步一样&#xff0c;总会有资料不足的一些问题。就比如我们学习下载完DevEco…

[yolov11改进系列]基于yolov11引入上下文锚点注意力CAA的python源码+训练源码

【CAA介绍】 本文记录的是基于CAA注意力模块的RT-DETR目标检测改进方法研究。在远程遥感图像或其他大尺度变化的图像中目标检测任务中&#xff0c;为准确提取其长距离上下文信息&#xff0c;需要解决大目标尺度变化和多样上下文信息时的不足的问题。CAA能够有效捕捉长距离依赖…

【linux】全志Tina预编译一个so库文件到根文件系统/usr/lib/下

一、sdk中新建文件夹 路径&#xff1a; V:\t113\work3\t113\openwrt\package\feeds\libs\md5util md5util为需要注入的库文件夹。 文件结构 libs md5util files libmd5util.so makefile etc.. 二、编写makefile include $(TOPDIR)/rules.mkPKG_NAME : md5util PKG_VERSIO…

C# 类和继承(成员访回修饰符)

成员访回修饰符 本章之前的两节阐述了类的可访问性。对类的可访问性&#xff0c;只有两种修饰符&#xff1a;internal和public。 本节阐述成员的可访问性。类的可访问性描述了类的可见性&#xff1b;成员的可访问性描述了类成员的可 见性。 声明在类中的每个成员对系统的不同…

Linux-文件管理及归档压缩

1.根下的目录作用说明&#xff1a; /&#xff1a;Linux系统中所有的文件都在根下/bin&#xff1a;(二进制命令目录)存放常用的用户命令/boot&#xff1a;系统启动时的引导文件&#xff08;内核的引导配置文件&#xff0c;grub配置文件&#xff0c;内核配置文件&#xff09; 例…

微软认证考试科目众多?该如何选择?

在云计算、人工智能、数据分析等技术快速发展的今天&#xff0c;微软认证&#xff08;Microsoft Certification&#xff09;已成为IT从业者、开发者、数据分析师提升竞争力的重要凭证。但面对众多考试科目&#xff0c;很多人不知道如何选择。本文将详细介绍微软认证的考试方向、…

Dify工作流实践—根据word需求文档编写测试用例到Excel中

前言 这篇文章依赖到的操作可查阅我之前的文章&#xff1a; dify里的大模型是怎么添加进来的&#xff1a;在Windows本地部署Dify详细操作 flask 框架app.route()函数的开发和调用&#xff1a;PythonWeb开发框架—Flask工程创建和app.route使用详解 结构化提示词的编写&…

【LC实战派】小智固件编译

这篇写给立创吴总&#xff0c;是节前答应他配合git代码的说明&#xff1b;也给所有对小智感兴趣的小伙伴。 请多提意见&#xff0c;让这份文档更有价值 - 第一当然是拉取源码 - git clone https://github.com/78/xiaozhi-esp32.git 完成后&#xff0c;先查看固件中实际的…

jdbcTemplate.query备忘录

jdbcTemplate.query中使用全部字符串和参数注入&#xff0c; 查询速度为什么差距这么大 如何正确使用JdbcTemplate参数化查询 1、使用?占位符 String sql "SELECT * FROM users WHERE name LIKE ?"; List<User> users jdbcTemplate.query(sql,new Object[…

如何搭建Z-Blog PHP版本:详细指南

Z-Blog是一款功能强大且易于使用的博客平台&#xff0c;支持PHP和ASP两种环境。本文将重点介绍如何在PHP环境下搭建Z-Blog博客系统&#xff0c;帮助您快速上线自己的个人博客站点。 准备工作 1. 获取Z-Blog PHP版本 首先&#xff0c;访问Z-Blog官方网站下载最新版本的Z-Blog…

Github Copilot新特性:Copilot Spaces-成为某个主题的专家

概述 当今的工程团队都会面临知识碎片化的问题。关键的上下文分散在代码、文档和团队成员的头脑中&#xff0c;这使得他们很难在一个新的领域快速上手并完成工作。Copilot Spaces 通过集中您的项目上下文解决了这个问题&#xff0c;因此 Copilot 可以根据您的工作提供更智能、…

攻防世界-XCTF-Web安全最佳刷题路线

每次写序都是最烦恼的&#xff0c;都不知道写什么&#xff0c;CTF是团队竞赛&#xff0c;有很多分支&#xff08;Web安全&#xff0c;密码学&#xff0c;杂项&#xff0c;Pwn&#xff0c;逆向&#xff0c;安卓&#xff09;&#xff0c;可以每个领域都涉猎&#xff0c;或许感觉那…

t021-高校物品捐赠管理系统【包含源码材料!!!!】

视频演示地址 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装高校物品捐赠管理系统软件来发挥其高效地信息…

设计模式——面向对象设计六大原则

摘要 本文详细介绍了设计模式中的六大基本原则&#xff0c;包括单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则、依赖倒置原则和合成复用原则。每个原则都通过定义、理解、示例三个部分进行阐述&#xff0c;旨在帮助开发者提高代码的可维护性和灵活性。通过具体代码…

Python制作史莱姆桌面宠物!可爱的

史莱姆桌面宠物 一个可爱的桌面史莱姆宠物&#xff0c;它会在您的任务栏上移动并提供可视化设置界面。 这里写目录标题 史莱姆桌面宠物功能特点安装与运行直接运行方式创建可执行文件 使用说明自定义GIF说明打包说明开源地址 功能特点 可爱的史莱姆在任务栏上自动移动支持…

Dify源码教程:账户和密码传递分析

概述 Dify系统中账户创建过程中的密码处理是Web应用安全的重要环节。本教程详细分析了从前端表单到后端存储的完整流程&#xff0c;展示了Dify如何安全地处理用户凭据。 前端部分 在 dify/web/app/install/installForm.tsx 文件中&#xff0c;当用户填写完表单并点击安装按钮…