C语言:预编译过程的剖析
目录
一.预定义符号和#define定义常量
二.#define定义宏
三.宏和函数的对比
四、#和##运算符
五、条件编译
在之前,我们已经介绍了.c文件在运行的过程图解,大的方面要经过两个方面。
一、翻译环境
1.预处理(预编译)
2.编译
3.汇编
4.链接
二、运行环境
我们在这里,主要介绍以下预处理阶段的事情,重点是#define定义宏,宏和函数对比的各自优点和缺点。

预处理阶段主要处理那些源文件中#开始的预编译指令。比如:#include,#define,处理的规则如下:
(1)将所有的 #define 删除,并展开所有的宏定义。
(2)处理所有的条件编译指令,如:#if、#ifdef、#elif、#else、#endif
(3)处理#include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说也被包含的头文件可能包含其他文件。
(4)删除所有的注释
(5)添加行号和文件名标识,方便后续编译器生成调试信息等。
(6)或保留所有的#pragma 的编译器指令,编译器后续会使用。
一.预定义符号和#define定义常量
1)预定义符号
在C语言中,设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。
1. _ _FILE_ _ //进行编译的源文件
2. _ _LINE_ _ //文件当前的行号
3. _ _DATE_ _ //文件被编译的日期
4. _ _TIME_ _ //文件被编译的时间
5. _ _STDC_ _ //如果编译器遵循ANSI C,其值为1,否则未定义
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{FILE* pf = fopen("test1.txt", "w+");printf("file:%s line:%d date:%d\n", __FILE__, __LINE__, __DATE__);return 0;
}

这里,可以看出,我们是可以直接运行这些与定义符号的。
2)#define定义常量
# define MAX 100
# define reg register //为register这个关键字,创建一个新的名字
#define do_forever for(;;) //死循环,起一个更加形象的名字
#define CASE break;case //在写case语句的时候自动把break写上
//如果定义的stuff过长,可也分成几行写, 除了最后一行外,每行的后面都加上一个反斜杠(续行符)
#define DEBUG_PRINT printf("fine:%s tline:%d\t \date:%s \ time:%s\n \",__FILE__,__LINE__, \__DATE__,TIME__ )
这里需要注意的是,当定义的语句过长的时候,用 '\'来换行继续写,这里当续行符来使用。
注意:在使用#define定义标识符的时候,不要在最后加上分号(防止我们出错)
二.#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或者定义宏(define macro)。
#define name( parament-list ) stuff
这里 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
举例:
#define SQUARE( x ) x*x
int a = 5;
printf("%d\n", SQUARE( a + 1));
这里看这一段代码,我们可能会说执行的结果为36,但是实际上它将打印11。

我们为了得到36的正确结果,在定义宏的时候,为了避免出现的错误,我们一般是在宏定义表达式的两边加上一对括号。
#define DOUBLE(x) ( (x) + (x) )
注意:所有用于对数值表达式进行求值得宏定义都应该用这种加括号的方式,避免在使用宏时由于参数中得操作符或操作符之间不可预料得相互作用。
宏的替换规则
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏, 参数名被它们的值所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就是重复上述处理过程。
注意:1 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
三.宏和函数的对比
1.宏通常被应用于简单的运算。(如下:)
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
用函数来完成上述代码比较两数大小也是可以的,但是于宏相比,宏更好。
宏的优势:
1.宏比函数再程序的规模和速度上更胜一筹。
2.宏的参数是于类型无关的。(很重要)
3. 宏的参数可以出现类型,函数做不到。(很重要)
宏的参数出现了类型。
#define MALLOC(num, type) (type )malloc(num*sizeof(type))
MALLOC(10,int);//上述的代码,相当于下面的代码
(int*)malloc(10 * sizeof(int));
宏的劣势:
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没办法调试的。
3.宏由类型无关,也就不够严谨。
4.宏可能会带来运算符优先的问题,导致程序容易出错。
| 属性 | #define定义宏 | 函数 |
| 代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序长度会大幅度增加 | 函数的代码只出现于一个地方;每次使用函数的时候,都调用那个地方的同一份代码 |
| 执行速度 | 更快 | 存在函数的调用和返回的额外开销,相对慢一些 |
| 操作符优先级 | 宏参数的求值是再所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多加括号 | 函数参数只在函数调用的时候求值一次,将结果值传递给函数。 |
| 带有副作用的参数 | 参数可能被替换到宏中的多个位置,如果宏的参数被多次计算,带由副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果容易控制 |
| 参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如果参数类型不同,就需要不同的函数,即使它们的任务是不同的。 |
| 调试 | 宏不方便调试 | 函数可以逐语句调试 |
| 递归 | 不能递归 | 可以递归 |
四、#和##运算符
1. #运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
2. #运算符所执行的操作可以理解为“字符串化”
int a = 10;
#define PRNT(n) printf("the value of "#n " is %d",n);

