C语言预处理详解
1.预定义符号
C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
例子:
printf("file:%s line:%d\n", __FILE__, __LINE__);
2.define定义常量
基本语法:
#define name stuff
例子:
#define MAX 1000
#define reg register //为 register这个关键字,创建⼀个简短的名字
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
#define CASE break;case //在写case语句的时候⾃动把 break写上。
// 如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
思考:在define定义标识符的时候,要不要在最后加上;
?
#define MAX 1000;
#define MAX 1000
建议不要加上;,这样容易导致问题
比如:
if(condition)max = MAX;
elsemax = 0;
如果加了分号的情况,等替换之后,ij和else之间就是2条语句,而没有大括号的时候,if后边只能有一条语句,这里会出现语法错误
3.#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或者定义宏(define macro)
申明方式
#define name( parament-list ) stuff
其中parament-list
是一个由逗号隔开的符号表,它们可能出现在stuff中
- 注:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
举例:
#define SQUARE( x ) x * x
这个宏接受收一个参数x
,如果在上述声明之后,把SQUARE(5);
置于程序走中,预处理器就会用下面的表达式替换前5*5
警告:
这个宏存在一个问题
例子1:
//观察下⾯的代码段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );//这里的参数x会被替换成a+1,所以这条语句实际上变成了
printf ("%d\n",a + 1 * a + 1 );//替换产生的表达式没有按照预想的次序进行求值
//在宏定义上加上两个括号,这个问题便轻松的解决了:
#define SQUARE(x) (x) * (x)//这样预处理之后就产⽣了预期的效果:
printf ("%d\n",(a + 1) * (a + 1) );
例子2:
//2
#define DOUBLE(x) (x) + (x)//定义中我们使⽤了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));//看上去,好像打印100,但事实上打印的是55.我们发现替换之后:
printf ("%d\n",10 * (5) + (5));//乘法运算先于宏定义的加法,所以出现了 55 .
//这个问题,的解决办法是在宏定义表达式两边加上⼀对括号就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
提示:用于对数值表达式进行求值的宏定义应该加上合适的括号,避免在使用宏时由于参数中的操作符或者邻近操作符之间不可预料的相互作用
4.带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,在使用这个宏时会出现危险我,导致不可预测的后果,副作用就是表达式求值时出现的永久性效果
例如:
x+1;//不带副作⽤
x++;//带有副作⽤
MAX宏可以证明具有副作用参数引起的问题
#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定义符号和宏时,需要涉及几个步骤
1.调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是,首先被替换
2.替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值所替换
3.最后,再次对结果文件扫描,看看它是否包含任何由#define定义的符号,如果是,重复上面过程
注意:
1.宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归
2.当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索
6.宏函数的对比
宏通常被应用于执行简单的运算
⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些。
#define MAX(a, b) ((a)>(b)?(a):(b))
为什么不用函数来完成?
1.用于调用函数的和从函数返回的代码可能比实际这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹
2.更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用,反之这个宏可以适用于整形型,长整形型,浮点型等可以用>
来比较的类型,宏是类型无关的
和函数相比宏的优势:
1.每次使用宏的时候,一份宏定义的代码被插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
2.宏是没法调试的
3.宏由于类型无关,不够严谨
4.宏可能会带来运算符优先级问题,导致程序容易出错
宏有时可以做到函数做不到的事情,宏的参数可以出现类型,函数做不到
#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);
当我们按照下⾯的⽅式调⽤的时候:
PRINT(a);//当我们把a替换到宏的体内时,就出现了#a,⽽#a就是转换为"a",时⼀个字符串
代码就会被预处理为:
printf("the value of ""a" " is %d", a);
运⾏代码就能在屏幕上打印:
the value of a is 10
7.2##运算符
##
可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,##被称为记号粘合
这样的连接必须产生一个合法的标识符,否则结果是未定义
这⾥我们想想,写⼀个函数求2个数的较⼤值的时候,不同的数据类型就得写不同的函数。
⽐如:
int int_max(int x, int y)
{return x>y?x:y;
}
float float_max(float x, float y)
{return x>yx: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);float fm = float_max(3.5f, 4.5f);printf("%f\n", fm);return 0;
}
输出:
3
4.500000
在实际开发过程中##使⽤的很少,很难取出⾮常贴切的例⼦。
8.命名约定
⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
那我们平时的⼀个习惯是:
- 把宏名全部⼤写
- 函数名不要全部⼤写
9.#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__//..
#endif
2.多个分⽀的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#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头文件被包含的方式
12.1.1本地文件包含
#include "filename"
查找策略:先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在
标准位置查找头⽂件。
如果找不到就提⽰编译错误。
Linux环境的标准头⽂件的路径:
/usr/include
VS环境的标准头⽂件的路径:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径
注意按照自己的安装路径查找
12.1.2库文件包含
#include <filename.h>
查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件了。
12.2嵌套文件包含
#include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。
test.c
#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
{int id;char name[20];
};
如果直接这样写,test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。
如果test.h ⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家都能使⽤,⼜不做任何的处理,那么后果真的不堪设想。
如何解决头⽂件被重复引⼊的问题?答案:条件编译。
每个头⽂件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或者
#pragma once
就可以避免头⽂件的重复引⼊。
注:
推荐《⾼质量C/C++编程指南》中附录的考试试卷(很重要)。
思考题:
- 头⽂件中的 ifndef/define/endif是⼲什么⽤的?
- #include <filename.h> 和 #include "filename.h"有什么区别?
13.其他预处理指令
#error
#pragma
#line
...
不做介绍,⾃⼰去了解。
#pragma pack()在结构体部分介绍
参考《C语⾔深度解剖》学习
相关文章:

