文档章节

预编译指令详解

pp__qq
 pp__qq
发布于 2013/12/26 00:46
字数 1550
阅读 180
收藏 12

三字符序列

主要是因为以前键盘上没有某些字符,所以为了使用这些字符,必须使用多个可以打印字符来标识这个不能打印的字符,三字符的替换发生在所有操作之前;

??=
#
??(
[
??<
{
??/
\
??)
]
??>
}
??'
^
??!
|
??-
~

不过 现在编译器默认是关掉替换的,替换会造成很多有趣的事情,如:

int ci=1;
//接下来的??/
++ci;
printf("%d\n",ci);
/* ??/会被替换为'\';而'\'作为一个行连接符会让"++ci"被注释掉
 * 不过现在编译器默认是关掉替换的 */


宏展开的流程

  1. 首先根据','将实际参数分割成一个个记号,此时不会被实际参数进行扩展

#define cat(x,y)	x;y;
cat(int a;,int b;);/* 宏展开: int a;;int b;; */
/* 根据','分割记号 */
  1. 用实参产生的记号替换宏中未用引号引起来的相应形式参数的标识符

    • 如果标识符前面没有'#',或者前者或后面都没有'##'符号;则检查实际参数产生的记号,并在必要的时刻进行宏扩展

  2. 根据','将实际参数分成记号,并匹配宏定义中的形参,此时位于引号或者嵌套括号的逗号不会用于分割,如

/**
 *  根据 func 的返回值测试 func 是否成功执行,若出错,则抛出 std::runtime_error() 类型的异常,异常字符串为:文件名;行号;func;format..
 *  @param func 若 func 返回 int 类型,则当返回值不为0时,认为 func 出错;若 func 返回指针类型,则当返回值为 0 时,认为出错.
 */
#define TF(func,format,...)  testSystemCall(func,__FILE__,LINE_TO_STR(__LINE__),#func,format,##__VA_ARGS__)

TF(socket(AF_INET,SOCK_STREAM,0),"HELLOWORLD");
// 此时根据','进行分割,匹配情况为:
// func = socket(AF_INET,SOCK_STREAM,0)
// format = "HELLOWORLD"
// 所以此时替换后内容为:testSystemCall(socket(2,1,0),"main.cc","13","socket(AF_INET,SOCK_STREAM,0)","HELLOWORLD");
// 即 socket() 内的","不会用于分割.

#