##运算符:可以把位于它两边符号和成一个符号,它允许宏定义从分离的文本片段创建标识符。##被称为记号粘合
当定义一个比较两个数较大值的时候,类型不同的数据就得写不同的函数,例如:
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); \
} \
五、条件编译
在执行编译一个程序的时候,我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
1.
#if 常量表达式
#endif
2.多个分支的条件编译
#if 常量表达式
#elif 常量表达式
#else
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef
#if !defined(symbol)
#ifdef 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
相关文章:
C语言:预编译过程的剖析
目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前,我们已经介绍了.c文件在运行的过程图解,大的方面要经过两个方面。 一、翻译环境 1.预处理(预编译) 2.编译 3…...
算法——单调栈
单调栈: 保持栈内的元素始终递增或递减。 单调递增 待处理数组{1,5,2,5,7,2,8} public void sameyIncrease(int[] nums) {Stack<Integer> stack new Stack<>();for(int i 0; i < nums.length; i) {//当栈空的时候可以直接进栈或者要进栈的数大于…...
LeetCode讲解篇之695. 岛屿的最大面积
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们遍历二维矩阵,如果当前格子的元素为1进行深度优先搜索,将搜索过的格子置为0,防止重复访问,然后对继续深度优先搜索上下左右中为1的格子 题解代码 func maxAr…...
招联2025校招内推倒计时
【投递方式】 直接扫下方二维码,或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus,使用内推码 igcefb 投递) 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…...
vite学习教程01、vite构建vue2
文章目录 前言一、vite初始化项目二、修改配置文件2.1、修改main.js文件2.2、修改App.vue文件2.3、修改helloworld.vue2.4、修改vite.conf.js2.5、修改vue版本--修改package.json文件 三、安装vue2和vite插件四、启动服务资料获取 前言 博主介绍:✌目前全网粉丝3W&…...
强化学习部分代码的注释分析
引言 对一些代码块进行注释。我直接复制过来的,不能运行的话别怪我。 多臂赌博机 代码来自链接。欢迎回到原来的链接学习。 %I thought what Id do was Id pretend I was one of those deaf-mutes,or should I ?clear all; epsilon[0.5,0.2,0.1,0.0…...
ctf.bugku-备份是个好习惯
访问页面得到字符串 这串字符串是重复的; d41d8cd98f00b204e9800998ecf8427e 从前端、源码上看,除了这段字符串,没有其他信息;尝试解密,长度32位;各种解密方式试试; MD5免费在线解密破解_MD5在…...
C++面试速通宝典——14
220. static关键字的作用 static关键字在编程中有多种作用: 在类的成员变量前使用,表示该变量属于类本身,而不是任何类的实例。在类的成员函数前使用,表示该函数不需要对象实例即可调用,且只能访问类的静…...
k8s的简介和部署
一、k8s简介 在部署应用程序的方式上面,主要经历了三个阶段: 传统部署:互联网早期,会直接将应用程序部署在物理机上优点:简单,不需要其它技术的参与缺点:不能为应用程序定义资源使用边界,很难合理地分配计算资源&…...
Thingsboard 网关实战 modbus通信 rpc下发控制指令
我们这里说的是Thingsboard通过网关modbus通信接入设备,然后通过rpc下发指令去控制开关信号的设备,不会网关通过modbus接入设备的,可以看我之前的文章,从小白教学。 下面我们就说如何下发rpc开关信号指令 第一步.在modbus配置文…...
基于pytorch的手写数字识别
import pandas as pd import numpy as np import torch import matplotlib import matplotlib.pyplot as plt from torch.utils.data import TensorDataset, DataLoadermatplotlib.use(tkAgg)# 设置图形配置 config {"font.family": serif,"mathtext.fontset&q…...
MySQL 实验 7:索引的操作
MySQL 实验 7:索引的操作 索引是对数据表中一列或多列的值进行排序的一种结构,索引可以大大提高 MySQL 的检索速度。合理使用索引,可以大大提升 SQL 查询的性能。 索引好比是一本书前面的目录,假如我们需要从书籍查找与 xx 相关…...
为Floorp浏览器添加搜索引擎及搜索栏相关设置. 2024-10-05
Floorp浏览器开源项目地址: https://github.com/floorp-Projects/floorp/ 1.第一步 为Floorp浏览器添加搜索栏 (1.工具栏空白处 次键选择 定制工具栏 (2. 把 搜索框 拖动至工具栏 2.添加搜索引擎 以添加 搜狗搜索 为例 (1.访问 搜索引擎网址 搜狗搜索引擎 - 上网从搜狗开始 (2…...
如何设置WSL Ubuntu在Windows开机时自动启动
如何设置WSL Ubuntu在Windows开机时自动启动 步骤详解1. 创建批处理脚本2. 添加到Windows启动项 注意事项结语 在使用Windows Subsystem for Linux (WSL) 时,我们可能希望Ubuntu能够在Windows启动时自动运行。本文将介绍如何实现这一功能,让您的开发环境更加便捷。 步骤详解 …...
使用TensorBoard可视化模型
目录 TensorBoard简介 神经网络模型 可视化 轮次-损失曲线 轮次-准确率曲线 轮次-学习率曲线 迭代-评估准确率曲线 迭代-评估损失曲线 TensorBoard简介 TensorBoard是一款出色的交互式的模型可视化工具。安装TensorFlow时,会自动安装TensorBoard。如图: TensorFlow可…...
《深度学习》OpenCV 图像拼接 原理、参数解析、案例实现
目录 一、图像拼接 1、直接看案例 图1与图2展示: 合并完结果: 2、什么是图像拼接 3、图像拼接步骤 1)加载图像 2)特征点检测与描述 3)特征点匹配 4)图像配准 5)图像变换和拼接 6&am…...
Hive数仓操作(三)
一、Hive 数据库操作 1. 创建数据库 基本创建数据库命令: CREATE DATABASE bigdata;说明: 数据库会在 HDFS 中以目录的形式创建和保存,数据库名称会存储在 Hive 的元数据中。如果不指定目录,数据库将在 /user/hive/warehouse 下…...
TDSQL-C电商可视化,重塑电商决策新纪元
前言: 在数字化浪潮席卷全球的今天,电子商务行业以其独特的魅力和无限潜力,成为了推动全球经济增长的重要引擎。然而,随着业务规模的急剧扩张,海量数据的涌现给电商企业带来了前所未有的挑战与机遇。如何高效地处理、…...
翔云 OCR:发票识别与验真
在数字化时代,高效处理大量文档和数据成为企业和个人的迫切需求。翔云 OCR 作为一款强大的光学字符识别工具,在发票识别及验真方面表现出色,为我们带来了极大的便利。 一、翔云 OCR 简介 翔云 OCR 是一款基于先进的人工智能技术开发的文字识别…...
HTML ASCII:Web 开发中的字符编码基础
HTML ASCII:Web 开发中的字符编码基础 ASCII,全称为美国信息交换标准代码(American Standard Code for Information Interchange),是一种用于电子通信的字符编码标准。它最初于1963年提出,用于在不同的计算…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