C语言预处理详解
1.预定义符号 C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI C&#…...
大语言模型画图(流程图、框架图)
第一步:向随意大语言模型,描述内容,推荐豆包 豆包 加上下面Prompt 通过Mermaid语法,描述上面流程图 第二步:将生成Mermaid输入流程图生成网站 中文Mermaid - 流程图、关系图在线画图、生成和编辑器...
2024年API接口发展趋势:智能化、自动化引领潮流
随着信息技术的飞速发展,应用程序编程接口(API)已成为现代软件开发的核心组成部分。API作为不同系统之间的桥梁,使得数据、功能和服务能够在各种平台和设备之间无缝流动。在2024年,API接口正经历着一系列显著的变革和发…...

数据挖掘与机器学习DMML(part 8)K近邻(KNN)
K Nearest Neighbours KNN Definition KNN 是一种简单的算法,它存储所有可用案例,并根据相似度量对新案例进行分类。 KNN 不同名称: K-Nearest Neighbors • Memory-Based Reasoning基于记忆的推理 • Example-Based Reasoning基于实例的…...

Fortify 24.2.0版本最新版 win/mac/linux
工具介绍: Fortify SCA作为一款业内主流的静态代码扫描工具,被广泛应用于白盒测试中。与其他静态代码扫描工具相比,Fortify SCA的突出优势主要在于更加广泛地支持的语言和开发平台、更全面和权威的安全规则库使扫描更加全面、更加智能化的自定…...

突破时间与空间限制的富媒体百宝箱——智能工具箱:让云上内容生产更easy
“这是你的同款日常吗?老是在赶deadline,苦练PS还未出师,premiere、达芬奇真的好难,学python脑容量确实不够~打工人太难了~~” 来试试智能工具箱吧!即来即用,一键实现办公自由。图片工具、视频工具、音频工…...
MacOs使用Wine 安装UaExpert与UaExpert的使用
要在 macOS 上使用 Wine 安装和运行 UaExpert,可以按照以下步骤操作: 安装 Wine 在 macOS 上,你可以通过 Homebrew 来安装 Wine。如果你还没有安装 Homebrew,可以先安装 Homebrew,然后使用它来安装 Wine。 bash /bin…...
【Prompt Engineering】3.文本概括
一、引言 文本信息量大,LLM在文本概括任务上展现出强大能力。本章介绍如何通过编程方式调用API接口实现文本概括功能。 首先,我们需要引入 zhipuAI 包,加载 API 密钥,定义 getCompletion 函数。 from zhipuai import ZhipuAIke…...

