C 语言 —— 明示常量指令:#define
预处理指令可以出现在源文件的任何地方。
变量有作用域,#define 定义的明示常量同样有其作用区域,其定义从指令出现的地方到该文件末尾或者 #undef 命令之间有效。
使用 #define 指令最常见的用法就是用来定义明示常量(也叫做符号常量),但是该指令还有许多其他用途。
用法:#define 标识符 常量
。注意后面没有分号!!
#define 又称宏定义,标识符是所定义的宏名,简称宏。标识符的命名规则遵守 C 语言的标识符命名规则,不过通常采用大写字母来命名宏,以此提醒程序员注意。#define 的功能是将标识符定义为其后的常量。
明示常量(符号常量)
C 语言中,可以用 #define 定义一个标识符来表示一个常量。其特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。因此这种常量也叫做符号常量。
// 例1.
#include<stdio.h>
#define A1 300
#define S1 "Hello, World"
int main()
{const int a2 = 100;cosnt char* s2 = "hello, world";printf("%d\n%s\n", A1, S1);printf("%d\n%s\n", a2, s2);return 0;
}
1. 常考:预处理阶段只进行文本替换,不进行运算
对于最后一条,预处理指令定义的标识符都不会占据内存。预处理指令也称预编译指令,即在编译之前进行一些处理,而预处理做的其实就是文本替换,这个过程也叫做宏展开,即用宏定义后面的替换体将程序中出现宏的地方替换掉。
例如,#define 定义的明示变量就是在预处理阶段用标识符后面的值替换标识符,因此不会占据内存。例如,上面程序中将在预处理阶段将 printf("%d\n%s\n", A1, S1);
替换成 printf("%d\n%s\n", 300, "Hello, World");
在这个文本替换过程中需要注意的一点是,#define 只执行文本替换,不执行计算!这个考点在各种笔试中都经常碰到。例如:
// 例2.
#include<stdio.h>
#define SUM 3+4
int main()
{int a = SUM * SUM;printf("%d\n", a);return 0;
}
例2. 运行结果是 19,而不是 49。因为预处理阶段预处理器将 SUM 替换成 3+4,经过预处理之后的程序为 int a = 3 + 4 * 3 + 4;
,在运行阶段程序才进行计算,此时根据运算符优先级,先算 * 的部分,因此 a 的值为 3+12+4,即 19。
2. 易错点
// 例3.
#include<stdio.h>
#define PI 3.14;
int main()
{double r = 4.3;double s = PI * r * r;printf("%lf\n", s);return 0;
}
例3. 会在 PI * r
处报错,错误描述为:"*" 的操作数必须是指针,但它具有类型 "double"
。这时候是不是很纳闷为什么作为乘法运算符会被识别为指针的解引用?原因在于 PI 宏定义中多加了分号!在预处理阶段例3. 中的 double s = PI * r * r;
会被替换成 double s = 3.14; * r * r;
这下就能明白为什么会被识别为解引用运算符了吧。
4. 什么时候用明示常量?
对于绝大部分数字常量,应该使用字符常量。如果在算式中用字符常量代替数字,常量名能更清楚地表达该数字的含义。如果是表示数组大小的数字,用符号常量后更容易改变数组的大小和循环次数。如果数字是系统代码(如,EOF),用符号常量表示的代码更容易移植(只需改变EOF的定义)。助记、易更改、可移植,这些都是符号常量很有价值的特性。
C 语言现在也支持 const 关键字,提供了更灵活的方法。用 const 可以创建在程序运行过程中不能改变的变量,可具有文件作用域或块作用域。另一方面,宏常量可用于指定标准数组的大小和 const 变量的初始值。
5. 宏定义和 const 常量区别
- 定义的区别:
- 明示变量用
#define
声明,而 const 常量用const + 数据类型
声明。 - 明示变量最后没用分号,const 常量声明需要用分号表示语句结束。
- 明示变量不需要用等号赋值,cosnt 常量需要用等号赋值。
- 明示变量不占内存,cosnt 常量需要占据内存。
- 明示变量用
- 处理阶段阶段的不同:
- 宏定义在预处理阶段进行文本替换。
- cost 常量在程序运行时使用。
- 存储方式不同:
- 宏定义是直接替换,不会分配内存,存储于程序的代码段中。
- const 常量需要进行内存分配。
- 是否进行类型检查:
- 宏定义是字符替换,不进行类型检查。
- const 常量定义时需要声明数据类型,使用时会进行类型检测。
- 宏定义可以声明函数。
宏函数
宏函数介绍
前面介绍了明示常量(宏定义),其实宏定义除了定义常量之外还可以定义宏函数。
在 #define 中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中。
// 例4.
// 宏函数定义
#define SQUARE(X) X*X
// 在程序中使用:
z = SQUARE(2); // 结果:z = 4;
例4. 看上去像函数调用,但是它的行为和函数调用完全不同。
例5.
#define SQUARE(X) X*X
int t = 4;
x = SQUARE(t); // result: x = 16
y = SQUARE(t+2);// result: y = 14
例5. 演示了宏函数和函数调用的不同,y = SQUARE(t+2)
在预处理阶段被替换为 y = t + 2 * t + 2
,因此 y 的值为 14。如果想要避免这样情况,需要在宏定义时多加几个括号来确保运算和结合的正确顺序。
#define SQUARE(X) ((X)*(X))
替换体最外面的括号是为了避免诸如 100/SQUARE(4)
这样的情况下运算顺序。如果不加最外面的括号,在预处理阶段替换之后就变成了 100/(4)*(4)
,会先算 100/4,再将结果×4。
尽管如此,这样做还是无法避免程序中最后一种情况的问题 —— SQUARE(++x) 变成了 ++x*++x,递增了两次x,一次在乘法运算之前,一次在乘法运算之后:++x*++x = 6*7 = 42。由于标准并未对这类运算规定顺序,所以有些编译器得 7*6。而有些编译器可能在乘法运算之前已经递增了x,所以7*7得49。
解决这个问题最简单的方法是,避免用 ++x 作为宏参数。一般而言,不要在宏中使用递增或递减运算符。
字符串中的宏参数:#
// 例6.
#define PSQR(X) printf("The square of X is %d.\n", ((X)*(X)));
PSQR(8); // 输出为: The square of X is 64.
注意双引号字符串中的 X 被视为普通文本,而不是一个可被替换的记号。C 允许在字符串中包含宏参数。在类函数宏的替换体中,# 号作为一个预处理运算符,可以把记号转换成字符串。例如,如果 x 是一个宏形参,那
么 #x 就是转换为字符串"x"的形参名,这个过程称为字符串化。
#define PSQR(X) printf("The square of " #X " is %d.\n", ((X)*(X)));
int y = 8;
PSQR(y); // 输出为: The square of y is 64.
调用宏函数的时候,用 “y” 替换 #x,printf("The square of " "y" " is %d.\n", ((y)*(y)));
,然后利用 ANSI C 字符串的串联特性将这些字符串组合起来,printf("The square of y is %d.\n", ((y)*(y)));
预处理器粘合剂:##
与 # 运算符类似,## 运算符可用于类函数宏的替换部分。而且,## 还可用于对象宏的替换部分。## 运算符把两个记号组合成一个记号。例如,可以这样做:
#define XNAME(n) x ## n
然后,宏 XNAME(4) 将展开为 x4。
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void)
{int XNAME(1) = 14; // 变成 int x1 = 14; int XNAME(2) = 20; // 变成 int x2 = 20; int x3 = 30; PRINT_XN(1); // 变成 printf("x1 = %d\n", x1); PRINT_XN(2); // 变成 printf("x2 = %d\n", x2); PRINT_XN(3); // 变成 printf("x3 = %d\n", x3); return 0;
}
// 该程序的输出如下: x1 = 14 x2 = 20 x3 = 30
PRINT_XN() 宏用 # 运算符组合字符串,## 运算符把记号组合为一个新的标识符。
变参宏:… 和 VA_ARGS
C99/C11 对宏提供了变参的工具。虽然标准中未使用“可变”这个词,但是它已成为描述这种工具的通用词(虽然,C 标准的索引添加了字符串化词条,但是,标准并未把固定参数的函数或宏称为固定函数和不变宏)。
变参宏通过把宏参数列表中最后的参数写成省略号(即,3个点…)来实现这一功能。而 __VA_ARGS____可用在替换部分中,表明省略号代表什么。
#include <stdio.h>
#include <math.h> #define PR(X, ...) printf("Message " #X ": " __VA_ARGS__)
int main(void)
{double x = 48double y; y = sqrt(x); PR(1, "x = %g\n", x); PR(2, "x = %.2f, y = %.4f\n", x, y); return 0;
}
// 输出结果:
// Message 1: x = 48
// Message 2: x = 48.00, y = 6.9282
记住,省略号只能代替最后的宏参数
#define WRONG(X, ..., Y) #X #_ _VA_ARGS_ _ #y //不能这样做
宏和函数的选择
有些编程任务既可以用带参数的宏完成,也可以用函数完成。应该使用宏还是函数?
使用宏比使用普通函数复杂一些,稍有不慎会产生奇怪的副作用。
宏和函数的选择实际上是时间和空间的权衡。
宏生成内联代码,即在程序中生成语句。如果调用20次宏,即在程序中插入20行代码。如果调用函数20次,程序中只有一份函数语句的副本,所以节省了空间。然而另一方面,程序的控制必须跳转至函数内,随后再返回主调程序,这显然比内联代码花费更多的时间。 因此,对于简单的函数,程序员通常用宏来处理。
C99 提供了第3种可替换的方法——内联函数。
在使用宏函数时,依旧推荐使用大写字母表示宏,以此来提醒程序员宏带来的副作用。
如果打算使用宏来加快程序的运行速度,那么首先要确定使用宏和使用函数是否会导致较大差异。在程序中只使用一次的宏无法明显减少程序的运行时间。在嵌套循环中使用宏更有助于提高效率。许多系统提供程序分析器以帮助程序员压缩程序中最耗时的部分。
C 语言 —— 明示常量指令:#define相关推荐
- C语言常用宏定义(#define)使用方法
· 正 · 文 · 来 · 啦 · 前言 ------在上篇文章里面,我们分析了预处理的一个完整过程,这能够让我们理解一个写好的程序,在生成一个可执行文件,到底发生了什么,对我们在大型 ...
- define定义的函数如何引用_C语言快速入门——使用#define让程序更易维护
与变量在运行时可以通过赋值操作更改这一特性不同,常量是一种在程序执行过程中,其值不发生改变的量.我们目前介绍了int.float.char三种数据类型,与它们相对应的常量分别为整型常量.浮点常量.字符 ...
- c语言浮点型常量表示平均数_数据类型与常量、变量解析
C语言入门参考-第五章-数据类型与常量.变量 常量即为在程序运行过程中值不会改变的量,常量又有字面常量与符号常量之分.符号常量使用#define预处理器指令与const关键字定义,(#define符号 ...
- c语言if多条件并列_C/C++编程笔记:C语言预编译指令—条件编译,零基础推荐收藏
一. 内容概述 本文主要介绍c语言中条件编译相关的预编译指令,包括#define.#undef.#ifdef.#ifndef.#if.#elif.#else.#endif.defined. 二.条件编 ...
- c语言中常量有何作用,正确的C语言常量是什么?
C语言的常量有整数常量,实型常量,字符常量,符号常量等. 1.整数常量 整数常量是指直接实用的整形常数,又称整形常数或者整数,例如,1,-9等.整数常量可以是长整形.短整型.符号整型和无符号整型. a ...
- C语言中常量、变量和函数
1. 常量 常量指定的是在软件编程过程中不能给赋值且值不能被改变的量.一般包括数字.字符.字符串常量等. 例如:整型常量:12.0.-3: 实型常量:4.6.-1.23: 字符常量:'a'.'b'. ...
- c语言的预编译指令是什么,c语言预编译指令有哪些?
c语言预编译指令有哪些? 预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理器指令有:文件包含.条件编译.布局控制和宏替换4种. 文件包含 #include是一种最为常 ...
- 合法的c语言整形 常量,0011在c语言是不是合法的整形常量?
2018-07-23 正确的C语言常量是什么? C语言的常量有整数常量,实型常量,字符常量,符号常量等.1.整数常量 整数常量是指直接实用的整形常数,又称整形常数或者整数,例如,1,-9等.整数常量可 ...
- c语言 const常量作用,C语言 const常量讲解
//const的本质 //const本质上是伪常量,无法用于数组初始化以及全局变量初始化 //原因在于const仅仅限定变量无法直接赋值,但是却可以通过指针间接赋值 //例如局部常量在栈区,而不在静态 ...
最新文章
- 简述ospf的工作原理_物联网水表工作原理简述
- android之SharedPreferences
- java中生成1000~10000之间的随机数
- 实现了某一个接口的匿名类的例子_java中的内部类内部接口详解,一文搞定
- #python计算结果百位500向下取整,(0-499取000,500-999取500)
- 大数据分析机器学习(一)之线性模型-年龄和心率关系
- PHP获取一段时间内的每个周几, 每月几号, 遇到特殊日子就往后延
- 2016年ICT行业前瞻:竞合生态,牵着手 一起走
- android中计算日期差,Android编程实现根据不同日期计算天数差的方法
- 关于如何修改CSDN中的字体大小和颜色
- CentOS 6.5 中安装Jenkins
- system.Exception:端口已被占用1080
- 华为C语言的编程规范
- 两个网段共享打印机_两个网段打印机共享
- 蓝桥杯 并查集汇总学习 及其代码
- 苹果笔记本如何安装windows系统
- 开放原子开源基金会OpenHarmony开发者大会2023圆满举办
- 【重新定义matlab强大系列一】利用MATLAB进行清洗缺失数据
- 刘剑 计算机科学与技术,刘剑-控制科学与工程学院
- 在Visual Studio 2019中创建ASP.NET Web项目