C语言:预处理
C语言:预处理
- 预定义符号
- #define
- 定义常量
- 定义宏
- 宏与函数对比
- #操作符
- ##操作符
- 条件编译
- 头文件包含
- 库文件包含
- 本地文件包含
- 嵌套文件包含
预定义符号
C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
示例:
printf("file:%s line:%d\n", __FILE__, __LINE__);
输出结果:
file:test.c line:1
以上预定义符号,都是一些常量,可以自己一一尝试。
#define
在C语言中,#define是一个预处理器指令,用于定义宏。
宏是一个被预处理器替换的标识符或一个标识符和参数的组合。宏定义可以用来简化代码、提高代码的可读性和可维护性。
使用#define可以定义常量、函数宏等。
定义常量
- 定义常量:
#define PI 3.14
这样就可以在代码中使用PI来代表3.14。
- 定义关键字:
#define reg register
将 reg 定义为关键字 register,可以将reg这个简写代替register关键字使用。
- 定义代码段
#define CASE break;case
正常的switch语句每一个case都要加上break,通过这个写法,我们可以在写CASE时自动补齐break。
定义宏
#define 机制允许把参数替换到文本中,这种功能叫做 宏(macro) / 定义宏(define macro)
语法:
#define name(parament-list) stuff
其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
示例:
#define SQUARE(x) x * x
当我们在代码中输入以下代码:
int main()
{int a = 5;int b = SQUARE(a);return 0;
}
在编译后就会转化为:
int main()
{int a = 5;int b = a * a;return 0;
}
也就是直接发生了文本替换,这种宏的形式非常像函数,因此也可以称为宏函数。但是其也有很多需要注意的地方。
比如以下代码:
int a = 5;
int b = SQUARE(a + 1);
我们希望先执行a + 1,然后再传入SQUARE中,但是其不会这样做因为其会将上述代码直接替换为:
int a = 5;
int b = a + 1 * a + 1;
由于操作符优先级的问题,我们不会得到想要的结果。为了处理这个情况,我们需要把参数用小括号括起来:
#define SQUARE(x) (x) * (x)
代码就变成:
int a = 5;
int b = (a + 1) * (a + 1);
这样我们就可以行使预期的功能了。
那么我们再看到一串代码:
#define DOUBLE(x) (x) + (x)int a = 5;
int b = 10 * DOUBLE(a);
代码编译后为:
int a = 5;
int b = 10 * 5 + 5;
又出现了一样的问题,我们的 10 * DOUBLE(a)并没有先执行DOUBLE(a),而展开后,又出现了操作符优先级问题,所以我们的宏还要再优化:
#define DOUBLE(x) ((x) + (x))
在宏的最外侧再加一层括号,就可以独立运行,不受外界操作符影响了。
通过以上推断,我们可以发现,宏虽然可以很好的替换代码,但是会受到外界操作符的影响,此时就要注意很多细节。
宏与函数对比