力扣-图论-14【算法学习day.64】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非…...
redis 架构详解
Redis架构详解可以从以下几个方面进行阐述: 一、部署架构 Redis有多种部署架构,适用于不同的应用场景和需求,主要包括以下几种: 单机模式(Standalone Mode) 特点:部署简单,配置方便…...

多分类交叉熵与稀疏分类交叉熵
总结: 标签为 One-hot 编码的多分类问题,用分类交叉熵对于标签为整数的多分类问题,用稀疏分类交叉熵稀疏分类交叉熵内部会将整数标签转换为 One-hot 编码,而如果标签已经是 One-hot 编码的形式,再使用稀疏分类交叉熵就会多此一举。 算例 假设我们有三个类别:A、B 和 C。…...
PHP 8新特性深度解析与实战应用
引言 PHP作为一种广泛使用的开源脚本语言,以其在Web开发领域的卓越性能而闻名。随着PHP 8的发布,这门语言再次迎来了许多令人兴奋的新特性和改进。本文将深入探讨PHP 8的新特性,并提供实战应用示例,帮助开发者更好地理解和使用PH…...
【C语言】UDP通信
udp使用的是数据报传输。可以一对一,一对多进行传输,用于快速,实时性高的场景 服务器端: 使用步骤: 1.创建socket 2.bind绑定可接收的客户端 3.while{ recv接收数据 send发送数据 } #include <stdio.h> #inclu…...
用Keytool和OpenSSL生成和签发数字证书
一)keytool生成私钥文件(.key)和签名请求文件(.csr),openssl签发数字证书 J2SDK在目录%JAVA_HOME%/bin提供了密钥库管理工具Keytool,用于管理密钥、证书和证书链。Keytool工具的命令在JavaSE6中已经改变,不过以前的命令仍然支持。Keytool也可以用来管理对称加密算法中…...

算法-动态数组-62.不同路径
一、题目 二、思路解析 1.思路: 对于找到目的地它的来源主要来源于目的地的上一格和目的地的左一格 2.常用方法: 无 3.核心逻辑: 1.处理边界: a.只向右移动,至始至终只有一条路径 for(int i0;i<m;i){dp[i][0]1; } …...

Y3编辑器教程5:触发器进阶使用(镜头、UI、表格、函数库、排行榜、游戏不同步)
文章目录 一、游戏声音设计二、 游戏镜头设计2.1 镜头的基本参数2.2 镜头时间轴动画 三、界面编辑3.1 界面编辑器设置3.2 添加按钮事件3.3 触发编写 四、 表格编辑器(实现对话UI)4.1 一维表和多维表4.2 数据验证、搜索、保存与撤销4.3 Excel导入导出4.4 …...

基于小程序实现地图定位、轨迹绘制、地图标点、快捷导航、唤醒导航APP、开箱即用
目录 前言研究背景与意义研究目标与内容研究方法与技术路线小程序地图组件介绍定位技术与原理轨迹绘制技术地图标注与标记功能地图定位与轨迹绘制功能实现定位功能设计与实现获取用户当前位置总结说明代码块前言 研究背景与意义 地图定位和轨迹追踪作为智能手机中常见的功能之…...

JAVA后端实现全国区县下拉选择--树形结构
设计图如图: 直接上代码 数据库中的格式: JAVA实体类: Data public class SysAreaZoningDO {private Long districtId;private Long parentId;private String districtName;private List<SysAreaZoningDO> children; } MapperSQL语句…...

数据结构速成
1. 数据结构与算法 2. 顺序表 3. 链表 4. 栈与队列 5. 串 6. 树与二叉树(1) 7. 树与二叉树(2) 8. 图 9. 图的应用 10. 查找 11. 排序(1) 12. 排序(2)...
Electron-Vite 项目搭建(Vue)
前提条件 Node.js: 确保已安装 Node.js 版本 18 或更高版本 (推荐使用最新稳定版)。Vite: 确保 Vite 版本为 4.0 或以上。包管理工具: 推荐使用 pnpm,但也可以使用 npm 或 yarn。 安装 Electron-Vite 首先,在项目中安装 electron-vite 作为开发依赖&a…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...