018+limou+C语言预处理

news2024/11/14 11:22:23

0.前言

您好,这里是limou3434的一篇博客,感兴趣您可以看看我的其他博文系列。本次我主要给您带来了C语言有关预处理的知识。

1.宏的深度理解与使用

1.1.数值宏常量

#define PI 3.1415926

注意define和#之间是可以留有空格的

1.2.字符宏常量

#include <stdio.h>
//注意下面这个字符串要加上反斜杠得转移,整体也要加上双引号,也可以带上续行符
#define PATH "C:\\users\\\
limou_file"
int main()
{
	printf("%s", PATH);//成功打印
}

1.3.使用宏充当注释

//gcc会好演示一些
#include <stdio.h>
//下面这个语句是没有办法充当注释使用的,因为“先去注释仔宏替换”
#define ANNOTATION //这里得注释被去掉了
int main()
{
  	ANNOTATION printf("abcd\n");//那么这里就是没有内容(空)的宏,所以前面就用空来替代了
	return 0;
}

包含但不仅限于这四个(为什么不直接转为二进制呢?为了站在巨人的肩膀上,因为直接转化成二进制的成本太大,先转化为已有的汇编代码,再由汇编代码转为二进制,则转化成本更小)
1.预处理:头文件展开、去注释、宏替换、条件编译(先“去注释”、“再宏替换”)
2.编译:C语言翻译为汇编语言
3.汇编:将汇编语言转化为可重定向目标文件(可被链接)
4.链接:自身程序+库文件进行关联(静态链接、动态链接),形成可执行程序

1.4.宏定义表达式

  • 宏定义表达式在使用的时候尽量加上括号就行,但是还是有些东西需要我们去注意
#include <stdio.h>
#define INT_VAL(a, b) \
  	a = 0;\
    b = 0; //尽管可以这么写,但是由于分号的存在容易出错
int main()
{
	int x = 10;
  	int y = 20;
  	INT_VAL(x, y);
  	printf("%d %d", x, y);
  	if(1)
		INIT_VAL(x, y);
  	else
    	printf("nihao\n");
  	return 0;
}
//那么如果在宏里面加上花括号呢?也同样不行,如果这么做,有的人写的if else语句比较规范,喜欢加上花括号。这个时候照样出错(花括号后面有分号了,这是if else语句中不被允许的写法),那么有没有什么方法可以在宏里面写入大量的代码块呢?
  • 大块代码的宏编写可以使用do while循环,这种结构也叫“do-while-zero结构”,这是一种大量使用的编码技巧
#define DEF do{/*某些代码*/}while(0)
int main()
{
  	//带花括号
	if(1)
    {
		DEF;
    }
  	else
    {
    	;
    }
  
  	//不带花括号
  	if(1)
      	DEF;
  	else
      	;
}
//这样会先执行循环的判定条件,就完美解决了“带花括号”或“不带花括号”时,带上分号出错的两种情况
  • 宏调用时,宏和“()”之间可以使用空格

1.5.取消宏定义#undef(或叫“限定宏的有效范围”)

  • 在源文件的任何地方宏都可以被定义,宏的作用域从定义开始往后都是有效的。(在宏定义的后方所有代码文本只要有宏的存在都可以被替换,但是写在前面的代码文本哪怕有也不会被替换)
void function(void)
{
	printf("%d", NUM);
}
int main()
{
  	function();
	#define NUM 100
    printf("%d", NUM);
	return 0;
}
  • #undef是为了辅助宏的使用范围,不过在使用的时候也有一些需要注意的地方(其实只需要一条一条宏语句看下去,逐一执行宏替换就行,这样理解宏就不会出问题)

注意一

int main()
{
#define X 3
#define Y X*2 //注意这里的X不会先被前一句语句替换
#undef X
#define X 2
    int z = Y;
  	printf("%d", z);
  	return 0;
}
//结果为4,对于“int z = Y”这条语句来说,可见的只有“#define Y X*2”和“#define X 2”两条语句

注意二

#define M 10
int main()
{
  	printf("%d\n", M);//这里在函数调用之前就被替换了
}
int main()
{
#undef M
  	show();//可以正常打印10
}
  • 宏尽量不在代码块中使用#define和#undef,尽管这是合法的,但是会让人误解这个宏有局部的作用域
  • 尽量使用不同的函数而不使用宏定义表达式,出现问题比较难以调试

2.条件编译的基本使用与理解

条件编译做的是代码裁剪的工作,例如:著名的Linux的内核在功能上也是使用条件编译来进行功能裁剪,来满足不同平台的软件

2.1.条件编译有多种写法

2.1.1.写法一(判断宏是否被定义,在源代码出场率较低)

