【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_max

int 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 == 0
	printf("hello\n");
#elif M == 1
	printf("world\n");
#elif M == 2
	printf("csdn\n");
#endif
	printf("886\n");

	return 0;
}

  

11.3、判断是否被定义

#if defined(symbol)
#ifdef symbol

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

在这里插入图片描述

  

11.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

  

十二、头文件的包含

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.mfbz.cn/a/794209.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GO channel 学习

引言 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换&#xff0c;但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性&#xff0c;必须使用互斥量对内存进行加锁&#…

使用 Apache DolphinScheduler 构建和部署大数据平台,将任务提交至 AWS 的实践经验

作者介绍 李庆旺 - 软件开发工程师&#xff0c;思科 引言 大家好&#xff0c;我是李庆旺&#xff0c;来自思科的软件开发工程师。我们的团队已经使用Apache DolphinScheduler搭建我们自己的大数据调度平台近三年时间。从最初的2.0.3版本开始至今&#xff0c;我们与社区一同成…

k8s集群部署mysql8主备

一、搜索mysql8版本 # helm search repo mysql# helm pull bitnami/mysql --version:11.1.2# tar -zxf mysql-11.1.2.tgz# cd mysql 二、修改value.ysqml文件 动态存储类自己提前搭建。 # helm install mysql8 -n mysql-cluster ./ -f values.yaml NAME: mysql8 LAST DEPLOYED…

Prototype, POC, MVP:区别与比较

在软件开发和产品设计领域&#xff0c;Prototype&#xff08;原型&#xff09;、Proof of Concept&#xff08;概念证明&#xff0c;简称POC&#xff09;和Minimum Viable Product&#xff08;最小可行产品&#xff0c;简称MVP&#xff09;是三个重要的概念。它们各自在项目的不…

MySQL数字相关数据处理函数

目录 1. 随机数生成 rand ( ) 2. 四舍五入 round&#xff08;&#xff09; 3. 舍去 truncate ( ) 4. 向上/下取整 5. 空处理 ifnull&#xff08; x , y &#xff09; 1. 随机数生成 rand ( ) rand ( ) 生成 0 到 1 的随机数&#xff1b; rand ( x ) 生成 0 到 1 的随机数…

什么是海外仓管理自动化?策略及落地实施步骤指南

作为海外仓的管理者&#xff0c;你每天都面临提高海外仓运营效率、降低成本和满足客户需求的问题。海外仓自动化管理技术为这些问题提供了不错的解决思路&#xff0c;不过和任何新技术一样&#xff0c;从策略到落地实施&#xff0c;都有一个对基础逻辑的认识过程。 今天我们整…

FastAPI 学习之路(四十三)路径操作的高级配置

在实际开发中&#xff0c;可能我们有些接口不能在接口文档中与其他业务接口一样开放给前端或者其他对接人&#xff0c;那么我们肯定会想着在接口文档中对其进行屏蔽隐藏操作&#xff0c;那么可以实现吗&#xff1f; 接口文档中隐藏接口 当然&#xff0c;还很简单&#xff0c;…

Java中的代理机制

代理的概述 代理&#xff1a;给目标对象提供一个代理对象&#xff0c;并且由代理对象控制着对目标对象的引用&#xff08;跟多态一样&#xff09; mybatis、spring都运用了代理机制 跳过创建类的过程&#xff0c;直接产生对象 简单来说&#xff0c;我们使用代理对象来代替对…

【《无主之地3》风格角色渲染在Unity URP下的实现_角色渲染(第四篇) 】

文章目录 概要描边问题外秒变分叉解决办法1:测试效果如下:外秒变分叉解决办法2:URP管线下PBR渲染源码关键词解释:完整shader代码如下:URP管线下二次元皮肤渲染源码URP管线下二次元头发渲染源码简要介绍文章的目的、主要内容和读者将获得的知识。 概要 提示:《无主之地3》…

华贝甄选干细胞科技,揭秘生命修复的奥秘

在探索生命奥秘的漫漫征途中&#xff0c;华贝甄选凭借干细胞科技的神奇力量&#xff0c;为您点亮健康与活力的希望之光。 我们深知&#xff0c;细胞是生命的基石&#xff0c;而干细胞则是这基石中蕴含的无限潜能。华贝甄选精心打造的干细胞疗法&#xff0c;如同神奇的魔法&…

