你必须知道的C语言预处理的问题详解

  C语言预处理器执行宏替换、条件编译和文件包含。通常采用以“#”为行首的提示。下面是C语言预处理的应用场合:

  1.三字母词(Trigraph Sequences)

  C源程序的字符集被包含在7位的ASCII字符集中,但是它是ISO 646-1983 Invariant Code Set的超集。为了让程序可以在缩减集(reduced set)中呈现出来,下面的三字母词会被替换成相应的单字符.
































三字母词 单字符
??= #
??/ \
??' ^
??( [
??) ]
??! |
??< {
??> }
??- ~

  替换发生在任何其他处理之前。

  例如:如果你尝试打印字符串"what??!"  


代码如下:

printf("what??!\n");

  会得到字符串"what|"。

  如果你这样注释代码,结果会让你意外:  


代码如下:

// Will the next line be executed?????????????/
a++;

  a++并不会执行。前提是你知道\的作用。

  注意:由于编译器对ANSI C的支持不一样,有些编译器会把三字母词当普通字符处理,你需要给编译选项加上“-trigraphs”

  2.行拼接

  以反斜杠"\"结尾的行会把该行和下一行拼接成一行(预处理器做的工作就是把该反斜杠'"\"和接着的换行字符'\n'删除)。['\'称为续行符]

  例如你可以这样写


代码如下:

/\
* is a legal comment. *\
/

  3.宏定义和展开

  a)简单宏替换

  简单宏替换使程序中能用一个标识符来表示一个单词串,指令形式为:


代码如下:

#define 标识符 单词串

  标识符(称为宏名)被定义为后面的单词串;单词串(简称串)是任意以换行结束的用于替换程序中该标识符的正文。如果串太长需要写成多行,则除了最后一行外每一行末尾都要有一个续行符(即添加一个“\”后回车)。

  注意:字符串常数中出现的与宏名相同的字符串不在替换之列。例如:


代码如下:

#define YES 1
printf("YES");            // 输出 YES,而不是1

  b)带参数的宏替换

  预处理指令的形式为:


代码如下:

#define    标识符(标识符,标识符,...,标识符)    单词串

  “标识符(标识符,标识符,...,标识符)”是被定义的宏,()外面的标识符称为宏名,()中的标识符是宏的形式参数;宏名与其后的()之间不能有空白符。

  例如:  


代码如下:

#define max(a,b) ((a)>(b)? (a): (b))

  ♦操作符#和##

    操作符#把其后的串变成双引号包围的串;

    操作符##把两个标志符拼在一起,形成一个新的标识符


代码如下:

#define str(expr)    #expr
#define cat(x,y)      x ## y

int ab=12;
printf(str(hello world!));      // 会被替换成 printf("hello world!");
printf("ab=%d\n", cat(a,b));        // 会被替换成 printf("ab=%d\n", ab);  输出 ab=12

  ♦宏替换时的顺序  


代码如下:

#include <stdio.h>
 #define f(a,b)  a##b
 #define g(a)   #a
 #define h(a)   g(a)

int main()
 {
         printf("%s\n", h(f(1,2)));
         printf("%s\n", g(f(1,2)));
         return 0;
 }

  输出结果是12和f(1,2)。为什么会这样呢,宏的解开不像函数,由里到外。

  (1)在""内的宏名或宏参数名不被替换

  (2)宏替换顺序:一个带参数的宏内部调用另一个宏,参数也是一个宏,则先替换外层的宏,再替换外层宏的参数,最后替换内层宏。

  知道这些规则对于出现上面的结果就不难理解了。

  温馨提示:在写带参数的宏替换指令时,推荐的做法时将单词串中的每一个参数都用()括起来,整个表达式也要用()括起来;否则,替换结果可能不是你想要的,例如:


代码如下:

#define sqr(x)    x * x
// 如果程序中的宏的引用形式为
sqr(3.0+1.0);                // 经预处理后会被替换为 3.0 + 1.0 * 3.0 + 1.0

  结果与你的原意(3.0+1.0)*(3.0+1.0)不等价

  c)取消宏定义


代码如下:

#undef 标识符

  会使宏名标识符失去定义。如果#undef 一个没有定义过的标识符  也不会引发错误。

  4.文件包含  


代码如下:

#include <filename>     // 引用标准库的头文件(编译器将从标准库目录开始搜索)
#include "filename"       // 引用非标准库的头文件(编译器将从用户的工作目录开始搜索)
#include 标识符            // 标识符是由#define 定义的<filename>或"filename"的宏名

  5.条件编译

  条件编译指令格式如下:


代码如下:

if-line 正文
[#elif 常量表达式 正文]
...
[#else 正文]
#endif

  if-line为下面中的任意一种形式:

  (1)#if 常量表达式

  (2)#ifdef 标识符

  (3)#ifndef 标识符

  ♦defined操作符用来判断标识符是否定义过。形式如下:

  defined identifier

  defined (identifier)

  下面的

  #ifdef identifier

  #ifndef identifier

  等价于

  #if defined identifier

  #if ! defined identifier

  6.行控制

  行控制指令有下列两种形式

  (1)#line n "filename"

  (2)#line n

  行控制预处理功能为其他产生C源程序的预处理程序(例如数据库系统中的宿主C预编译程序)在跟踪被处理程序(例如被宿主C预编译程序处理的扩展名为.pc的预编译源程序)的行号时提供方便,便于最终用户的源程序查错和该错。它会使编译器相信n(十进制正整数)为下一个源程序行的行号,“filename”会被当作当前文件名。

  7.生成错误

  #error error_messageopt

  让编译器输出错误信息error_message

  8.Pragmas

  #pragma token-sequenceopt

  #pragma是编译程序实现时定义的指令,它允许由此向编译程序传入各种指令。例如,一个编译程序可能具有支持跟踪程序执行的选项,此时可以用#pragma语句选择该功能。编译程序忽略其不支持的#pragma选项。#pragma提高C源程序对编译程序的可移植性。

  9.空指令

  形如

  #

  没有任何作用

  10.预定义宏

  C语言规范了5个固有的预定义宏,他们分别是

  __LINE__  当前源程序的行号

  __FILE__  正在编译的程序的文件名

  __DATE__  编译的日期字符串,形如"Mmm dd yyyy"

  __TIME__  编译的时间字符串,形如"hh:mm:ss"

  __STDC__  如果__STDC__的内容是十进制常数1,则表示编译程序的实现符合标准C

(0)

相关推荐

  • 详解C语言编程中预处理器的用法

    预处理最大的标志便是大写,虽然这不是标准,但请你在使用的时候大写,为了自己,也为了后人. 预处理器在一般看来,用得最多的还是宏,这里总结一下预处理器的用法. #include <stdio.h> #define MACRO_OF_MINE #ifdef MACRO_OF_MINE #else #endif 上述五个预处理是最常看见的,第一个代表着包含一个头文件,可以理解为没有它很多功能都无法使用,例如C语言并没有把输入输入纳入标准当中,而是使用库函数来提供,所以只有包含了stdio.h这个头文

  • C#预处理器指令的用法实例分析

    本文实例讲述了C#预处理器指令的用法.分享给大家供大家参考.具体用法分析如下: C#预处理器指令是在编译时调用的.预处理器指令(preprocessor directive)告诉C#编译器要编译哪些代码,并指出如何处理特定的错误和警告.C#预处理器指令还可以告诉C#编辑器有关代码组织的信息. 1. 定义符号和取消符号定义的预处理指令#define 和 #undef 预处理指令都以#号开头并位于行首前面可以出现空格符. 复制代码 代码如下: #define DEBUG #define ISSAY

  • 简介C/C++预处理器的一些工作

    多么令人愉快的一个问题啊 就在被带到编译器那里之前,预处理器都会对你的源代码瞧上一瞧, 做一些格式化的工作,并执行任何你在源代码里面留给它来执行的指令. 像什么? 好吧,预处理器的指令就被叫做预处理器指令,而他们都以一个#开头. 像 #include 这样? 正确. 每一个被预处理器遇到的 # 命令都会导致在某种方式上对源代码的修改. 让我们来简单的研究研究它们,然后我们就会之后这背后都是怎么运转的了. #include 包含其他库.类.接口等的头文件.预处理器实际上就只是把整个头文件复制到你的

  • 常用C/C++预处理指令详解

    预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查.预处理命令以符号"#"开头. 常用的预处理指令包括: 宏定义:#define 文件包含:#include 条件编译:#if.#elif.#ifndef.#ifdef.#endif.#undef 错误信息指令:#error #line指令 布局控制:#pragma 宏定义 宏定义又称为宏代换.宏替换,简称"宏".宏替换只作替换,不做计算,不做表达式求解.宏定义分带参数的宏定义和不带参数的宏

  • C#中的预处理器指令详解

    目录 1. #define 和 #undef 2. #if.#elif.#else 和#endif 3. #warning 和 #error 4. #region 和#endregion 5. #line 6. #pragma C#中有许多名为"预处理器指令"的命令.这些命令从来不会转化为可执行代码中的命令,但会影响编译过程的各个方面. 例如,使用预处理器指令可以禁止编译器编译代码的某一部分.如果计划发布两个版本的代码,即基本版本和拥有更多功能的企业版本,就可以使用这些预处理器指令.在

  • 深入理解C预处理器

    C 预处理器不是编译器的组成部分,是编译过程中一个单独的步骤.C预处理器只是一个文本替换工具,它会指示编译器在实际编译之前完成所需的预处理. 所有的预处理器命令都是以井号(#)开头.它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始. 下表包含所有重要的预处理器指令: 指令 描述 #define 定义宏 #include 包含一个源代码文件 #undef 取消已定义的宏 #ifdef 如果宏已经定义,则返回真 #ifndef 如果宏没有定义,则返回真 #if 如果给定条件为真,则

  • 你必须知道的C语言预处理的问题详解

    C语言预处理器执行宏替换.条件编译和文件包含.通常采用以"#"为行首的提示.下面是C语言预处理的应用场合: 1.三字母词(Trigraph Sequences) C源程序的字符集被包含在7位的ASCII字符集中,但是它是ISO 646-1983 Invariant Code Set的超集.为了让程序可以在缩减集(reduced set)中呈现出来,下面的三字母词会被替换成相应的单字符. 三字母词 单字符 ??= # ??/ \ ??' ^ ??( [ ??) ] ??! | ??<

  • 必须知道的C语言八大排序算法(收藏)

    概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序-直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

  • Android开发新手必须知道的10大严重错误

    本文总结了Android开发新手必须知道的10大严重错误.分享给大家供大家参考,具体如下: 作为过去10年广泛涉猎各热门平台的经验丰富手机应用开发商,我们觉得Android平台对新手开发者来说是最通俗易懂的平台.平价的工具.友好的开发社区.及众所周知的编程语言(Java),可以说开发Android应用不是难事.但尽管如此,我们依然发现有些错误反复出现在刚涉足Android平台的新手开发者身上,下面是他们常犯的10大严重错误.希望新手朋友们能够借鉴,避免误区. 1. 未阅读Android文件 An

  • 前端开发必须知道的JS之闭包及应用

    在前端开发必须知道的JS之原型和继承一文中说过下面写篇闭包,加之最近越来越发现需要加强我的闭包应用能力,所以此文不能再拖了.本文讲的是函数闭包,不涉及对象闭包(如用with实现).如果你觉得我说的有偏差,欢迎拍砖,欢迎指教.一. 闭包的理论 首先必须了解以下几个概念: 执行环境 每调用一个函数时(执行函数时),系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境.函数总是在自己的执行环境中执行,如读写局部变量.函数参数.运行内部逻辑.创建执行环境的过程包含了创建函数的作用域,函数也是在

  • 3分钟看懂Python后端必须知道的Django的信号机制

    概念 django自带一套信号机制来帮助我们在框架的不同位置之间传递信息.也就是说,当某一事件发生时,信号系统可以允许一个或多个发送者(senders)将通知或信号(signals)发送给一组接受者(receivers). (感觉就很像Qt的信号与槽机制) 信号系统包含以下三要素: 发送者-信号的发出方 信号-信号本身 接收者-信号的接受者 Django内置了一整套信号,下面是一些比较常用的: Django内置信号 Model signals pre_init # django的modal执行其

  • C语言冷知识之预处理字符串操作符详解

    目录 在C语言中什么是标记 预处理字符串操作符 #字符串化操作符 ##标记(Token)连接操作符 当年学习C语言的第一门课就提到过标记(Token)的概念,不过,相信在多年之后你再次听到这个术语时会一脸懵逼,比如我. 因此特地翻了翻资料,整理下来这些笔记. 在C语言中什么是标记 标记是编程语言处理的基本单元,也叫最小划分元素,比如关键字.操作符.变量名.函数名.字符串.数值等等. 下面举例说明一下: printf("hello world!"); 对上面的语句进行标记划分,可分为5个

  • java语言注解基础概念详解

    1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃: 2.RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期: 3.RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在: 这3个生命周期分别对应于:Java源文件(.java文件)--->.class文件--->内存中的字节码.

  • C语言指针教程示例详解

    目录 指针 内存 指针类型 指针运算 二级指针 指针数组 指针 指针提供了对地址操作的一种方法,因此,使用指针可使得 C 语言能够更高效地实现对计算机底层硬件的操作.另外,通过指针可以更便捷地操作数组.在一定意义上可以说,指针是 C 语言的精髓. 概念解释就不去搬原定义了,又臭又长不好理解,精炼两点就是: 1.指针是内存中的一个最小单元的编号,也就是地址: 2.平时我们说的指针,通常是指指针变量,用来存储内存地址的变量 也就是说:指针就是地址,口语中指针通常是指针变量 内存 要搞明白指针首先要搞

  • C语言数据结构之二叉树详解

    目录 1. 树概念及结构 1.1树概念 1.2树的表示 2. 二叉树概念及结构 2.1概念 2.2数据结构中的二叉树 2.3特殊的二叉树 2.4二叉树的存储结构 2.5二叉树的性质 3. 二叉树顺序结构及概念 3.1二叉树的顺序结构 3.2堆的概念及结构 3.3堆的实现 4. 二叉树链式结构及实现 4.1二叉树链式结构的遍历 4.2二叉树的链式实现 1. 树概念及结构 1.1树概念 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合.把它叫做树是因为它看起来像一棵

  • 基于JS脚本语言的基础语法详解

    JS脚本语言的基础语法:输出语法  alert("警告!");  confirm("确定吗?");   prompt("请输入密码");为弱类型语言: 开始时要嵌入JS代码:<script type="text/javascript"></script>: 关于写程序是需注意的基本语法: 1.所有的字符全都是英文半角的: 2.大部分情况下每条语句结束后要加分号: 3.每一块代码结束后加换行:4.程序前呼

随机推荐