IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 使用经验 56 使用#define定义字面值和伪函数 -> 正文阅读

[C++知识库]使用经验 56 使用#define定义字面值和伪函数

#define是C语言提供的宏定义命令,其主要目的:在编程时,为程序员提供一定方便,并能在一定程度上提高程序的执行效率。#define将一个标示符定义为一个字符串,该标示符被称为宏,被定义的字符串称为替换文本。宏定义一般有两种形式:一种是简单宏定义(即字面值),另一种是带参数宏定义(即常说的伪函数)。

简单的宏定义定义形式为:

#define <宏名>  <字符串>

例如:

#define PI  3.1415926

说明:

  • 一般,宏名用大写字母表示,但这并非规定,也可用小写。从编码一致性、可读性角度考虑,强烈建议宏名统一采用大写字母表示。
  • 使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量,同时也避免了由于疏忽导致的字符串书写错误问题。
  • 宏定义是用宏名代替一个字符串,也就是做简单的置换,并不做正确性检查。如写成 :
    #define PI 3.l4l5926(把1错写成了l),在预编译时,不会做任何语法检查;在编译宏展开后的源程序时,才会发现语法错误并报告。
  • 宏定义不是C语句,不必在行尾加分号,如果加了分号,将连同分号一起被替换。例如:
    #define PI 3.1415926;
    area = PI * r * r;
    宏展开后该语句:area = 3.1415926;* r * r;
  • 在程序中,如果#define出现函数外面,宏的有效范围为宏定义之后到本源文件结束,通常宏定义写在文件开头,在此文件范围内有效。另外,用#undef可终止宏定义的作用域,这样可以灵活控制宏定义的作用范围。
  • 在进行宏定义时,可以用已经定义的宏名,可层层置换,但对于用引号括起来的字符串内的字符,即使与宏名相同,也不进行替换。
  • 宏定义是专门用于预处理的专用名词,它与定义变量的含义不同,只做字符替换,不分配内存空间。

在C语言中,通过简单的宏定义恒值常量,是C语言中定义恒值常量的唯一手段。但是在C++中这不在是唯一了,C++的const也可定义一个恒值常量。例如:

const int PI  = 3.1415926;

const定义的恒值常量比#define定义的恒值常量存在很多优点,两者的区别如下:

(1)const定义常量是有数据类型,而#define定义的常量无数据类型

通过const定义的常量,编译器可对其进行静态类型安全检查,而#define宏定义的常量只是简单的字符替换,没有类型安全检查,且有时还会产生边际效应。所谓边际效应,举例如下:

#define N 100
#define M 200 + N

当程序中使用 M*N 时,你期望的结果是:100 * (200+ N),而实际结果是:100 * 200 + N。而const常量就没有这些问题。

(2)有些调试程序可对const进行调试,但不无法对#define进行调试。

(3)当定义局部变量时,const作用域仅限于定义局部变量的函数体内。但#define其作用域不限于定义局部变量的函数体,而是从定义点到整个程序的结束点。但可用#undef取消其定义,从而限定其作用域范围。

(4)const定义常量,并不能起到其强大的作用。const还可修饰函数形式参数、返回值和类的成员函数等。从而提高函数的健壮性。因为const修饰的东西必须受c/c++的静态类型安全检查机制的强制保护,可防止意外的修改。

带参数的宏定义定义形式为:

#define <宏名><参数表><宏体>

例如:

#define MAX(x,y)  (x)>(y)?(x)(y)

标示符被定义为宏后,该标示符便是一个宏名。在程序中,出现宏名的地方,在程序被编译前,先将宏名用被定义的字符串替换,这被称为宏替换。替换后才进行编译,宏替换是简单字符替换。

说明

  • 带参数宏展开,只是将语句中宏名后面括号内的实参字符串,替换#define宏定义中的形式参数。
  • 宏定义时,宏名与包含宏参数的括号之间不应加空格,否则编译器会将空格后的字符都作为替换字符串的一部分。

例如:

#define S (r) PI*r*r  // (在S与(之间有空格)

这个宏真正的含义:S是符号常量(不带参数的宏名),它代表字符串“( r ) PI*r*r”。在程序中,语句:area = S(a);,将会被展开为:area = ( r ) PI*r*r(a);。这显然不对。

