重读The C programming Lanuage 笔记四:c预处理
C预处理器执行宏替换、条件编译以及包含指定的文件。以#开头的命令行就是与处理器的对象。这些命令行的语法独立于语言的其他部分,它们可以出现在任何地方,其作用可延续到所在编译单元的末尾(与作用域无关)。行边界是有实际意义的;每一行都将单独进行分析。对预处理器而言,记号可以是任何语言记号,也可以是类似于#include指令中表示文件名的字符序列。此外,所有未进行其他定义的字符都将被认为是记号。但是在预处理器指令行中,除空格、横向制表符以外的其他空白符的作用都是没有意义的。
以下摘自百度知道http://zhidao.baidu.com/link?url=kRcWYY-uCXnJSZAovYKbx5iuuRN6NOzPgG13tsDgAK2bIoOc-ngUJFfibod6RWQK1rs6Ni8zOeQDCkEzMPf1BSSo9HbjawwGGAyDCwlcDly
————————————————————————————————————————————————————————————
一、 宏定义和扩展
宏定义:
1.不带参数的宏定义:
格式:#define 标识符 字符串 标识符即“宏名"
预处理器的的工作叫做宏展开:将宏名替换为字符串
如 #define Pi 3.14
说明:
1、宏名一般大写
2、使用宏可提高程序的通用性和易读性,便于修改。 例如:数组大小常用宏定义
3、预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
4、宏定义末尾不加分号(;)
5、宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
6、可以用#undef命令终止宏定义的作用域
7、宏定义可以嵌套
8、字符串”“中永远不包含宏
9、宏定义不分配内存,变量定义才分配内存
2,带参数的宏定义
格式:#define 宏名(参数表) 字符串
例如:#define S(a,b) a*b 则m =S(3,2) 被展开为 m = 3*2 (宏展开只替换不计算结果,此时m值不为6)
说明
1、实参如果是表达式容易出问题 如#define s(x) x*x 则 s(x+2) 将扩展为 x + 2 * x + 2 正确应写为 #define s(x) (x)*(x)
2、宏名和参数的括号间无空格、
3、宏替换只作替换,不做计算,不做表达式求解
4、函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存
5、宏的哑实结合不存在类型,也没有类型转换。
6、函数只有一个返回值,利用宏则可以设法得到多个值
7、宏展开使源程序变长,函数调用不会(函数调用会有额外开销)
8、宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)
#define用法
1、 用无参宏定义一个简单的常量
#define LEN 12
这个是最常见的用法,但也会出错。
比如下面几个知识点你会吗?可以看下:
(1) #define NAME "zhangyuncong"
程序中有"NAME"则,它会不会被替换呢?
(2) #define 0x abcd
可以吗?也就是说,可不可以用把标识符的字母替换成别的东西?
(3) #define NAME "zhang
这个可以吗?
(4) #define NAME "zhangyuncong"
程序中有上面的宏定义,并且,程序里有句:
NAMELIST这样,会不会被替换成"zhangyuncong"LIST
四个题答案都是否定的。
第一个,""内的东西不会被宏替换。这一点应该大都知道。
第二个,宏定义前面的那个必须是合法的用户标识符
第三个,宏定义也不是说后面东西随便写,不能把字符串的两个""拆开。
第四个:只替换标识符,不替换别的东西。NAMELIST整体是个标识符,而没有NAME标识符,所以不替换。
也就是说,这种情况下记住:#define 第一位置第二位置
(1) 不替换程序中字符串里的东西。
(2) 第一位置只能是合法的标识符(可以是关键字)
(3) 第二位置如果有字符串,必须把""配对。
(4) 只替换与第一位置完全相同的标识符
还有就是老生常谈的话:记住这是简单的替换而已,不要在中间计算结果,一定要替换出表达式之后再算。
2、 带参宏一般用法
比如#define MAX(a,b) ((a)>(b)?(a):(b))
则遇到MAX(1+2,value)则会把它替换成:
((1+2)>(value)?(1+2):(value))
注意事项和无参宏差不多。
但还是应注意
#define FUN(a) "a"
则,输入FUN(345)会被替换成什么?
其实,如果这么写,无论宏的实参是什么,都不会影响其被替换成"a"的命运。
也就是说,""内的字符不被当成形参,即使它和一模一样。
那么,你会问了,我要是想让这里输入FUN(345)它就替换成"345"该怎么实现呢?
请看下面关于#的用法
3、 有参宏定义中#的用法
#define STR(str) #str
#用于把宏定义中的参数两端加上字符串的""
比如,这里STR(my#name)会被替换成"my#name"
————————————————————————————————————————————
附注:
假定有下列定义:#define tempfile(dir) #dir "%s" 宏调用tempfile(/usr/tmp)将生成"/usr/tmp" "%s"
随后,该结果将被连接为一个单个的字符串。给定下列定义: #define cat(x, y) x ## y 那么,宏调用cat(var, 123)将生成var123。
但是,宏调用cat(cat(1,2),3)没有定义:##阻止了外层调用的参数的扩展。因此,它将生成下列记号串:cat ( 1 , 2 )3
并且,)3 (不是一个合法的记号,它由第一个参数的最后一个记号与第二个参数的第一个记号连接而成。
如果再引入第二层的宏定义,如下所示:#define xcat(x, y) cat(x,y)
我们就可以得到正确的结果。xcat(xcat(1, 2), 3)将生成123,因为xcat 自身的扩展不包含##运算符。
类似地,ABSDIFF(ABSDIFF(a,b),c)将生成所期望的经完全扩展后的结果。
————————————————————————————————————————
一般由任意字符都可以做形参,但以下情况会出错:
STR())这样,编译器不会把“)”当成STR()的参数。
STR(,)同上,编译器不会把“,”当成STR的参数。
STR(A,B)如果实参过多,则编译器会把多余的参数舍去。(VC++2008为例)
STR((A,B))会被解读为实参为:(A,B),而不是被解读为两个实参,第一个是 (A 第二个是 B)。
4、 有参宏定义中##的用法
#define WIDE(str) L##str
则会将形参str的前面加上L
比如:WIDE("abc")就会被替换成L"abc"
如果有#define FUN(a,b) vo##a##b()
那么FUN(id ma,in)会被替换成void main()
5、 多行宏定义:(行末加”\“,参见九、行连接)
#define doit(m,n) for(int i=0;i<(n);++i)\
{\
m+=i;\
}
——————————————————————————————————————————————————————————
以上
二、文件包含
下列形式的控制指令:#include <文件名>
将把该行替换为文件名指定的文件的内容。文件名不能包含>或换行符。如果文件名中包含字符"、'、\、或/*,则其行为没有定义。预处理器将在某些特定的位置查找指定的文件
查找的位置与具体的实现相关。
类似地,下列形式的控制指令:#include "文件名"
首先从源文件的位置开始搜索指定文件(搜索过程与具体的实现相关),如果没有找到指定的文件,则按照第一种定义的方式处理。如果文件名中包含字符'、\、或/*,其结果仍然
是没有定义的,但可以使用字符>。
最后,下列形式的指令行:
#include 记号序列
同上述两种情况都不同,它将按照扩展普通文本的方式扩展记号序列进行解释。记号序列必须被解释为<...>或"..."两种形式之一,然后再按照上述方式进行相应的处理。
#include文件可以嵌套。
三、条件编译
预处理器条件:
if 行文本elif 部分opt else部分opt #endif
if行:
#if 常量表达式
#ifdef 标识符
#ifndef 标识符
elif部分:
elif行文本elif 部分opt
elif行:
#elif 常量表达式
else 部分:
else行文本
else行:
#else
其中,每个条件编译指令在程序中单独占一行。预处理器依次对#if以及以后的#elif行中的常量表达式进行计算,直至发现某个指令的常量表达式为非零值为止,这是将放弃值为零的指令行后面的文本。
#if和#elif中的常量表达式将执行通常的宏替换。并且如: define 标识符 或 Define(标识符) 都将在宏扫描之前进行替换。
如果该标识符在预处理器中已经定义,则用1替换,否则用0替换
#ifdef 标识蒋
#ifndef 标识符
分别等价于:
#if defined 标识符
#if !defined 标识符
例如:为保证hdr.h文件只被包含一次,可以将该文件内容包含在下列形式的条件语句中
#if !define(HDR)
#define HDR
//HDR内容
#endif
四、行控制
为了便于其它预处理器生成C语言程序,下列形式的指令行:
#line 常量 "文件名"
#line 常量
将使编译器认为(出于错误诊断的目的):下一行源代码的行号是以十进制整型常量的形式给出的,并且,当前的输入文件是由该标识符命名的。如果缺少带双引号的文件名部分,则将
不改变当前编译的源文件的名字。行中的宏将先进行扩展,然后再进行解释。
五、错误信息生成
下列形式的预处理器控制指令:
#error 记号序列 opt
将使预处理器打印包含该记号序列的诊断信息。
六、pragma
下列形式的控制指令:
#pragma 记号序列opt
将使预处理器执行一个与具体实现相关的操作。无法识别的pragma(编译指示)将被忽略掉。
七、空指令
下列形式的预处理器行不执行任何操作:
#
八、预定义名字
某些标识符是预定义的,扩展后将生成特定的信息。它们同预处理器表达式运算符defined一样,不能取消定义或重新进行定义。
__LINE__ 包含当前源文件行数的十进制常量。
__FILE__ 包含正在被编译的源文件名字的字符串字面值。
__DATE__ 包含编译日期的字符串字面值,其形式为“Mmm dd yyyy”。
__TIME__ 包含编译时间的字符串字面值,其形式为“hh:mm:ss”。
__STDC__ 整型常量1。只有在遵循标准的实现中该标识符才被定义为1。
说明:#error 与#pragma 是ANSI 标准中新引入的特征。这些预定叉的预处理器宏也是新引入的,其中的一些宏先前已经在某些编译器中实现。
九、行连接(在上文宏展开中提到)
通过将以反斜杠\结束的指令行末尾的反斜杠和其后的换行符删除掉。可以将若干指令行合并成一行。这种处理要在分隔记号之前进行。
十、三字符序列(很少使用吧)
C语育源程序的字符集是7 位ASCII码的子集,但它是ISO 646-1983不变代码集的超集。为了将程序通过这种缩减的字符集表示出来,下列所示的所有三字符序列都要用相应的单个
字符替换,这种替换在进行所有它他处理之前进行。
??= # ??( [ ??< {
??/ \ ??) ] ??> }
??' ^ ??! | ??- ~
除此之外不进行其它替换。
说明:三字符序列是ANSI标准新引入的特征。
以上内容除标注外大多摘抄自THE C PROGRAMMING LANGUAGE (2nd)中文版
重读The C programming Lanuage 笔记四:c预处理的更多相关文章
- 重读The C programming Lanuage 笔记三:简单计算器程序
//简单计算器 #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <str ...
- 重读The C programming Lanuage 笔记二:运算符优先级
运算符的优先级和结合性有明确的规定,但是,除少数例外情况外,表达式的求值次序没有定义,甚至某些有副作用的子表达式也没有定义. 也就是说运算符的定义保证了其操作数按某一特定的顺序求值,否则具体实现可以自 ...
- 重读The C programming Lanuage 笔记一:类型转换
首先说自动类型转换: 当一个运算符的几个操作数类型不同时,就需要吧他们转换位某种共同的类型.一般来说,自动转换把“较低”的类型转换为”较高“的类型.运算结果为较高的类型 以下是不严格的规则: 首先,如 ...
- Linux System Programming 学习笔记(四) 高级I/O
1. Scatter/Gather I/O a single system call to read or write data between single data stream and mu ...
- Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
- C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...
- 《MFC游戏开发》笔记四 键盘响应和鼠标响应:让人物动起来
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9327377 作者:七十一雾央 新浪微博:http:// ...
- IOS学习笔记(四)之UITextField和UITextView控件学习
IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...
- java之jvm学习笔记四(安全管理器)
java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...
随机推荐
- oracle-计算工作日
数据库模拟表如下 operate_id operate_type operate_date process_sn 1 GD 2013-09-15 17:18:37 10001 2 JD 2013-09 ...
- 安装javaUbuntu下安装JDK1.6,并将之设为默认的JDK
本篇文章个人在广东逛街的时候突然想到的...最近就有想写几篇关于安装java的博客,所以回家到之后就奋笔疾书的写出来发表了 1.在Windows系统下下载Liunx 版本JDK,我下的是jdk-6u4 ...
- Mysql监控及优化
一.Mysql连接数 1.配置Mysql连接数: vim /etc/my.cnf [mysqld]下面修改 max_connections=1000 不写默认为100. wait_timeout=60 ...
- C#中的集合类——ArrayList
1. ArrayList与数组 数组的长度不可变,元素的类型单一: ArrayList 实际上相当于一个可变长度的动态数组,由于集合中的元素都是object类型,元素的类型可以有多种了:与数组一样, ...
- jquery表格提交验证
在表格中的验证 1.body中的内容 <form action="http://www.qq.com" id="form1"><table w ...
- 用C++实现的八皇后问题
我是一个C++初学者,控制台实现了一个八皇后问题. 代码如下: //"八皇后问题"V1.0 //李国良于2017年1月11日编写完成 #include <iostream&g ...
- 使用spring-data-redis操作redis
redis.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...
- action = "#" 是什么意思 在HTML语言中
action = "#" 是form标签的属性,代表提交数据到本页,如:// 提交数据到a.aspx页面<form action="a.aspx"> ...
- vue-router之router-link
<router-link> 组件支持用户在具有路由功能的应用中(点击)导航. 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生 ...
- java 包 修饰符 权限详解
作用域 当前类 同package 子孙类 其他package public √ √ √ √ protected √ √ √ × friendly(default) √ √ × ...