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

poptosis

poptosis

2016-02-19 09:51

下面是个你必须知道的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=%dn", cat(a,b));        // 会被替换成 printf("ab=%dn", ab);  输出 ab=12

  ♦宏替换时的顺序  
代码如下:

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

 int main()
 {
         printf("%sn", h(f(1,2)));
         printf("%sn", 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 标识符

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  (3)#ifndef 标识符

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

  defined identifier

  defined (identifier)

  下面的

  #ifdef identifier

  #ifndef identifier

  等价于

  #if defined identifier

  #if ! defined identifier

(本文来源于图老师网站,更多请访问https://m.tulaoshi.com/bianchengyuyan/)

  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

展开更多 50%)
分享

猜你喜欢

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

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

IOS开发之路--C语言预处理

编程语言 网络编程
IOS开发之路--C语言预处理

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

这些你必须知道

营养价值
这些你必须知道

你必须知道的瑜伽常识

瑜伽 养生 健康
你必须知道的瑜伽常识

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

男人的秘密你必须知道

鸡汤 育儿知识 怀孕 孕妇
男人的秘密你必须知道

分娩前必须知道哪些问题

分娩
分娩前必须知道哪些问题

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

调整硬件因素 加快windows xp启动速度

调整硬件因素 加快windows xp启动速度

在Oracle中向视图中插入数据的方法

在Oracle中向视图中插入数据的方法
下拉加载更多内容 ↓