ANSI C规定:#前可以有空格或者tab,#和指令其余部分之间也可以有空格,可以出现在任何地方,作用域从定义处到文件结尾。

因为预处理开始前,系统会删除反斜线和换行符的组合,故可以把指令扩展到几个物理行,这些物理行组成单个逻辑行。

//每个#define行(指逻辑的行):三部分组成
//指令本身 宏 替换列表(或主体)
#define PI 3.141592653

宏分为类对象宏(代表值的宏)和类函数宏
宏的名字中间不能有空格,必须遵循c命名规则,从宏变成最终的替换文本叫宏展开,预处理器不进行计算,只是简单的文本替换

语言符号类型字符串和字符型字符串

系统把主体当作语言符号类型字符串,而不是字符型字符串,预处理器中的语言符号是宏定义主体里的单独的词,用空白字符把这些词分开。

#define SIX 2*2//定义里有一个语言符号,序列2*2
#define AAA 2 * 2//定义里有三个语言符号,2,*,3

若把主体解释为字符型字符串,预处理器用2    *     2替换AAA,额外的空格也算替换

若把主体解释为语言符号类型字符串,预处理器只用单个空白字符分割的三个语言符号去去替换AAA

2 * 2的空格只是分割主体语言符号的符号,不会都算(注意:不同编译器做法不一样)

重定义常量

开始把AAA定义为常量4,后来在该文件里,又把AAA定义为10,这叫重定义常量,不同编译器策略不一样,不过标准c规定:这是错误的,只允许新定义和旧定义完全相同!

//相同定义意味主体具有相同顺序的语言符号
#define A 2 * 3
#define A 2 * 3//两者等价,都是三个语言符号的主体

下面的定义被ANSI C认为不同

#define A 2*3//只有一个语言符号的主体,可以用#undef指令重新定义宏

在#define里使用参数——类函数宏

类函数宏:外形和作用和函数类似,可以使用参数,也是()括起来。一般要主体参数里都加上小括号来避免文本替换的错误发生。

#define  SQRT(x)  x*x
int main(void)
{
int x = ;
printf("%d\n", SQRT());//
printf("%d\n", SQRT());//
printf("%d\n", SQRT( + x));//
printf("%d\n", SQRT( + ));//没有打印16,而是8=2 + 2 * 2 + 2
//一定主要,类函数宏也是文本替换,可以通过在主体里加圆括号来约束结合性   //避免在宏里使用增量和减量运算符
printf("%d\n", SQRT(++x));//16,++x*++x,x两次增量,一次在乘法前,一次在后
printf("%d\n", x);//4
//不同编译器处理不一样,4*4=16,这里x先两次自增到4,再乘。有的编译器不一样,故宏避免使用增量减量
system("pause");
return ;
}

宏里的#和##运算符

#include <stdio.h>
#include <stdlib.h>
#define PSQR(X) printf("x的平方 = %d\n", ((X) * (X)));//注意这里的;是printf语句的分号
int main(void)
{
PSQR();
system("pause");
return ;
}

结果是 x 的平方 = 9

注意,printf引号里的x被看作是普通的文本,而不是一个可以被替换的语言符号!可以在类函数宏的替换部分,使用#运算符做预处理,这样普通文本转换为可以被替换的语言符号!如果x是宏参数,那么#x可以把参数名转换为相应的字符串处理,就可以输出3,而不是X!(该过程也叫字符串化)。

#define PSQR(X) printf(#X "的平方 = %d\n", ((X) * (X)));
int main(void)
{
PSQR();
system("pause");
return ;
}

3的平方 = 9

调用宏,用 ”3” 代替 #X ,标准C把这些字符串链接起来,产生最终结果。

#只可以用于类函数宏,而##运算符既可以用于类函数宏,也可以用到类对象宏。

##可以把两个语言符号组合为单个语言符号!

#define XNAME(n) X##n //类函数宏里,n是宏参数(一个语言符号),X是一个语言符号,加上##,两者变一个语言符号
#define PRINT_XN(n) printf("x" #n " = %d \n", X ## n);//注意这里X必须大写,因为XNAME里X大写了
int main(void)
{
int XNAME(1) = 14;//相当于 int x1 = 14;
PRINT_XN(1);
system("pause");
return 0;
}

打印 x1 = 14

可变宏

函数可以接收固定参数,也可以接收可变参数(printf函数)。同样宏也是这样。

注意:可变,字符串化是c的词汇,但是固定函数,固定宏,不变宏等不是C的词汇。