如果在上述替换时发现形式参数标识符之前存在'#'符号,则

  • 首先在实际参数产生的记号两端添加"符号,使得记号变成一个字符串字面值

  • 然后再用该记号替换 '#'与其后的标志符

  • 实际参数中的字符串字面值,字符常量两边或内部的每个双引号( " )或反斜杠( \ )前面都会由预编译器自动插入一个反斜杠( \ )。


#define mStr(x)	 #x
printf("%s\n",mStr("3\3"));
/* 此时宏展开 "\"3\\3\"" */


##

在形式参数都被替换后都要把##及其前后的空白符都删除掉,以便将相邻记号连接起来形成一个新记号。

#define cat(x,y) x ## y
printf("%d\n",cat(1,2));


多次扫描

重复扫描替换记号序列以查找更多的己定义标识符。但是, 当某个标识符在某个扩展中被替换后,再次扫描并再次遇到此标识符时不再对其执行替换,而是保持不变 。

printf("%d\n",mtest(m,test)(3,3));
/* 第一次扫描:mtest(3,3);此时因为mtest()
 * 在上一层扫描中已经被展开了,所以此时不会再展开 */
printf("%d\n",mtest(m,test1)(3,3));
/* 第一次扫描:mtest1(3,3);然后
 * 下一次扫描再展开mtest1 */


include

#include 把该行(直接在当前行)替换为文件名指定的文件的内容 ,三种形式:

  • #include <文件>

  • #include "文件"

  • #include 宏调用

#define incFile(file) #file
#include incFile(___Dlove_HTTPSend.h);
/* 就是直接 #inlcude "___Dlove_HTTPSend.h" */

if elif else endif

#if 整数表达式
   文本 
#else
   文本
#endif
  • "文本" 是指任何不属于条件编译指令结构的程序代码,它可以包含预处理指令,也可以为空

  • 因为预编译器不进行计算,所以#if后只能用整数表达式(必须是整型,并且其中不包含sizeof,强制类型转换运算符或枚举常量);

defined ifdef 

defined 标志符 或者 defined(标志符);
    如果该标识符在预处理器中已经定义,则用 1 替换它,否则,用 0 替换。预处理器进行宏扩展之后仍然存在的任何标识符都将用0来替换
        加粗字体。。我不甚了解,大概就是因为在宏的多次扫描中已经扩展过的标志符不会在被扩展,差不多这个意思;

error

#error 描述
    将终止编译并打印指定的诊断信息。

可变参数宏

语法

通过在宏定义中使用"...",在宏拓展中使用"__VA_ARGS__"来实现具有可变参数的宏,如

#define errRep(func,...)   \
   fprintf(stderr,func,__VA_ARGS__);

当只需要一个参数的时候

#define err(format,...) fprintf(stderr,format,__VA_ARGS__)

int main(int argc,char *argv[]){
    err("HelloWord");
    err("Hello;%d;%f",33,3.3);
    return 0;
}
$ g++ -E main.cc
# 1 "main.cc"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "main.cc"
int main(int argc,char *argv[]){
    fprintf(stderr,"HelloWord",); # 当没有参数时,__VA_ARGS__ 相当于空,所以此时会多了一个逗号.
    fprintf(stderr,"Hello;%d",360);
    return 0;
}
  • 使用 ##__VA_ARGS__ 来消除,

#define err(format,...) fprintf(stderr,format,##__VA_ARGS__)
$ g++ -E main.cc 
# 1 "main.cc"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "main.cc"
int main(int argc,char *argv[]){
    fprintf(stderr,"HelloWord"); # 多余的逗号不见了
    fprintf(stderr,"Hello;%d",360);
    return 0;
}

标准宏

__LINE__ 当前行号
__FILE__ 当前源文件名
__DATE__ 当前编译日期
__TIME__ 当前编译时间
__STDC__ 当程序严格遵循STDC时为1
__cplusplus 当程序使用C++语言编写为1
#include<iostream>
using namespace std;

int main(){
	cout<<"源文件为: "<<__FILE__<<endl<<"编译于:"__DATE__<<" "<<__TIME__<<endl;
	cout<<"使用";
#ifdef  __cplusplus
	cout<<"C++";
#else
	cout<<"C"
#endif
	cout<<"编写 "<<endl;
#ifndef __STDC__
	cout<<"不";
#endif
	cout<<"遵循ANSC标准"<<endl;
	return 0;
}

do{}while(0)

1.一般用在宏定义中,如:

/*
 * 功能:往stderr流中写入出错信息,以退出程序
 * 参数:与 fprintf() 参数一致
 */
#define mErrRep(errfuc,...)        \
    do{    \
        fprintf(stderr,errfuc,##__VA_ARGS__);    \
        exit(1);        \
    }while(0);

此时 mErrRep() 宏有两条语句,并且经常出现在 if() 后面;此时使用 do{}while(0) 可以很安全的这样:

if((dirs=opendir("/root")) == NULL)
   mErrRep("main:opendir: %s\n",strerror(errno));

/* 会被替换为: */ 
 if(dirs=opendir("/root") ==NULL )
     do{
          fprintf(stderr,"main:opendir: %s\n",strerror(errno));
          exit(1);
      }while(0)

如果不使用 do{}while(0) 的话在每一个if()后面都应该加上一个{}以括起宏定义中可能含有的多条语句

© 著作权归作者所有

共有 人打赏支持
pp__qq
粉丝 17
博文 66
码字总数 97223
作品 0
合肥
程序员
C语言的编译链接过程详解

学过C语言的人都应该知道,我们所编辑的C语言程序是不能直接放到机器上运行的,它只不过是一个带".c"后缀的文件(也称为源代码)而已,需要经过一定的处理才能转换成机器上可运行的可执行文件...

LIU-X1001
2016/06/30
45
0
深入理解C语言的预编译指令之 include

写过C语言的朋友都熟悉#include,在打印“hello world”这样一条语句也用上这条指令。但是,说熟悉它,只是表面熟悉,更多感觉是既熟悉又抽象陌生,结果也就只是不知道为什么的背诵了。抽象、...

ningcaichen66
2017/09/22
0
0
fatal error C1010: unexpected end of file while...

在编译VC++6.0是,出现fatal error C1010: unexpected end of file while looking for precompiled header directive 的错误. 解决方法: 1、如果发生错误的文件是由其他的C代码文件添加进入当...

Mr&Cheng
2013/01/20
0
0
C++中头文件、源文件之间的区别与联系

.h头文件和.cpp文件的区别 疑惑1:.h文件能够编写main函数吗? 实验: 编写test.h文件,里面包含main函数 若直接编译g++ test.h -o test,通过file命令 file test,得到如下结果test: GCC p...

风筝Fergus
2013/04/17
0
2
iOS代码运行的磨刀石-预编译指令

iOS中代码运行的磨刀石--预编译指令 所谓预编译,就是程序代码在编译之前,开发工具为我们预先做的一些工作。不要小瞧这些指令,没有它们,我们的代码可能寸步难行。 一、文件包含相关预处理...

珲少
2015/04/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

大数据框架对比:Hadoop、Storm、Samza、Spark和Flink

简介 大数据是收集、整理、处理大容量数据集,并从中获得见解所需的非传统战略和技术的总称。虽然处理数据所需的计算能力或存储容量早已超过一台计算机的上限,但这种计算类型的普遍性、规模...

hblt-j
14分钟前
0
0
正则介绍及grep/egrep用法

10月16日任务 9.1 正则介绍_grep上 9.2 grep中 9.3 grep下 扩展 把一个目录下,过滤所有*.php文档中含有eval的行 grep -r --include="*.php" 'eval' /data 9.1 正则介绍_grep上 什么是正则 ...

zgxlinux
29分钟前
1
0
想用Unity3D引擎软件赚点钱的看过来

前言: 你可以不拥有很多钱 但你一定要有赚钱的能力 目前手上有项目, 需要熟练Unity3D引擎软件的伙伴 有意向的给我发私信

猿神出窍
31分钟前
0
0
Spring Boot全局异常处理

Spring Boot默认的异常处理机制 默认情况下,Spring Boot为两种情况提供了不同的响应方式。 一种是浏览器客户端请求一个不存在的页面或服务端处理发生异常时,一般情况下浏览器默认发送的请求...

狼王黄师傅
58分钟前
8
0
Thinkphp5 优雅配置两个数据库

工作需要需要配置两个数据库,框架5.0的,步骤如下: 1、在database.php同级创建一个database2.php文件 在里面配置第二个数据库信息, 2、在config中配置这个数据库信息: 3、创建第二个表的...

wqzbxh
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部