C语言_预处理详解
1. 预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的
1 __FILE__ //进行编译的源文件
2 __LINE__//文件当前的行号
3 __DATE__ //文件被编译的日期
4 __TIME__//文件被编译的时间
5 __STDC__//如果编译器遵循ANSI C,其值为1,否则未定义
举个例子:
printf("file:%s line:%d\n",__FILE__,__LINE__);
2. #define 定义常量
语法:
#define name stuff
示例如下:
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
//如果定义的stuff太长,可以分为几行写,除了最后一行外,每行的后面都要加一个续行符(反斜杠)
#define DEBUG_PRINT printf("file:%s\tline:%s\t \date:%s\ttime:%s\n",\__FILE__,__LINE__,\__DATE__,__TIME__);
还有个问题,我们使用define定义标识符的时候,要不要最后加;
?
我建议不要,因为可能会导致一些问题。
比如:
#define MAX 1000;if(condition)max=MAX;
elsemax=0;
我们都知道,没有大括号的if
语句只能跟一条语句,而if
后面的语句经过替换后会有两个 ;
,也就是两个语句,所以会出现语法错误
3. #define 定义宏
本质:带参数的文本替换。类似函数但无函数调用的开销
这种实现被称为宏(macro)或定义宏(define macro)
宏的申明方式如下:
#define name(parament-list) stuff
parament-list
是一个由逗号隔开的符号表,可能出现在stuff
注:
参数列表的左括号必须和name
紧邻,如果两者之间有空格存在,参数列表就会被解释为stuff
的一部分
4. 带有副作用的宏参数
当宏参数在宏的定义中出现的次数超过一次时,如果参数带有副作用,那使用这个宏就可能出现问题
例如:
#define MAX(a,b) ((a)>(b) ? (a) : (b))x=5;
y=8;
z=MAX(x++,y++);
printf("x=%d y=%d z=%d\n",x,y,z);
预处理器处理之后宏展开的样子如下:
z=((x++) > (y++) ? (x++) : (y++));
所以输出结果为:
x=6 y=10 z=9
5. 宏替换的规则
在程序中扩展#define定义符号和宏时,涉及如下几步:
- 在调用宏时,首先对参数进行检查,看看是否包含由
#define
定义的符号。如果是,则首先被替换 - 替换文本随后被插入到程序中原来文本的位置
- 最后,再次对结果文件进行扫描,看它是否包含任何由
#define
定义的符号。如果包含,就重复上述处理过程
注:
- 宏参数和
#define
定义中可以出现其他#define`定义的符号。但是对于宏,不能出现递归 - 当预处理器搜索
#define
定义的符号的时候,字符串常量的内容并不被搜索
6. 宏和函数的对比
宏通常被应用于执行简单的运算
如在两个数中找出较大的一个时:
#define MAX(a, b) ((a)>(b)?(a):(b))
那么问题来了,为什么不用函数呢?
原因有两个:
- 用于调用函数和从函数返回的代码可能比执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
- 函数的参数必须申明为特定的类型,所以函数只能在类型适合的表达式上使用。而宏的参数是跟类型无关的,任何类型都可以参与
和函数相比宏的劣势:
- 每次使用宏时,一份宏定义的代码将插入到程序中。若宏比较长,会大幅度增加程序的长度
- 宏是没法调试的
- 宏由于与类型无关,也就不够严谨
- 宏可能会带来运算符优先级的问题,从而导致程序可能出错
宏有时也能做到函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到
如:
#define MALLOC(num,type) \(type*)malloc(num *sizeof(type))MALLOC(10,int);//类型作为参数
//预处理器替换之后:
(int*)malloc(10*sizeof(int));
7. # 和##
7.1 #运算符
#运算符会将宏的一个参数转换为字符串字面量。它仅被允许出现在带参数的宏的替换列表中
示例如下:
当有一个变量int a=10;
的时候,想打印出:the value of a is 10
#define PRINT(n) printf("the value of "#n" is %d",n);int a=10;
PRINT(a);//会打印出the value of a is 10
PRINT(a)
会被预处理为:
printf("the value of ""a" " is %d", a);
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); \
}
使用宏,定义不同函数:
GENERIC_MAX(int) //替换到宏体内后int##_max 生成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max 生成了新的符号 float_max做函数名int main()
{//调用函数int m = int_max(2, 3);printf("%d\n", m);//输出 3float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);//输出 4.500000return 0;
}
在实际开发过程中##
的使用很少,很难有非常贴切的例子
8. 命名约定
函数与宏的使用语法很相似,所以语言无法帮我们区分二者。
所以我们要有的一个习惯是:
把宏名全部大写;函数名不要全部大写
9. #undef
如果现存的一个宏名字需要被重新定义,那么它需要先被移除。这时我们就要用到了#undef
如下:
#undef NAME
10. 命令行定义
许多C 的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同⼀个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了⼀个某个长度的数组,如果机器内存有限,我们需要⼀个很小的数组,但是另外⼀个机器内存大些,我们需要⼀个数组能够大些。)
#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 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c
11. 条件编译
在编译一个程序的时候我们要将一条语句(或一组语句)编译或者编译是很方便的。我们可以用条件编译语句
比如:
调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译
#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__//..
#endif2.多个分⽀的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol4.嵌套指令
#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. 头文件的包含
12.1 本地文件包含
#include"filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件
如果找不到就提示编译错误
linux环境下标准头文件的路径:
/usr/include
VS环境下标准头文件的路径:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径
12.2 库文件包含
#include<filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库⽂件也可以使用 “”
的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件了。
12.3 重复包含问题
test.h
void test();
struct Stu
{int id;char name[20];
};
test.c
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"int main()
{return 0;
}
若test.c文件中将test.h包含5次,那么test.h文件的内容将会被拷贝5份在test.c中
如果test.h 文件比较大,这样预处理后代码量会剧增。如果⼯程比较大,有公共使用的头文件,被大家都能使用,用不做任何的处理,那么后果真的不堪设想
如何解决头文件被重复引入的问题?
答:条件编译
每个头⽂件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或
#pragma once
这样就避免了头文件的重复引入
相关文章:
C语言_预处理详解
1. 预定义符号 C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的 1 __FILE__ //进行编译的源文件 2 __LINE__//文件当前的行号 3 __DATE__ //文件被编译的日期 4 __TIME__//文件被编译的时间 5 __STDC__//如果编译器遵循ANSI…...
将前后端分离版的前端vue打包成EXE的完整解决方案
将若依前后端分离版的前端打包成EXE的完整解决方案 将若依前后端分离版的Vue前端打包成Windows可执行文件(.exe),同时保持与后端API的通信,需要使用Electron框架来实现。下面是详细的步骤和解决方案。 一、准备工作 1. 环境要求 Node.js (推荐v16+)npm 或 yarn若依前后端分…...
物联网协议之MQTT(一)基础概念和设备
前言: 本文内容均以实战出发,像MQTT概念这种基础内容建议大家自行百度。 推荐资料: mica-mqtt文档 一、MQTT简单介绍 作为当今物联网的主流协议,MQTT的使用范围非常广,如果你想了解甚至是从事物联网行业,…...