函数在调用时,是会开辟内存创建栈帧的,而宏则直接执行,所以速度更快。但是由于宏是在编译阶段就已经处理好了,所以宏不能通过调试观察现象,还要操作符优先级带来的种种问题。因此宏不适合处理复杂的函数,但是很适合短小简单的函数。
#操作符
功能:
#可以将宏的参数转化为字符串
比如以下代码:
#define PRINT(n) printf("the value of "#n " is %d", n);
我们尝试调用:
int a = 5;
PRINT(a);
代码就会被转化为:
int a = 5;
printf("the value of ""a" " is %d", a);
可以看到,两个n的替换效果是不同的,对于n其会直接被替换为变量a;而对于#n,其不是简单的替换,而是把参数名转化为了字符串”a“。
##操作符
##操纵符可以将两个符号合并为一个符号
示例:
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
该宏用于创建不同类型的比大小函数,由于不同函数需要不同的函数名,于是利用##来连接函数名,也就是type##_max部分。type是一个宏参数,当传入float,type##_max整体就被连接为float_max,当传入int,整体就被连接为int_max。也就是##起到一个连接作用。
条件编译
条件编译是C语言中的一种编译指令,用于在编译过程中根据指定的条件选择性地包含或排除某些代码。它主要是为了满足不同平台、不同编译选项或不同场景下的需求。
条件编译使用预处理指令实现,预处理指令以#开头。下面是一些常用的条件编译指令及其用法:
#if/#elif/#else /#endif
#if用于基于预处理器常量的值进行条件判断。
#elif用于在多个条件之间进行选择。
#else用于在没有匹配的#if或#elif时执行。
#endif用于结束条件编译块。
示例:
#define NUM 5#if NUM > 10printf("NUM is greater than 10\n");#elif NUM > 0printf("NUM is greater than 0\n");#elseprintf("NUM is less than or equal to 0\n");#endif
这个代码和C语言的if代码很像,不过多讲解了。
#ifdef/#ifndef
#ifdef用于检查一个标识符是否已经定义,如果已定义则编译后面的代码,否则跳过。
#ifndef与#ifdef相反,用于检查一个标识符是否未定义。
示例:
#ifdef DEBUGprintf("Debug mode enabled\n");
#endif
以上代码中,只要我们定义了DEBUG这个变量,就会输出"Debug mode enabled\n"语句。
-
#define
#define用于定义宏。宏是一种将一组指令作为一个整体进行替换的方式。
示例:#define MAX(a, b) ((a) > (b) ? (a) : (b))int x = 10; int y = 20; int max = MAX(x, y); -
#include
#include用于将指定的头文件包含到当前文件中。
示例:#include <stdio.h>int main() {printf("Hello, World!\n");return 0; } -
#pragma
#pragma用于向编译器发出特定的指令,如优化选项、警告控制等。它的语法和功能因编译器而异。
示例:#pragma warning(disable: 4996)
这些是C语言中常用的条件编译指令和代码用法。通过合理使用条件编译,我们可以根据不同的需求自由地控制代码的编译过程。
头文件包含
头文件包含分两种形式:本地头文件与库文件。
库文件包含
语法:
#include <filename.h>
查找头⽂件会直接去标准路径下去查找,如果找不到就提⽰编译错误
我们平常使用的库文件都通过尖括号<>来包含,其会直接到存放库文件的路径中查找。
本地文件包含
#include "filename"
先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件,如果找不到就提⽰编译错误
如果是用户自己编写的头文件,我们要用双引号""包含,如果通过这种方式包含头文件,那么会先在当前源文件的目录下查找,如果没有找到,再去库文件中查找。
也就是说:库文件也可以通过双引号包含,但是会多出额外的查找步骤,所以库文件还是用尖括号包含更好,而自己编写的头文件必须双引号包含。
嵌套文件包含
假设我们现在有以下文件结构:
头文件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反复被展开,产生大量重复代码。这就是嵌套文件包含的问题,那么我们要如何处理这个问题,让其只能被包含一次呢?
条件编译方法:
在头文件test.h中加入以下代码:
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
第一次包含头文件:
先执行#ifndef __TEST_H__,我们此时没有定义__TEST_H__这个变量,if成立,此时头文件会被展开,同时执行#define __TEST_H__,此时__TEST_H__就已经被定义了
第二次包含头文件:
第二次站时,由于上一次展开已经定义了__TEST_H__这个变量,导致#ifndef __TEST_H__判断为假,此时整个头文件都不会再被编译,直接舍弃
后续再展开头文件,都会因为 __TEST_H__被定义而不会编译,解决了嵌套编译的问题。
一般而言,我们这个用于判断头文件有没有被展开过的变量,是头文件名通过一定规则转化来的:
- 在头文件前后加上两个下划线
__头文件.h__- 把头文件中的点
.也改为下划线__头文件_h__
因此test.h的常量就是:__TEST_H__。
pragma:
通过条件编译其实是比较传统的写法,我们还有一种更加简洁方便的写法:
#pragma once
只要在任何头文件前面加上这句话,就只会被编译一次了。
相关文章:
C语言:预处理
C语言:预处理 预定义符号#define定义常量定义宏宏与函数对比 #操作符##操作符条件编译头文件包含库文件包含本地文件包含嵌套文件包含 预定义符号 C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。 __FILE__ //…...
计算机网络:路由协议
路由协议简介 路由协议是计算机网络中不可或缺的一部分,它们负责确定数据包从源地址到目的地址的最佳路径。想象一下,如果你是一个数据包,路由协议就像是地图或导航工具,指导你如何到达目的地。 目录 路由协议简介 工作原理简化…...
经典动态规划题目leetcode322. 零钱兑换
题目链接:https://leetcode.cn/problems/coin-change/description/ 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合…...
python 使用curl_cffi 绕过jax3指纹-Cloudflare 5s盾
现在越来越多的网站已经能够通过JA3或者其他指纹信息,来识别你是不是爬虫了。传统的方式比如换UA,加代理是没有任何意义了,所以这个时候我们就需要使用到curl_cffi 了。 1.TLS 指纹是啥? 在绝大多数的网站都已经使用了 HTTPS&am…...
Python3学习笔记39-passlib
passlib处理密码哈希的python包,支持很多哈希算法和工具 bcrypt 安装 pip install passlib[bcrypt] 会安装passlib包和bcrypt两个包 密码哈希与校验 from passlib.context import CryptContext# 创建CryptContext对象,指定加密算法 pwd_context CryptContext…...
Matlab 机器人工具箱 动力学
文章目录 R.dynR.fdynR.accelR.rneR.gravloadR.inertiaR.coriolisR.payload官网:Robotics Toolbox - Peter Corke R.dyn 查看动力学参数 mdl_puma560; p560.dyn;%查看puma560机械臂所有连杆的动力学参数 p560.dyn(2);%查看puma560机械臂第二连杆的动力学参数 p560.links(2)…...
Android ShellUtils手机管理器
1. Android ShellUtils手机管理器 Android Shell工具类,可用于检查系统root权限,并在shell或root用户下执行shell命令。如: checkRootPermission() 检查root权限 。execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg)…...
《梦幻西游》本人收集的34个单机版游戏,有详细的视频架设教程,值得收藏
梦幻西游这款游戏,很多人玩,喜欢研究的赶快下载吧。精心收集的34个版本。不容易啊。里面有详细的视频架设教程,可以外网呢。 《梦幻西游》本人收集的34个单机版游戏,有详细的视频架设教程,值得收藏 下载地址࿱…...
吴恩达机器学习全课程笔记第六篇
目录 前言 P96-P100 使用多个决策树 随机森林算法 XGBoost 什么时候使用决策树 P101-P107 聚类 K-means 初始化K-means 选择聚类的个数 P108-P113 异常检测算法 开发和评估异常检测系统 异常检测vs监督学习 选择要使用的特征 前言 这是吴恩达机器学习笔记的第…...
ue4.27 发现 getRandomReachedLocation 返回 false
把这个玩意儿删掉,重启工程,即可 如果还不行 保证运动物体在 volum 内部,也就是绿色范围内确保 project setting 里面的 navigation system 中 auto create navigation data 是打开的(看到过博客说关掉,不知道为啥) 如果还不行&…...
【C++ AVL树】
文章目录 AVL树AVL树的概念AVL树节点的定义AVL树的插入AVL树的旋转右单旋左单旋左右双旋右左双旋 代码实现 总结 AVL树 AVL树的概念 二叉搜索树在顺序有序或接近有序的情况下,而插入搜索树将退化为单叉树,此时查找的时间复杂度为O(n),效率低…...
记录一次架构优化处理性能从3千->3万
0.背景 优化Kafka消费入Es,适配600台设备上报数据,吞吐量到达2万每秒 1.环境配置 2.压测工具 3.未优化之前的消费逻辑 4.优化之后的消费流程 5.多线程多ESclient 6.修改ES配置,增加kafka分区,增加线程,提升吞吐量 7.…...
c++二进制位运算使用方法
文章主要内容: C 中的位运算符主要用于对整数类型的数据进行位操作,包括按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<&#…...
TypeScript之JSON点语法调用
场景 当我们想要通过将JSON中的属性名赋值给一个变量,并且通过点语法实现字段调用.常规的String变量保存会出现下述问题,就可以通过String[][]实现动态调用字段. let parentJSON{"name":"liupeng"}let a:String;Object.keys(parentJSON).forEach(key >…...
手撕Java集合之简易版Deque(LinkedList)
在目前,许多互联网公司的面试已经要求能手撕集合源码,集合源码本身算是源码里比较简单的一部分,但是要在面试极短的10来分钟内快速写出一个简易版的源码还是比较麻烦的,很容易出现各种小问题。所以在平时就要注重这方面的联系。 以…...
MySQL知识点归纳总结(二)
10、MVCC实现原理? 事务ID(Transaction ID):每个事务在执行时都会被分配一个唯一的事务ID,用于标识该事务的开始时间顺序。事务ID是一个递增的整数,随着每个新事务的开始而递增。 Undo日志(Un…...
vue:实现顶部消息横向滚动通知
前言 系统顶部展示一个横向滚动的消息通知,就是消息内容从右往左一直滚动。 效果如下: 代码 使用 <template><div class"notic-bar"><img :src"notic" class"notice-img" /><div class"noti…...
[笔记] wsl 禁用配置 win系统环境变量+代理
wsl 配置禁用 win系统环境变量 进入 wsl 的 /etc/wsl.conf 目录,增加以下配置: [interop] enabledfalse appendWindowsPathfalse然后退出wsl,并且执行关闭正在运行的 wsl,执行命令 wsl --shutdown 最后重新进入wsl 即可。 参考…...
Mysql标量子查询
目录 子查询标量子查询数据准备 子查询 SQL语句中嵌套select语句,称为嵌套查询,又称子查询。 SELECT * FROM t1 WHERE column1 ( SELECT column1 FROM t2 ... );子查询外部的语句可以是insert / update / delete / select 的任何一个&…...
深入了解Java虚拟机(JVM)
Java虚拟机(JVM)是Java程序运行的核心组件,它负责解释执行Java字节码,并在各种平台上执行。JVM的设计使得Java具有跨平台性,开发人员只需编写一次代码,就可以在任何支持Java的系统上运行。我们刚开始学习Ja…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
