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. java反射技术详解

    反射: 其实就是动态的从内存加载一个指定的类,并获取该类中的所有的内容. 反射的好处:大大的增强了程序的扩展性. 反射的基本步骤: 1. 获得Class对象,就是获取到指定的名称的字节码文件对象. 2 ...

  2. [原创]MySQL innodb_rollback_on_timeout参数对锁的影响

    环境:Server version:         5.6.21-log MySQL Community Server (GPL) 前提提要: innodb_rollback_on_timeout是 ...

  3. MySQL修改root密码的多种方法

    不知道在cmd模式下是不是区分大小写,我大写成功了,小写没成功,不知是我拼写的错误,还是区分大小写!!?? 方法1: 用SET PASSWORD命令 mysql -u root mysql> S ...

  4. php 图片上传的公共方法(按图片宽高缩放或原图)

    写的用于图片上传的公共方法类调用方法: $upload_name='pic';$type = 'logo_val';$file_name = 'logo_' . $user_id .create_st ...

  5. 初探XSS

    1. 基础准备知识 (1) php: <?php ?>部分由服务器解析后并连带html代码一并返回给浏览器,类似jsp的操作,一般开发中都使用smarty模板将前端后端分开.所以在XSS跨 ...

  6. Js函数的概念、作用、创建、调用!

    一.函数是用来帮助我们封装.调用代码的最方便的工具! 二.函数的创建方法有三种: 三.函数的创建方式有3种,调用方式也不是单一的,调用方式有4种!      1.作为一个函数去调用 函数名+();(函 ...

  7. Android ANR 分析解决方法

    一:什么是ANR ANR:Application Not Responding,即应用无响应 二:ANR的类型 ANR一般有三种类型: 1. KeyDispatchTimeout(5 seconds) ...

  8. Net作业调度(一) -Quartz.Net入门

    背景 很多时候,项目需要在不同时刻,执行一个或很多个不同的作业. Windows执行计划这时并不能很好的满足需求了,迫切需要一个更为强大,方便管理,集群部署的作业调度框架. 介绍 Quartz一个开源 ...

  9. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...

  10. 微软CMS项目 Orchard 所用到的开源项目

    研发了Orchard一年左右了,时常遇到瓶颈,总觉得力不从心,其实并不是基础不够,关键还是概念性的东西太多,一会儿这个概念名词,一会那个,关于Orchard的技术文档也的确很少,每次看起来总是焦头烂额 ...