「Java教案」Java程序的构成
课程目标 1.知识目标 能够按照Java标识符的命名规则,规范变量的命名。能够区分Java中的关键字与保留字。能够对注释进行分类,根据注释的用途合理的选择注释方式。 2.能力目标 能编写符合规范的标识符。能识别Java中的关键字和…...
还原Windows防火墙
还原Windows防火墙 1. 背景2. 为何“还原”完胜“关闭”?三大核心优势3. 还原防火墙默认值操作步骤4. 还原防火墙时,系统背后的工作5. 需要还原防火墙场景一招拯救混乱网络!还原Windows防火墙,找回你的“安全速度”1. 背景 你是否曾因一时误操作关闭了Windows防火墙?是…...

区块链可投会议CCF B--EDBT 2026 截止10.8 附录用率
Conference:EDBT: 29th International Conference on Extending Database Technology CCF level:CCF B Categories:数据库/数据挖掘/内容检索 Year:2026 Conference time:24th March - 27th…...

经典ReLU回归!重大缺陷「死亡ReLU问题」已被解决
来源 | 机器之心 在深度学习领域中,对激活函数的探讨已成为一个独立的研究方向。例如 GELU、SELU 和 SiLU 等函数凭借其平滑梯度与卓越的收敛特性,已成为热门选择。 尽管这一趋势盛行,经典 ReLU 函数仍因其简洁性、固有稀疏性及…...