#ifdef 宏标识符//一般很少写多分支
  	//code1
#elif 宏标识符
  	//code2
#else
  	//code3
#endif

2.1.2.写法二(判断宏是否没被定义,在源代码出场率较低)

#ifndef 宏标识符//一般很少写多分支
  	//code1
#elif 宏标识符
  	//code2
#else
  	//code2
#endif

2.1.3.写法三(判断宏是真还是假,在源代码出场率还可以)

#if 常量表达式(如果宏没被定义,默认为假。如果是空宏,则会报错)
  	//code1
#elif 常量表达式
  	//code2
#else
  	//code3
#endif

2.1.4.写法四(使用#if实现#ifdef和#ifndef)

从以下的代码可以看到#if可以实现的功能很多,完全可以替代很多的条件编译指令

//1.检测宏是否被定义#ifdef
#if define(宏名)
	//某些code
#else
  	//某些code
#endif
  
//2.测宏是否没被定义#ifndef
#if !define(宏名)
  	//某些code
#else
  	//某些code
#endif
  
//3.检测两个以上的宏是否都被定义
#if (define(1) && define(2))//最外层的括号最好加上,更加严谨
  	//code1
#else
  	//code2
#endif
  
//4.检测两个以上的宏中的其中一个是否没被定义 
#if !(define(1) && define(2))//最外层的括号最好加上,更加严谨
  	//code1 
#else 
  	//code2 
#endif
  
//5.检测两个以上的宏的其中一个是否被定义 
#if (define(1) || define(2))//最外层的括号最好加上,更加严谨 
  	//code1 
#else 
  	//code2 
#endif

2.1.5.写法五(条件编译的嵌套)

//条件编译是允许多层嵌套的
#if define(宏名)
  	#if define(宏名)
  		//code1
  	#endif
#else
  	//code2
#endif

2.2.“宏是/否被定义”和“宏为真/假”

  • 这两种是不一样的:宏定义不管真假,宏真假必定是有被定义的。
  • 不过注意#define 某标识符这种写法也算是定义了宏,即使后面没有任何值,这种的宏可以叫作“空宏”

2.3.条件编译的意义

快速实现版本维护、方便代码在不同平台移植

2.4.在命令行中定义宏(但是这样的应用场景不多)

  • 比如linux下的命令
gcc .c文件 -D 宏名=宏值
  • 在VS2022中,可以到“选中项目->属性->配置属性->C/C+±>预处理器(相当于Linux下的命令)->预处理定义->将里面追加“;宏名=宏值”。这样不在源文件中定义宏也可以正常使用宏。(请注意,在C语言中C、CPP等名称会有些敏感,可能会出现一些不可预知的错误,请尽量不出现这样的宏名)

2.5.文件包含的本质

2.5.1.避免重复包含头文件的方法

为了避免头文件被重复包含,有两种方式:一是使用“#pragma once”,而是使用“条件编译”

#ifndef _TEST_H_ //根据自己的头文件名字命名
#define _TEST_H_ //根据自己的头文件名字命名
	//然后放入一些头文件的内容
#endif

2.5.2.头文件展开的本质

那么什么是头文件展开呢?可以简单理解为将头文件内容“拷贝”到目标源文件,但是这种“拷贝”是经过一定处理的

3.“#”和“##”符号

首先需要做一些铺垫,在C语言里多个字符串会自动连接,即:“abcd"和"efgh"这两串字符串如果相邻,就会连接在一起成为"abcdefgh”,C语言将两者视为一串字符串

3.1.“#”符号

本质是将对应的字面值转化为字符串(这一替换过程在Linux下gcc的预处理中会更加清晰)

int main()
{
#define STR(X) #X
	printf("PI:"STR(3.1415926)"\n");//这里被替换程“#3.1415926”,然后C语言将这样带有#的内容视为字符串

	return 0;
}//直接打印“PI:3.1415926”

而这一特性还可以利用起来:写成转化字面值为字符串的宏定义,而非直接写算法做处理

#define TOSTRING(S) #S
int main()
{
	char str[64] = TOSTRING(1000000);//但是注意,这个括号有什么就转化什么,放入变量的话只会打印变量的名字(实际上这个时候变量也还没开辟空降)
	printf("%s", str);
	return 0;
}

3.2.“##”符号

将宏参组合形成一个全新的符号(不是字符串,要区分开来)

#define NUMBER(n) number##n 
int main()
{
	int NUMBER(1) = 100; 
	int NUMBER(2) = 1000;
	int NUMBER(3) = 10000;
	printf("%d\n", NUMBER(1));
	printf("%d\n", NUMBER(2));
	printf("%d\n", NUMBER(3));
	return 0;
}

在这里插入图片描述

4.常见的预处理符号

除了#define、#include、#ifdef等,还有一些比较常用的,但是有的部分我只是列出没做解释,您可以试着查询一下。

4.1.#pragma

用于给编译器传递指令或控制编译器的行为

4.1.1.#pragma message():编译时的消息提醒

在这里插入图片描述

和#error最大的区别是:代码会通过,只是做一个提醒

4.1.2.#pragma once:防止头文件被包含

4.1.3.#pragma code_seg

4.1.4.#pragma hdrstop

4.1.5.#pragma warning

比如:#pragma warning(disable:4996)

4.1.6.#pragma comment

4.1.7.#pragma pack()

4.2.#error

用于输出错误信息并停止编译,其最核心的作用就是可以自定义编译错误
在这里插入图片描述

4.3.#line

用于定制代码行号和文件名称
预定义符号“FILE”在预处理期间做处理,打印文件名
预定义符号“LINE”在预处理期间做处理,打印当前行号
在这里插入图片描述

4.4.#warning

用于输出警告信息

4.5.#include_next

用于引用下一个同名的头文件,主要是用于避免头文件重复包含的问题

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

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

相关文章

是德DSO9254A示波器/KEYSIGHT DSO9254A:2.5 GHz

KEYSIGHT是德DSO9254A示波器&#xff0c;Infiniium 9000 系列 2.5 GHz 示波器提供 4 个模拟通道、10 Mpts 存储器和 20 GSa/s 采样率。 简介 Keysight(原Agilent) Infiniium DSO9254A 配有 15 英寸 XGA 显示屏&#xff0c;而且包装非常轻巧&#xff0c;仅有 9 英寸深、26 磅重…

C++ 编写二维码(有源码)

首先来展示一下成果&#xff1a; 二维码图片好像违规了&#xff0c;直接给链接吧网址链接 如果你扫了这个二维码就会得到一个网址&#xff0c;该网址是我写代码的参考&#xff0c;该网站讲述了如何编写一个二维码&#xff0c;很详细&#xff0c;我没有实现汉字的编码&#xff…

LeetCode ! 42 Trapping Rain Water

参考资料&#xff1a;leetCode评论区大佬, 《程序员代码面试指南》 思路1&#xff1a;使用单调栈 维持一个从栈底到栈顶中的元素——下标&#xff0c;对应到数组是从大到小排序。 遍历数组&#xff0c;如果新值大于栈顶元素&#xff08;下标&#xff09;对应的数组值&#xf…

『Linux』第九讲:Linux多线程详解(六 - 完结)_ 线程池 | 读写锁

「前言」文章是关于Linux多线程方面的知识&#xff0c;上一篇是 Linux多线程详解&#xff08;五&#xff09;&#xff0c;今天这篇是 Linux多线程详解&#xff08;六&#xff09;&#xff0c;也是多线程最后一篇&#xff0c;内容大致是线程池&#xff0c;讲解下面开始&#xff…

什么?英语不好?这所211可以不考英语!

本期为大家整理热门院校“哈尔滨工程大学810”的择校分析&#xff0c;这个择校分析专题会为大家结合&#xff1a;初试复试占比、复试录取规则&#xff08;是否公平&#xff09;、往年录取录取名单、招生人数、分数线、专业课难度等进行分析。希望能够帮到大家! –所有数据来源…

KDJJC-80绝缘油介电强度测试仪

一、概述 测试仪&#xff08;单杯&#xff09;是我公司科研技术人员&#xff0c;依据国家标准GB507-1986及行标DL/T846.7-2004的有关规定&#xff0c;发挥自身优势&#xff0c;经过多次现场试验和长期不懈努力&#xff0c;精心研制开发的高准确度、数字化工业仪器。 为满足不同…

初步了解SpringCloud微服务架构

✅作者简介&#xff1a;大家好&#xff0c;我是Cisyam&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Cisyam-Shark的博客 &#x1f49e;当前专栏&#xff1a; 微服务探索之旅 ✨特色专…

Apikit SaaS 10.9.0 版本更新:接口测试支持通过 URL 请求大型文件,覆盖更多场景的文件请求测试

Hi&#xff0c;大家好&#xff01; Eolink Apikit 即将在 2023年 6月 8日晚 18:00 开始更新 10.9.0 版本。本次版本更新主要是对多个应用级资源合并&#xff0c;并基于此简化付费套餐和降低费率。 本次应用合并是为了接下来更好的发挥 Eolink Apikit 的优势&#xff0c;提供 …

Web前端-React学习

React基础 React 概述 React 是一个用于构建用户界面的JavaScript库。 用户界面&#xff1a; HTML页面&#xff08;前端&#xff09; React主要用来写HTML页面&#xff0c; 或构建Web应用 如果从MVC的角度来看&#xff0c;React仅仅是视图层&#xff08;V&#xff09;,也就…

多目标建模loss为什么最好同时收敛?

多目标的多个loss是否同时收敛最好&#xff1f; 假设 task A的独有参数 W a W_a Wa​task B的独有参数 W b W_b Wb​task A和 task B的共享的参数 W s W_s Ws​ 那么 l o s s l o s s a l o s s b loss loss_a loss_b losslossa​lossb​ 假设损失函数为 f f f&…

【DepthFilter】深度滤波器

14讲P326-327 函数实现一个深度滤波器&#xff0c;用于计算图像中某个像素点的深度值。算法步骤的含义和含义&#xff1a; 将当前帧的像素点和参考帧的像素点通过三角化计算深度。将参考帧到当前帧的变换矩阵 T_C_R 转换为当前帧到参考帧的变换矩阵 T_R_C。将参考帧像素点 pt_…

Docker超详细基础使用(带图)

目录 安装ubuntu 基本使用命令 docker run 容器名 延伸命令 启动ubuntu 查看所有正在运行的容器 指定容器别名启动 doker ps 延伸命令 退出容器 重新进入正在运行的容器 启动容器 删除已停止的容器 强制删除容器 查看容器日志 查看容器内部运行的进程 ​编辑 查看容…

Axure教程—分段滑动条

本文将教大家如何用AXURE中动态面板制作单分段滑动条 一、效果 预览地址&#xff1a;https://c00qrq.axshare.com 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87881401?spm1001.2014.3001.5503 二、功能 滑块滑动到相应的浮点&#xff0c;显示…

【SVN】SVN查看日志时报错:联系服务器时出现问题,条目不可读

目录 0.背景介绍 1.问题原因 2.解决步骤 0.背景介绍 环境&#xff1a;SVN服务器在ubuntu下&#xff0c;SVN客户端在windows下。 最近在搭ubuntu下的SVN的服务器&#xff0c;然后再windows下用SVN客户端将文件上传至服务器保管。 windows下想查看日志时&#xff0c;报错【…

React学习7 redux

redux的三个核心概念 1. action 动作的对象包含2个属性 type&#xff1a;标识属性, 值为字符串, 唯一, 必要属性data&#xff1a;数据属性, 值类型任意, 可选属性例子&#xff1a;{ type: ADD_STUDENT,data:{name: tom,age:18} } 2. reducer 用于初始化状态、加工状态。加工…

健身器材开发方案,带有12位ADC检测、LED屏显的语音IC-N9300

身体锻炼过程中所使用到的所有物品&#xff0c;健身器材类体育用品则主要涉及健身领域&#xff0c;包括室外健身器材和室内健身器材。 每天清晨或傍晚跑跑步&#xff0c;不仅能提高身体素质同时能得到很好的瘦身效果。然而大部分人觉得慢跑等运动过于无聊没有给予运动者本身进行…

【Redis编译安装】---redis-4.0.8

【Redis编译安装】---redis-4.0.8 &#x1f53b; 一、Redis 编译安装1.1 ⛳ 上传解压1.2 ⛳ 升级gcc环境1.3 ⛳ 编译安装1.3.1 &#x1f341;cd 到redis解压目录1.3.2 &#x1f341;编译1.3.3 &#x1f341; make test1.3.4 &#x1f341; 安装tcl-8.51.3.5 &#x1f341; 安装…

shell 第十一章

1.写一个库函数&#xff0c;用定时任务调用这个库函数&#xff0c;每月1号执行 1.sh: 1.1.sh: 2.以免交互的方式实现 ssh 远程登录&#xff0c;密码错误也直接退出&#xff0c;不用人干预 3.以免交互的方式&#xff0c;实现磁盘分区、格式化、挂载

Keysight 34970A数据采集记录仪产品介绍

Keysight 34970A数据采集记录仪 Keysight 34970A数据采集记录仪开关单元由一个 3 插槽主机和一个内置的 6 1/2 位数字万用表组成。每个通道可以单独配置&#xff0c;以测量 11 种不同功能之一&#xff0c;这样既不会增加成本&#xff0c;也不必使用复杂的信号调理附件。您可用…

【干货】PCB材料选择与性能比较

PCB板被广泛应用于电子行业&#xff0c;作为电子设备的重要组成部分之一&#xff0c;负责连接各种电子元件。PCB板的性能直接影响着电子设备的质量和稳定性。而PCB板的材料选择则是影响PCB板性能的关键因素之一。本文将对常见PCB材料进行比较分析&#xff0c;以便于选择适合的材…