实现思想:宏定义的参数列表最后一个参数为省略号…(三个点),预定义宏__VA_ARGS__就可以用在被替换部分,说明省略号的含义。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PR(X, ...) printf("可变宏:" #X __VA_ARGS__ );//注意这里是两个下划线__
int main(void)
{
double x = ;
double y;
y = sqrt(x);
//第一个参量对应宏的X,值为1,则#X 变为1,宏展开后成为:
//printf("可变宏:" "1" "x = %g\n", x);
//然后三个字符串被编译器连接为:
//printf("可变宏:1x = %g\n", x);
//最后指向输出
PR(, "x = %g\n", x);//__VA_ARGS__告诉了省略号代表了什么
//注意:省略号只能代表最后一个参数!!!
system("pause");
return ;
}

宏是用空间换取时间,函数是用时间换取空间,比如,使用宏100次,那么会在程序里插入100次宏代表的代码,浪费空间,但是速度很快。使用函数100次,程序只有一份函数的拷贝,节省空间,但是每次调用函数,都要压栈出栈,返回调用点,浪费时间!

简单函数就能处理的功能,一般用宏,或者内联函数。

宏名字不能有空格,必须大写(约定俗成的,提醒程序员这是宏),宏不检测类型只是简单的文本替换,好处是int类型,或者double类型等都能使用。

用圆括号括起来类函数宏里的每个参数,好习惯,避免文本替换造成逻辑混乱的错误。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

回忆:#define的用法的更多相关文章

  1. typedef和#define的用法与区别

    typedef和#define的用法与区别 typedef和#define的用法与区别 一.typedef的用法 在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程 ...

  2. C语言中#define的用法(转)

    转自:http://www.dingge.com/main/article.asp?id=10 今天整理了一些#define的用法,与大家共享! 1.简单的define定义 #define MAXTI ...

  3. define的用法

    define的用法小结 define的用法只是一种纯粹的替换功能,宏定义的替换是预处理器处理的替换. 一:简单的宏定义用法 格式:#define 标识符 替换内容 替换的内容可以是数字,字符,字符串, ...

  4. C语言中#define的用法

    今天整理了一些#define的用法,与大家共享! 1.简单的define定义 #define MAXTIME 1000 一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写 if(i& ...

  5. 【转】typedef和#define的用法与区别

    typedef和#define的用法与区别 一.typedef的用法 在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像: ...

  6. c++define的用法

    c++define的用法   在写程序时经常会碰到这样一个问题,我们需要重复写很多相同的代码,并且这些代码结构相同.总是想自己把这段代码封装一下然后直接进行调用,但是如果这段代码逻辑并不复杂,并且代码 ...

  7. (转)typedef和#define的用法与区别

    typedef和#define的用法与区别 一.typedef的用法 在C/C++语言中,typedef常用来定义一个标识符及关键字的别名,它是语言编译过程的一部分,但它并不实际分配内存空间,实例像: ...

  8. c++ define的用法(转)

    #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用 ...

  9. define的用法与注意事项

    ------------------------------------------------- 在编程使用宏替换时,当字符串中不只一个符号时,加上括号表现出优先级, 如果是带参数的宏定义,则要给宏 ...

随机推荐

  1. ArcGIS Server SOE开发之奇怪异常:

    添加之后结果显示如下:fjsontokenezkBvir0Tj5q31UEst7pTFPwrwocmHklCajKeh-xXM91qWdBXDuQMmtGcaHaaXCJ 具体如下: 该SOE扩展在另 ...

  2. FastReport.Net 常用功能总汇

    一.常用控件 文本框:输入文字或表达式 表格:设置表格的行列数,输入数字或表达式 子报表:放置子报表后,系统会自动增加一个页面,你可以在此页面上设计需要的报表.系统在打印处理时,先按主报表打印,当碰到 ...

  3. centos中yum安装mysql路径

    1. 使用命令service mysqld stop 停止mysql 查看mysql数据库的默认路径:/var/lib/mysql 使用cp -afir  /var/lib/mysql/*   /us ...

  4. while(cin.eof)出错 poj

    zoj遇到c++如何判定输入流结尾的问题,一不小心就超时了 楼下的代码可以通过zoj #include<iostream> using namespace std; int main(){ ...

  5. 执行jar文件生成pdf报错,Unsupported URL <file:///home

    java -Djava.library.path=/usr/local/lib/ruby/gems/1.8/gems/sharp_office-1.0.1/ext/sigar -jar /usr/lo ...

  6. PHP文件相关的操作函数——目录操作

    1.有关文件类型的函数 PHP是以UNIX的文件系统为模型的,因此在Windows系统中我们只能获得“file”.“dir”或者“unknown”三种文件类型.而在UNIX系统中,我们可以获得“blo ...

  7. linux内核分析作业8:理解进程调度时机跟踪分析进程调度与进程切换的过程

    1. 实验目的 选择一个系统调用(13号系统调用time除外),系统调用列表,使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 分析汇编代码调用系统调用的工作过程,特别是参数的传递的方 ...

  8. js问题杂记

    1.如何把字符串数组 转成数组对象? eval妙用 var str = "[\"UserName=1,Pwd=1\",\"UserNmae=1,Pwd=1,Sa ...

  9. 在Visual Studio中将现有.NET Framework项目迁移至.NET Core 1.1 Preview 1

    1)下载安装包含 .NET Core 1.1 Preview 1 的 SDK:Windows x64 安装包(下载地址列表) 2)下载最新 VS 2015 NuGet 插件:https://dist. ...

  10. 你必须知道的Javascript 系列

    JavaScript是见过最多人说它“有趣”,“好玩”的一门语言.不仅仅是因为它的灵活性,包括它本身很多的特性,比如说原型链,作用域链都是非常好玩的东西.现在已经有很多的JavaScript设计模式, ...