在VSCode中开发一个uni-app项目
创建项目 使用命令行工具(例如 vue-cli)来创建一个新的 uni-app 项目。 创建以JavaScript开发的工程 npx degit dcloudio/uni-preset-vue#vite my-vue3-project //或者 npx degit dcloudio/uni-preset-vue#vite-alpha my-vue3-project创建以TypeScript…...
quic为什么没有被大规模应用?
一、成本 将应用程序从 HTTP/2 迁移到 HTTP/3,或从 TCP 迁移到 UDP 需要付出一定的努力。它需要将整个应用层实现和传输层实现转换到UDP,并在服务器端和客户端构建一个全新的解决方案。对于资源有限的小型流媒体供应商来说,这是一个不小的挑…...
Delft3D软件介绍及建模原理和步骤;Delft3D数值模拟溶质运移模型建立;地表水环境影响评价报告编写思路
📚 教程以地表水数值模拟软件 Delft3D 4.03.00 的操作为核心内容,系统涵盖地表水水动力建模、基础资料获取、边界条件设定、模型率定与验证以及数据分析处理等关键环节。通过全面讲解,学员将掌握地表水数值模拟的全过程实际操作技术。 &…...
书籍在其他数都出现k次的数组中找到只出现一次的数(7)0603
题目 给定一个整型数组arr和一个大于1的整数k。已知arr中只有1个数出现了1次,其他的数都出现了k次,请返回只出现了1次的数。 解答: 对此题进行思路转换,可以将此题,转换成k进制数。 k进制的两个数c和d,…...
开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-function_tool(二)
一、前言 在人工智能技术迅猛发展的今天,OpenAI Agents SDK 为开发者提供了一个强大的工具集,用于构建基于 Python 的智能代理应用。这些代理可以执行从简单任务到复杂决策的一系列操作,极大地提升了应用程序的智能化水平。 通过 OpenAI Agents SDK,可以利用 Python 编程语…...

Python - 爬虫;Scrapy框架之插件Extensions(四)
阅读本文前先参考 https://blog.csdn.net/MinggeQingchun/article/details/145904572 在 Scrapy 中,扩展(Extensions)是一种插件,允许你添加额外的功能到你的爬虫项目中。这些扩展可以在项目的不同阶段执行,比如启动…...