【排序算法】插入排序(希尔排序)

目录 一.直接插入排序 1.基本思想 2.实现 3.特性 1.效率 2.时间复杂度&#xff1a;O(N^2) 3.空间复杂度&#xff1a;O(1) 4.稳定性&#xff1a;稳定 二.希尔排序 1.基本思想 2.实现 3.特性 1.效率 2.时间复杂度&#xff1a;O(N^1.3) ​编辑 3.空间复杂度&#xff…

《植物大战僵尸杂交版》2.2:新版本体验与下载指南

作为《植物大战僵尸》系列的忠实粉丝&#xff0c;我最近发现了一款令人兴奋的改版游戏——《植物大战僵尸杂交版》2.2。这款游戏不仅保留了原作的经典元素&#xff0c;还加入了一些创新的玩法&#xff0c;让我忍不住想要分享给大家。 2.2版本新体验 新僵尸登场 最新版本中&am…

vue3 - vue项目自动检测更新

GitHub Demo 地址 在线预览 web项目当页面检测到需要更新&#xff0c;然后弹框提示是否更新&#xff08;刷新页面&#xff09;这种可以通过纯前端实现也可以通过接口实现 接口实现&#xff1a;通过调用接口轮询和本地的版本号比较&#xff0c;检查是否需要弹框提示更新纯前端实…

移动应用稳定性测试

移动应用稳定性测试 使用Monkey等工具进行移动应用稳定性测试是一种常见的自动化测试方法。Monkey工具可以自动生成各种随机事件来模拟用户操作&#xff0c;从而测试应用在不同情况下的表现。在执行monkey命令后&#xff0c;主要观察以下的结果信息来评估移动应用的稳定性。 崩…

【PyQt】

PyQT5线程基础&#xff08;2&#xff09; 线程案例案例一案例二 线程案例 案例一 案例一代码通过线程实现点击按钮向线程传输地址&#xff0c;程序等待20秒后&#xff0c;返回结果。 通过QtDesigner创建如下图所示的界面ui&#xff0c;并用UIC工具转成对应的py文件。 main文…

作业一:ER图 作业:二QQ项目思路 作业三:实现QQ的登录与注册界面

一、ER图 二、QQ项目思路&#xff1a;客户端功能&#xff0c;服务器端功能的实现 1.登录注册&#xff1a; 将基本信息如手机号码&#xff0c;验证码&#xff0c;还有已有的账号及账号相关信息等存入数据库中&#xff0c;登录方式为账号密码登录&#xff0c;还有忘记密码用邮箱…

深度学习DeepLearning多元线性回归 学习笔记

文章目录 多维特征变量与术语公式多元线性回归正规方程法Mean normalizationZ-score normalization设置合适的学习率Feature engineering 多维特征 变量与术语 列属性xj属性数n x ⃗ \vec{x} x (i)行向量某个值 x ⃗ j i \vec{x}_j^i x ji​上行下列均值μ标准化标准差σsigm…

为什么渲染农场渲染的是帧,而不是视频?

在3D动画产业的壮阔画卷中&#xff0c;渲染农场作为幕后英雄&#xff0c;以其庞大的计算能力支撑起无数视觉奇观的诞生。这些由高性能计算机集群构成的系统&#xff0c;通过独特的逐帧渲染策略&#xff0c;解锁了单机难以企及的创作自由与效率。本文将深入剖析这一策略背后的逻…

odoo 自定义菜单模型等进行报表输出

由于个性化需求&#xff0c;要定义不同报表不同条件搜索&#xff0c; 所以自定义有如下&#xff1a; 模型字段权限菜单 功能如下&#xff1a; 启用&#xff1a;创建新菜单、form视图、action动作 前提&#xff1a;模型已经创建好&#xff0c; 禁用&#xff1a;对菜单进行归档…

PMP–计算--图示

文章目录 概念基准绩效预测 公式 概念 基准绩效 最常见的基准是成本和进度。跟踪范围或技术基准的项目可以使用可交付物测量指标中的信息。 大多数进度测量指标会根据以下相关的计划绩效来跟踪实际绩效&#xff1a; ▶ 开始日期和完成日期。将实际开始日期与计划开始日期进行…