【C语言】宏定义详解
C语言中的宏定义(#define)详细解析
在C语言中,宏定义是一种预处理指令,使用 #define
关键字定义。它由预处理器(Preprocessor)在编译前处理,用于定义常量、代码片段或函数样式的代码替换。宏是一种强大的工具,可以提高代码的可读性、可维护性和效率,但也需要谨慎使用,以避免潜在的错误。
宏定义属于C/C++语言预处理功能的范畴,要透彻的搞明白宏定义,就需要先理解预处理的概念。使用预处理功能可以改变程序设计环境,提高编译效率。
预处理命令本身并不是C/C++语言的组成部分,不能直接对它们进行编译,只能在对写好的代码进行编译之前,先对程序中这些特殊的命令进行 “预处理” ,代码在经过预处理之后便不再包括预处理命令了,得到的是可供计算机直接执行的目标代码。
除了宏定义,预处理功能还包括其他两种:① 条件包含;② 条件编译。后面会逐一展开介绍。
宏定义的语法:#define 宏名 宏体
--> #define WEIGHT 150
;注意:宏体常包含常数、字符串和表达式。为了防止与字符串变量混淆,通常我们将宏名写成全部大写的字符串。
宏定义的效果是,在程序编译时,如果编译器遇到宏名,会自动用宏定义中的宏体去替换掉宏名,这个过程称为“宏替换” 或 “宏展开”。宏定义分为两种,一种不带参数,例如 #define WEIGHT 150
或 #define SPLIT "----"
,此类宏定义的宏体通常是简单的常数或者字符串,在宏展开时,编译器只会对宏名做简单的替换,并不涉及运算。另一种是带参数的宏,这类宏定义的宏体通常为表达式,可进行逻辑运算,例如 #define M(y) y+150
,这样,一个带参数的宏就定义好了:y
是M
的参数,在程序需要的地方传参调用即可:M(10)
。
调用带参宏的机制有点类似于函数,但它比函数更加简洁,我们通常会将一段短小精炼而且运算重复率又非常高的代码定义成带参数的宏,这样就减少了代码重复率,也提高了代码的可观赏性。使用宏定义一方面方便程序的修改,我们只需要改变宏定义中的值,不用对整个程序进行修改,仅仅只修改宏定义的宏体即可,并且当常量比较长而且具备了某种有意义的标识符,我们可以用宏定义来替换:#define PI 3.1415926535
,这极大的提高了代码的开发效率和可读性。另一方面提高程序的运行效率,这体现在程序的运行阶段而不是编译阶段,使用带参宏定义可以完成函数调用的功能,而且与真正的函数相比,宏定义减少了系统开销,省略分配内存的步骤,以此来提高运行效率。因为如果函数所完成的功能比较少,比如简单的数据运算,那么,从函数的执行 ——> 停滞 ——> 函数返回,中途转换开销则相对较大,而使用带参数的宏定义就不会出现这个问题,尽管宏定义可完成简单的操作,但是,复杂的操作还是需要由函数调用来完成,并且宏定义所占用的目标代码空间较大,所以在使用时要依据具体情况来决定是否使用宏定义。
C/C++ 语言的设计者为了方便开发者编码在C/C++语言中定义了少量的系统宏,例如 __FILE__
代表包含当前文件程序名和路径的字符串,__DATE__
则代表当前日期的字符串,类似的系统宏还有很多。
值得一提的是,#include
也属于预处理命令,也叫做“文件包含”,它的功能是指定文件的全部内容替换程序中的命令行,从而使指定的文件与当前源文件连成一个源文件。有两种常见的使用形式: #include "文件名"
和 #include <文件名>
。这两种形式都可以使用,但有些区别。
#include <文件名>
表示编译系统根据系统头文件存放的目录路径去搜索系统头文件,而不是在源文件目录查找。
#include "文件名"
表示编译系统首先在当前的源文件目录查找,若未找到才根据系统的头文件存放的目录路径去搜索系统头文件。
简而言之,#include <文件名>
——> 系统定义;#include "文件名"
——> 用户定义。文件包含命令可以出现在文件的任何位置,不过通常都集合放在文件的开头处,一条#include
命令只能指定一个被包含的文件,同时,文件包含也允许嵌套,即在一个被包含的文件中又可以包含另一个文件,在实际开发中,当一个 c
程序分散在若干个文件中时,可以将文件公用的符号常量定义和宏定义等单独写成一个文件,然后在其他需要这些定义和说明的源文件中,用文件包含命令包含该头文件,这样就可以避免在每个文件的开头都去重复书写那些共用代码,也可以避免因输入或修改的失误造成的不一致性。
1. 基本语法
#define 宏名 替换内容//宏名:宏的名称,通常是大写字母以区分变量。
//宏体:替换内容,宏名的替代值,可以是数字、字符串或代码片段。
示例代码片:
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
2. 宏的类型
2.1 对象宏
定义:宏名替换为一个常量或文本。
特点:适合定义常量,避免重复书写。
代码
#include <stdio.h>#define PI 3.14159
#define GREETING "Hello, World!"int main() {printf("PI = %f\n", PI);printf("%s\n", GREETING);return 0;
}
输出:
PI = 3.141590
Hello, World!
2.2 函数宏
定义:宏名替换为一个带参数的代码片段,类似于函数。
特点:
- 宏展开时直接进行文本替换,不会进行类型检查。
- 可用于简单操作,避免函数调用的开销。
- 在复杂表达式中需要注意括号问题,避免优先级错误。
代码
#include <stdio.h>#define SQUARE(x) ((x) * (x))int main() {printf("SQUARE(3) = %d\n", SQUARE(3)); // 输出 9printf("SQUARE(1 + 2) = %d\n", SQUARE(1 + 2)); // 输出 9,正确处理优先级return 0;
}
输出:
SQUARE(3) = 9
SQUARE(1 + 2) = 9
2.3 带条件的宏
定义:可以使用条件编译预处理指令(#if
、#ifdef
和 #ifndef
)对宏进行条件定义。
#include <stdio.h>#define DEBUGint main() {
#ifdef DEBUGprintf("Debug mode is enabled.\n");
#endif#ifndef RELEASEprintf("Release mode is disabled.\n");
#endifreturn 0;
}
输出:
Debug mode is enabled.
Release mode is disabled.
2.4 带参数的宏
定义:接受一个或多个参数的宏,类似于函数,但不进行类型检查。
特点:可以实现简单的逻辑和代码复用。
#include <stdio.h>#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {int x = 10, y = 20;printf("Max of %d and %d is %d\n", x, y, MAX(x, y));return 0;
}
输出:
Max of 10 and 20 is 20
3. 宏的优缺点
- 优点
- 提高代码可读性:使用宏定义常量或代码片段,可以使代码更清晰。
- 避免重复:定义宏后,避免重复书写常量或代码。
- 提高效率:函数宏的展开是直接替换文本,不涉及函数调用开销。
- 灵活性:宏可以用于条件编译,方便实现跨平台代码。
- 缺点
- 调试困难:宏在预处理阶段被替换,源代码中看不到替换后的代码,调试时不直观。
- 没有类型检查:宏只是文本替换,不检查参数的类型,可能导致错误。
- 优先级问题:如果宏定义中没有正确使用括号,可能出现优先级错误。
- 易引发错误:宏展开后可能导致意想不到的行为,例如重复计算问题。
4. 宏的常见问题及解决方法
1. 重复计算问题: 函数宏的参数如果是表达式,可能会被多次计算,导致性能问题或意外结果。
解决方法:使用临时变量避免重复计算。
代码片示例
#include <stdio.h>#define SQUARE(x) ((x) * (x))int main() {int a = 5;printf("SQUARE(a++) = %d\n", SQUARE(a++)); // a++ 被计算两次return 0;
}
输出(问题演示):
SQUARE(a++) = 36
解决方案:改用inline函数(推荐)
#include <stdio.h>inline int square(int x) {return x * x;
}int main() {int a = 5;printf("square(a++) = %d\n", square(a++)); // a++ 只计算一次return 0;
}
2. 优先级问题:宏展开时,运算符的优先级可能导致错误结果。
解决方法:使用括号确保运算优先级。
代码片示例
#include <stdio.h>#define SQUARE(x) x * xint main() {printf("SQUARE(1 + 2) = %d\n", SQUARE(1 + 2)); // 错误优先级 (1 + 2 * 1 + 2)return 0;
}
输出(问题演示):
SQUARE(1 + 2) = 5
解决方案:
#define SQUARE(x) ((x) * (x))
5. 宏与 const 的对比
特性 | 宏(#define) | 常量(const) |
---|---|---|
类型检查 | 无类型检查 | 有类型检查,编译时保证类型安全 |
调试支持 | 不便于调试,宏在预处理阶段被替换 | 常量是变量的一部分,可在调试器中查看 |
作用范围 | 替换后作用范围不限 | 根据作用域规则,局限在声明的范围内 |
内存占用 | 无内存分配,仅文本替换 | 占用内存,用于存储常量 |
性能 | 替换直接展开,性能高 | 编译器可能优化为常量使用,性能差异不明显 |
6. 特殊宏
C语言提供了一些内置的特殊宏,用于帮助开发和调试。
预定义的特殊宏(部分):
宏名称 | 含义 |
---|---|
FILE | 当前源文件的名称 |
LINE | 当前代码所在的行号 |
DATE | 当前编译的日期 |
TIME | 当前编译的时间 |
STDC | 如果遵循ANSI标准,该宏的值为1 |
代码
#include <stdio.h>int main() {printf("File: %s\n", __FILE__);printf("Line: %d\n", __LINE__);printf("Date: %s\n", __DATE__);printf("Time: %s\n", __TIME__);return 0;
}
输出:
File: example.c
Line: 5
Date: Nov 26 2024
Time: 14:30:10
7. 条件编译与宏
条件编译
预处理的最后一个功能是条件编译,在一般情况下源文件的所有代码行都会参加编译以生成目标代码,但在某些特殊情况下,也许只希望对部分满足条件的代码行进行编译,这便是条件编译:条件编译用于根据情况选择性编译代码,常用的条件编译命令有以下两种格式:
#ifdef / #ifndef
:检查宏是否被定义。#if / #elif / #else / #endif
:条件编译。
<程序段1>和<程序段2>由若干条预处理命令或C语句组成,格式一中条件编译命令的功能是,如果在程序中定义了指定的<标识符>时,就用<程序段1>参与编译,否则,用<程序段2>参与编译;格式二则恰恰相反,当程序中定义指令<标识符>时,<程序段2>参与编译,否则,<程序段1>参与编译,如果在此格式中省略else
分支也可以直接写为如下形式:
#ifdef <标识符>
程序段#ifndef <标识符>
程序段
代码
#include <stdio.h>#define DEBUGint main() {
#ifdef DEBUGprintf("Debug mode is enabled.\n");
#elseprintf("Release mode.\n");
#endifreturn 0;
}
输出:
Debug mode is enabled.
综上:
- 宏的类型:
- 对象宏:用于定义常量或简单文本替换。
- 函数宏:用于实现简单的代码片段替换,注意括号和重复计算问题。
- 条件宏:结合条件编译指令,实现灵活的代码控制。
- 宏的优缺点:
- 宏是一种强大的文本替换工具,但缺乏类型检查,容易引发错误。
- 在现代C代码中,建议用
const
或inline
函数替代复杂的宏。
通过合理使用宏定义,可以使代码更加简洁和高效,但需谨慎处理潜在问题(如重复计算和优先级问题)。
以上。仅供学习与分享交流,请勿用于商业用途!
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!
相关文章:
【C语言】宏定义详解
C语言中的宏定义(#define)详细解析 在C语言中,宏定义是一种预处理指令,使用 #define 关键字定义。它由预处理器(Preprocessor)在编译前处理,用于定义常量、代码片段或函数样式的代码替换。宏是…...
LangChain——多向量检索器
每个文档存储多个向量通常是有益的。在许多用例中,这是有益的。 LangChain 有一个基础 MultiVectorRetriever ,这使得查询此类设置变得容易。很多复杂性在于如何为每个文档创建多个向量。本笔记本涵盖了创建这些向量和使用 MultiVectorRetriever 的一些常…...
《岩石学报》
本刊主要报道有关岩石学基础理论的岩石学领域各学科包括岩浆岩石学、变质岩石学、沉积岩石学、岩石大地构造学、岩石同位素年代学和同位素地球化学、岩石成矿学、造岩矿物学等方面的重要基础理论和应用研究成果,同时也刊载综述性文章、问题讨论、学术动态以及书评等…...
数据结构 (12)串的存储实现
一、顺序存储结构 顺序存储结构是用一组连续的存储单元来存储串中的字符序列。这种存储方式类似于线性表的顺序存储结构,但串的存储对象仅限于字符。顺序存储结构又可以分为定长顺序存储和堆分配存储两种方式。 定长顺序存储: 使用静态数组存储ÿ…...
职场发展陷阱
一、只有执行,没有思考 二、只有过程,没有结果 三、只有重复,没有精进 四、不懂向上管理 五、定期汇报 六、不要憋大招 七、多同步信息...
Xcode15(iOS17.4)打包的项目在 iOS12 系统上启动崩溃
0x00 启动崩溃 崩溃日志,只有 2 行,看不出啥来。 0x01 默认配置 由于我开发时,使用的 Xcode 14.1,打包在另外一台电脑 Xcode 15.3 Xcode 14.1 Build Settings -> Asset Catalog Compliter - Options Xcode 15.3 Build S…...
极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【二】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
PVE相关名词通俗表述方式———多处细节实验(方便理解)
PVE设置初期,对CIDR、 网关、 LinuxBridge、VLAN等很有困惑的朋友一定很需要一篇能够全面通俗易懂的方式去理解PVE 中Linux网桥的工作方式,就像操作一个英雄,多个技能,还是需要一点点去学习理解的,如果你上来就对着别人…...
Ansible--自动化运维工具
Ansible自动化运维工具介绍 1.Ansible介绍 Ansible是一款自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。…...
微信小程序学习指南从入门到精通
🗽微信小程序学习指南从入门到精通🗽 🔝微信小程序学习指南从入门到精通🔝✍前言✍💻微信小程序学习指南前言💻一、🚀文章列表🚀二、🔯教程文章的好处🔯1. ✅…...
微服务篇-深入了解使用 RestTemplate 远程调用、Nacos 注册中心基本原理与使用、OpenFeign 的基本使用
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 认识微服务 1.1 单体架构 1.2 微服务 1.3 SpringCloud 框架 2.0 服务调用 2.1 RestTemplate 远程调用 3.0 服务注册和发现 3.1 注册中心原理 3.2 Nacos 注册中心 …...
使用 Django 构建支持 Kubernetes API 测试连接的 POST 接口
文章目录 使用 Django 构建支持 Kubernetes API 测试连接的 POST 接口功能需求使用 kubectl 获取 Token命令解析输出示例 完整代码实现Kubernetes API 客户端类功能说明 Django 接口视图关键点解析 路由配置 接口测试请求示例响应结果成功错误 优化建议1. 安全性2. 错误处理3. …...
十二、正则表达式、元字符、替换修饰符、手势和对话框插件
1. 正则表达式 1.1 基本使用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title&g…...
计算机毕业设计Python+大模型美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
【后端面试总结】MySQL索引
数据库索引不只一种实现方法,但是其中最具代表性,也是我们面试中遇到最多的无疑是B树。 索引为什么选择B树 数据量很大的查找,是不能直接放入内存的,而是需要什么数据就通过磁盘IO去获得。 红黑树,AVL树等二叉查找树…...
[蓝桥杯 2021 省 AB2] 小平方
题目描述 小蓝发现,对于一个正整数 nn 和一个小于 nn 的正整数 vv,将 vv 平方后对 nn 取余可能小于 nn 的一半,也可能大于等于 nn 的一半。 请问,在 11 到 n−1n−1 中, 有多少个数平方后除以 nn 的余数小于 nn 的一半。 例如&…...
Jmeter测试工具的安装和使用,mac版本,jmeter版本5.2.1
Jmeter测试工具的安装和使用JSON格式请求 一、安装1、安装jdk包和设置java环境2、去官网下载Jmeter3、解压后,打开mac终端,进入apache-jmeter的bin文件开启jmeter 二、使用jmeter1、添加线程2、添加HTTP请求3、配置请求的协议、IP地址、端口号、请求方法…...
kmeans 最佳聚类个数 | 轮廓系数(越大越好)
轮廓系数越大,表示簇内实例之间紧凑,簇间距离大,这正是聚类的标准概念。 簇内的样本应该尽可能相似。不同簇之间应该尽可能不相似。 目的:鸢尾花数据进行kmeans聚类,最佳聚类个数是多少? plot(iris[,1:4…...
【纪念365天】我的创作纪念日
过去的一年 没有注意加入csdn已经有一年了。 这几天翻看小猴儿的通知才发现时间来到了一年的纪念日。稍稍思索想要将这一段时间的学习到的知识以及偶然遇到的机遇做一下总结。 上一次写纪念日是来到csdn128天的时候, 200天前我的学习状态是非常疯狂的。 只记得我当时…...
Opencv+ROS实现颜色识别应用
目录 一、工具 二、原理 概念 本质 三、实践 添加发布话题 主要代码 四、成果 五、总结 一、工具 opencvros ubuntu18.04 摄像头 二、原理 概念 彩色图像:RGB(红,绿,蓝) HSV图像:H࿰…...
蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)
别忘了请点个赞收藏关注支持一下博主喵!!!! ! ! ! ! 关注博主,更多蓝桥杯nice题目静待更新:) 动态规划 三、括号序列 【问题描述】 给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合…...
C++设计模式(单例模式)
一、介绍 1.动机 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例? 这应该是类设计者的…...
前端---CSS(部分用法)
HTML画页面--》这个页面就是页面上需要的元素罗列起来,但是页面效果很差,不好看,为了让页面好看,为了修饰页面---》CSS CSS的作用:修饰HTML页面 用了CSS之后,样式和元素本身做到了分离的效果。---》降低了代…...
2024年最新版Java八股文复习
最新版本Java八股文复习,每天更新一篇,博主正在持续努力更新中~~~ 一、Java基础篇1、怎么理解面向对象?简单说说封装、继承、多态三大特性?2、多态体现在哪几个方面?3、面向对象的设计原则你知道有哪些吗?4…...
计算机毕业设计Hadoop+Spark音乐推荐系统 音乐预测系统 音乐可视化大屏 音乐爬虫 HDFS hive数据仓库 机器学习 深度学习 大数据毕业设计
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
MyBatis高级扩展
一、Mapper批量映射优化: 1.需求: Mapper 配置文件很多时,在全局配置文件中一个一个注册太麻烦,希望有一个办法能够一劳永逸 2.配置方式: Mybatis允许在指定Mapper映射文件时,只指定其所在的包: <mappers><package name"c…...
代码美学2:MATLAB制作渐变色
效果: %代码美学:MATLAB制作渐变色 % 创建一个10x10的矩阵来表示热力图的数据 data reshape(1:100, [10, 10]);% 创建热力图 figure; imagesc(data);% 设置颜色映射为“cool” colormap(cool);% 在热力图上添加边框 axis on; grid on;% 设置热力图的颜色…...
浅谈- “ 变量中 无符号 与 有符号 的 值转换 ”
在同一个表达式中,若同时出现 无符号变量 与 有符号变量 : 1、都转换为无符号类型:(注:2^324294967296)即unsigned int 的最大值 2、然后再运行表达式 实例: #include <stdio.h>char fun(…...
【AI绘画】Midjourney进阶:色调详解(上)
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AI绘画 | Midjourney 文章目录 💯前言💯Midjourney中的色彩控制为什么要控制色彩?为什么要在Midjourney中控制色彩? 💯色调白色调淡色调明色调 💯…...
代码管理之Gitlab
文章目录 Git基础概述场景本地修改未提交,拉取远程代码修改提交本地,远程已有新提交 GitIDEA引入Git拉取仓库代码最后位置 Git基础 概述 workspace 工作区:本地电脑上看到的目录; repository 本地仓库:就是工作区中隐…...
wordpress4.9漏洞利用/网站综合排名信息查询
1. 单例模式的简单实现 2. 单例模式的特点 3. 多线程安全的单例模式 4. 模版类的单例模式的实现 5. 使用单例模式需要注意的问题 1. 简单的单例模式如下: 1 class Singleton {2 private:3 Singleton() {};4 ~Singleton() {};5 public:6 static Singleto…...
360未经证实的网站如何做/百度地图网页版
关注 M r . m a t e r i a l , \color{Violet} \rm Mr.material\ , Mr.material ,...
网线水晶头排线图片/谷歌优化的最佳方案
最近使用开发的过程中出现了一个小问题,顺便记录一下原因和方法--串字符串 Description“回文串”是一个正读和读反都一样的字符串,比如“level”或者“noon”等等就是回文串。请写一个程序判断读入的字符串否是是“回文”。 Input输入包括多个试测实例&…...
赣州建设信息网/购买seo关键词排名优化官网
Objects类是一个提供对象基础操作的工具类,其提供的方法包括null-safe或tolerant-safe的对象hashcode计算,toString和比较等。所在路径:javautilObjects.javaObjects类方法列表一、构造器Objects类被final修饰,不能被继承。其构造…...
十个实用网站网址/哪里有网络推广
题目描述 Description【问题描述】C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数…...
阿里云网站方案建设书模板/品牌营销策略有哪些
源自:http://coolketang.com/tutorials/menu4lesson6.php本节将演示如何使用脚本,复制图层并插入到图层列表中某个位置。首先创建一个空白的脚本文档,并保存在硬盘上某个位置。 首先创建一个空白的脚本文档,并保存在硬盘上某个位置…...