Spark实战能力测评模拟题精析【模拟考】
1.println(Array(1,2,3,4,5).filter(_%20).toList() 输出结果是(B) A. 2 4 B. List(2,4) C. List(1,3,5) D. 1 3 5 2.println(Array("tom","team","pom") .filter(_.matches("")).toList) 输出结果为(List(tom,…...

【OSG学习笔记】Day 15: 路径动画与相机漫游
本章来学习下漫游相机。 路径动画与相机漫游 本届内容比较简单,其实就是实现物体的运动和相机的运动 当然这两个要一起执行。 贝塞尔曲线 贝塞尔曲线(Bzier curve)是一种在计算机图形学、动画制作、工业设计等领域广泛应用的参数曲线&am…...

PostgreSQL(PostGIS)触发器+坐标转换案例
需求,只录入一份坐标参考为4326的数据,但是发布的数据要求坐标必须是3857 对这种需求可以利用数据库触发器实现数据的同步 步骤: 1. 使用ArcGIS Pro创建一个名字为testfc_4326的图层,坐标参考为4326 2. 使用Pro再创建一个名字…...

Constraints and Triggers
目录 Kinds of Constraints Single-Attribute Keys Multiattribute Key Foreign Keys Expressing Foreign Keys Enforcing Foreign-Key Constraints Actions Taken Attribute-Based Checks Timing of Checks Tuple-Based Checks Assertions Timing of Assertion Ch…...
基于windows系统的netcore架构与SqlServer数据库,实现双机热备。
以下是基于 SQL Server Always On 可用性组 和 故障转移群集 的详细配置步骤,用于实现双机热备。 步骤 1:准备环境 1.1 硬件和软件准备 两台服务器:分别作为主服务器和备用服务器。SQL Server版本:确保两台服务器上安装的SQL S…...
【转bin】EXCEL数据转bin
如果DEC2BIN函数的默认设置无法满足需求(它最多只能处理10位的二进制转换),可以通过VBA宏方法来处理较大数的二进制转换并提取特定位置的数字: 十进制转二进制(不限位宽) 1、打开VBA编辑器(Al…...

BERT:让AI真正“读懂”语言的革命
BERT:让AI真正“读懂”语言的革命 ——图解谷歌神作《BERT: Pre-training of Deep Bidirectional Transformers》 2018年,谷歌AI团队扔出一篇核弹级论文,引爆了整个NLP领域。这个叫BERT的模型在11项任务中屠榜,甚至超越人类表现…...
【计算机组成原理】SPOOLing技术
SPOOLing技术 关键点内容核心思想通过输入/输出井虚拟化独占设备,实现共享,即让多个作业共享一台独占设备依赖条件1. 外存(井文件)2. 多道程序设计虚拟实现多道程序技术磁盘缓冲数据流方向输入设备 → 输入井 → CPU → 输出井 →…...

冷雨泉教授团队:新型视觉驱动智能假肢手,拟人化抓握技术突破,助力截肢者重获生活自信
研究背景:日常生活中,健康人依靠手完成对物体的操作。对于手部截肢患者,手部的缺失导致他们难以有效地操作物体,进而影响正常的日常生活。拥有一个能够实现拟人地自然抓取多种日常物体的五指动力假手是手部截肢患者的夙愿…...
CanvasGroup篇
🎯 Unity UI 性能优化终极指南 — CanvasGroup篇 🧩 什么是 CanvasGroup? CanvasGroup 是UGUI的透明控制器,用于整体控制一组UI元素的: 可见性 (alpha)交互性 (interactable)射线检测 (blocksRaycasts) 🎯…...
[Java 基础]银行账户程序
编写一个 Java 控制台应用程序,模拟一个简单的银行账户。该程序应允许用户执行以下操作: 查询账户余额。 账户初始余额设置为 1000.0 元。向账户存入资金。 用户可以输入存款金额,程序应更新账户余额。存款金额必须为正数。从账户提取资金。…...
2025.6.4总结
工作:今天效率比较高,早上回归4个问题,下午找了3个bug,晚上二刷了科目一(贪吃蛇系统),写了四个点,唯一没达标的就是两自动化没完成。美中不足的是电脑上下载不了PC版的番茄工作软件。…...
将音频数据累积到缓冲区,达到阈值时触发处理
实现了音频处理中的 AEC(声学回声消除)和 AES(音频增强)功能,其核心功能是: 数据缓冲管理:将输入的麦克风和扬声器音频数据块累积到缓冲区中块处理机制:当缓冲区填满预设大小&#…...

pikachu靶场通关笔记14 XSS关卡10-XSS之js输出(五种方法渗透)
目录 一、源码分析 1、进入靶场 2、代码审计 二、渗透实战 1、根据提示输入tmac 2、XSS探测 3、注入Payload1 4、注入Payload2 5、注入Payload3 6、注入Payload4 7、注入Payload5 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关)渗透集合&#x…...
5.Promise,async,await概念(1)
Promise 是 JavaScript 原生提供的异步处理机制,而 async 和 await 是基于 Promise 的语法糖,由 JavaScript 语言和其运行时环境(如浏览器、Node.js)支持,用于更清晰地编写异步代码,从而避免回调地狱。 Pr…...

李沐-动手学深度学习:RNN
1.RNN从零开始实现 import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l#8.3.4节 #batch_size:每个小批量中子序列样本的数目,num_steps:每个子序列中预定义的时间步数 #loa…...
Windows系统下npm报错node-gyp configure got “gyp ERR“解决方法
感谢原博主,此文参考网址:https://zhuanlan.zhihu.com/p/398279220 确保已经安装node.js (官方网址:https://nodejs.org/zh-cn/download) 首先在命令窗口执行命令安装windows-build-tools: npm install -…...