明白了带参数的宏定义,也许你会有这个感觉:带参数的宏,有点儿像函数,的确事实也是如此,带参数的宏和函数确实有很多的相同点和不同点。具体带参数的宏和函数有下述区别。

  • 函数调用时,先求实参表达式的值,然后传递给形参,而使用带参数的宏只是进行简单的字符串替换,不进行表达式求值。
  • 函数调用是在程序运行时处理的,为形参分配临时的内存空间,而宏展开是在编译前进行的,在展开时并不分配内存单元,不进行值的传递,同样也没有“返回值”的概念。
  • 函数的实参和形参都必须定义类型,二者的类型要求一致,如不一致,则要进行类型转换,而宏不存在类型问题,因为宏名无类型,它的参数也无类型,仅是字符串替换,展开时代入指定的字符串即可。宏定义时,字符串可是任何类型的数据。
  • 调用函数只会得获得一个返回值,而宏则不然,可设法得到几个返回值。
  • 宏展开会促使源程序变长,因为每次展开都使程序增长,而函数调用不会增加代码长度。
  • 宏替换不占用运行时间,只占编译时间,而函数调用则占用运行时间用于分配单元、保存现场、值传递、返回等。所以宏替换可以提供程序的执行效率,但代价是编译时间会变长。

讨论了宏的两种定义形式,我们继续分享宏的引入给程序带来好处,以及宏定义和命名空间的关系。

(1)宏定义的引入可方便程序的修改。使用简单宏定义,可用宏替换那些在程序中经常使用的常量,当需要修改该常量时,不用对整个程序进行修改,只修改宏定义的字符串即可奏效,而且当常量比较长时, 我们可以用较短、有意义的标识符来编写程序,这样更方便一些。

此处所说的常量改变,不是指在程序运行期间改变,而是在编程期间的改变,举一个大家熟悉的例 子,圆周率π是在数学上常用的一个值,有时我们会用3.14表示,有时也会用3.1415926等,这要看计算所需要的精度,如果我们编制的程序多次使用它,那么需要确定一个数值,在本次运行中不改变,但也许后来发现程序所表现的精度有变化,需要改变它的值, 这就需要修改程序中所有的相关数值,这会给我们带来一定的不便,但如果使用宏定义,使用一个标识符来代替,则在修改时只修改宏定义即可,还可以减少输入3.1415926这样长的数值多次,我们如此定义#define PI 3.1415926,既减少输入,又便于修改,何乐而不为呢?

(2)提高程序的运行效率。使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问题,因为它是在预处理阶段即进行了宏展开,在执行时不需要替换。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况决定是否使用宏定义。

(3)#define预处理器完全没把C++的作用域纳入考量,绝大多数C++实现都是封装在命名空间里,这样的做法有很多优点。但不幸的是,#define的作用域并未被限定在名字空间中,例如:

namespace Influential
{
	#define MAX 1<<16
	// Infuential命名空间实现
}
namespace Facility
{
	const int max = 512;
	// Facility 命名空间实现
}

int a[MAX]; // 可以通过编译,但这种实现是糟糕的。

讨论一下,上述代码为什么是糟糕的实现?按照命名空间的原理,定义在Infuential空间中的MAX不能在空间之外使用。但由于MAX是宏定义,不受名字空间的限制。

最后,需要提醒的是,宏不仅可定义字面值和伪函数。另一个重要的用法是条件编译。在大规模的开发过程中,特别是跨平台和系统的软件里。可以在编译的时候,通过#define设置编译环境。例如:

#ifdef WINDOWS
	......
	......
#endif

#ifdef LINUX
	......
	......
#endif

请谨记

  • 宏可以定义字面值常量,也可以定义带参数的伪函数。在宏定义时需注意其与函数的区别。宏定义不是C++的语句。在编译时使用宏的地方会发生字符串替换。
  • 在应用时,如果你定义一个字面值常量建议使用const替换#define。
  • 宏定义不受名字空间的限制。在名字空间外依然可见。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:19:22  更:2021-08-01 14:19:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/1 10:18:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码