C-09\编译预处理
一、预处理
C
语言在对源程序进行正常编译之前,会先对一些特殊的预处理命令作解释,产生一个新的源程序,该过程称为编译预处理- 为了区分预处理命令和一般的C语句,所有预处理命令行都以
"#"
开头,并且结尾不用分号 - 预处理命令可以出现在程序的任何位置
- C语言提供的预处理命令主要有三类
- 宏定义
- 文件包含
- 条件编译
二、文件包含
命令格式:
#include <文件名>
或#include "文件名"
作用:通知预处理器将指定文件的内容包含在指令出现的位置,也就是直接复制文件内容替换该指令
两者区别:
<>
:先根据每个/I
编译器选项指定的路径查找,然后再根据环境变量include
命令设置的路径去找""
:- 如果文件名给定的是全路径,则只搜索该路径
- 如果是相对路径,则先在源文件所在的目录查找,然后根据每个
/I
编译器选项指定的路径查找,最后再根据环境变量include
命令设置的路径去找
在
vs
开发环境中,将忽略环境变量,改为使用"项目属性"的”包含目录“中指定的值
三、宏
- 宏定义分为不带参数的宏定义和带参数的宏定义
- 区别于函数,其标识符一般用大写,多个单词用
_
隔开 - 程序中用双引号或单引号括起来的字符串内的字符,不会进行宏的替换操作
不带参宏
定义:
#define 标识符 语句序列或表达式
作用:在编译预处理时,将源程序中所有标识符替换成语句序列
#define PR printf // 定义后面出现的所有 PR 标识符在编译预处理时将被 printf 代替
#define N 2 // 定义后面出现的所有 N 标识符在编译预处理时将被 2 代替
注意:
编译预处理时宏名与字符串进行替换时,不作语法检查,只是简单的字符替换,只有在编译时才对已经展开的宏名的源程序进行语法检查
宏名的有效范围从欧诺个定义位置到文件结束
可以使用
#undef
提前终止宏定义的作用域#undef 标识符
带参宏
定义:
#define 标识符(参数表) 字符串
作用:在编译预处理时,将源程序中所有标识符替换成字符串,并将字符串中的参数用实际使用的参数替换
#define S(a,b,c) (a+b+c)/2 // 后面如果使用了 S(3,4,5), 在编译预处理时将替换为 (3+4+5)/2
展开顺序
首先用实参代替形参,将实参代入宏文本中
如果实参也是宏,则展开实参
最后继续处理宏替换后的宏文本,如果仍包含宏,则继续展开
注意:如果在第2步中,实参代入宏文本后,实参之前或之后遇到
#
或##
操作符,实参不再展开#define _T(x) L##x
#define __T(x) _T(x)
一般使用下面的 __T(x) 防止宏套宏 #define MYSTR "Hello world" int main()
{
printf(_T(MYSTR)); // 这样编译后就成为 LMYSTR
printf(__T(MYSTR)); // 这样编译后就正确成为 L"Hello world"
}
注意:
定义时,宏名和参数表之间不能有空格,否则空格后面的所有字符序列都作为替换的字符串
宏展开时,只做简单的字符和参数的替换,不进行任何计算操作。因此为了防止某些场合得出错误的结论,一般在定义宏时,字符串中的形式参数都会加一个小括号,且整个字符串也会加一个小括号
#define GETAREA(R) R * R * 3.14
// GETAREA(3 + 5) 处理后 3 + 5 * 3 + 5 * 3.14 可以看出处理后的结果并不是我们要的 #define GETAREA(R) (R) + (R) + 3.14
// GETAREA(3 + 5) * 6 处理后 (3 + 5) + (3 + 5) + 3.14 * 6 可以看出处理后的结果并不是我们要的
特殊符号
#
字符串化操作符作用:将宏定义中的传入参数名转换为一堆双引号括起来的参数名字符串
define DEF_STRING(s) #s // DEF_STRING(hello world) 会被转换为"hello world"字符串
注意:
忽略传入参数名前面和后面的空格
DEF_STRING( hello world ) 将被扩展为 "hello world"
当传入参数名中间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格
DEF_STRING(hello world) 将被扩展为 "hello world"
##
:符号连接操作符作用:将符号前后的两个字符串拼接起来
#define NAME_CAT(n) qls##n // ##会连接前后的 NAME_CAT(hl) 会被转换为 qlshl
注意:
##
前后的空格没有影响连接后的符号必须已经存在或是编译器已知的宏定义
#define NAME_CAT(n) qls##n int qlsHL = 1; // 如果去掉这句,会报错,找不到 qlsHL 符号 int main(int argc, char* argv[])
{
NAME_CAT(HL);
printf("Hello World!\n");
return 0;
}
#@
:字符化操作符作用:将传的单字符参数名转换成字符,以一对单引号括起来
#define makechar(x) #@x
a = makechar(b) // 展开后变成了 a = 'b'
\
:续行符作用:当定义的宏不能用一行表达完整时,可以用
\
表示下一行继续此宏的定义,例如下面的例子,方便代码阅读#define DEF_ADD(type) \
type type##Add(type x,type y) \
{ \
return x+y; \
}
功能分类
表达式宏
作用:用来描述一个表达式的一样,增加可读性
#define GETAREA(R) ((R) * (R) * 3.14) // 求圆的面积的表达式
符号名称宏
作用:用来描述一个常量值,增加可读性
#define PI 3.14
#define FILE_CLOSE 0x80000001
#define FILE_OPEN 0x80000002
语句块宏
作用:类似于函数,用来完成某项功能
#define SHOW_MSG(s) printf(s);printf("\r\n")
注意:如果在条件判断或循环语句块中使用语句块宏,带上
{}
,否则可能会得到错误的结论for(int i = 0; i < 5; i++)
SHOW_MSG("hello,world"); // 此处不用 {} 括起来,则会连续打印五次后再换行
兼容性宏
作用:让代码兼容各种平台或编译器版本
#define for if(1)for // for(int i = 0; i < 5; i++)
#define for if(0){}else for
// 因为vc++6.0中 for(int i = 0;i<5;i++)中i没不是在语句块内,而高版本修复了,所以为了兼容可以使用此宏,将for语句变为if的语句块,从而将i的作用域变为块作用域 #define INT int // 当觉得 int 不好用时,可以直接修改宏就好了
#define near // 为了兼容以前的代码,以前16位的指针分短指针(2字节)和长指针(4字节)
#define far // 为了兼容以前的代码,以前16位的指针分短指针(2字节)和长指针(4字节)
说明性宏
作用:起到说明的作用,便于阅读,对代码无影响
#define IN // 说明是一个输入参数
#define OUT // 说明是一个输出参数
#define AFXMSG // 说明是一个消息 void foo(IN int nCount, OUT int *pout)
{
int AFXMSG nMsg;
}
编译选项宏
作用:用于区别不同的编译环境,配合条件编译使用
// 在命令选项中使用下面命令,则程序起来的时候就会包含该宏名
/D"宏名" // 例如: /D"ISDEBUG"
编译器内置宏(厂商相关)
作用:编译器自己内置的一些宏,可以获取当前编译器的相关信息以及当前工程的相关信息
_MSC_VER // 编译器高版本号
__FILE__ // 当前文件的全路径
__LINE__ // 当前代码行数
条件宏
作用:用于区别不同条件下代码的编译,配合条件编译使用
#define ISDEBUG int main()
{
int nSum = 0;
for(int i = 1; i <= 100; i++)
{ #ifdef ISDEBUG
printf("%d\r\n", nSum);
#else #endif
nSum = nSum + i;
} return 0;
}
宏的花样玩法
宏实现自动识别类型
#define SET_NAME(s) #s
#define DEF_ARRAY(type,ary,nCount,size) type ary[nCount][size] = {SET_NAME(ary)}
int main()
{
DEF_ARRAY(char,ary,5,20);
return 0;
}
宏实现模板
#define DEF_ADD(type) \
type type##Add(type x,type y) \
{ \
return x+y; \
}
#define CALL_ADD(type,x,y) type##Add(x,y)
DEF_ADD(int);
DEF_ADD(float);
DEF_ADD(double);
int main()
{
int n = CALL_ADD(int,2,4);
float f = CALL_ADD(float,2.0,3.0);
double dbl = CALL_ADD(double,2.0,4.0);
return 0;
}
四、条件编译
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#if defined(宏名) 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#if !defined(宏名) 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码,其实就是else if的简写
#endif 结束一个#if……#else条件编译块
#error 停止编译并显示错误信息
五、重复包含的解决办法
// MY_LIB_H 为文件名,但是单使用文件名,可能会和别人的一样,所以在后面加上全球唯一标识保存宏名的唯一性(使用guidgen获取全球唯一标识)
#ifndef MY_LIB_H_1EF0DD15_D807_4ed9_85EE_02962DBCBAA4
#define MY_LIB_H_1EF0DD15_D807_4ed9_85EE_02962DBCBAA4
#endif // #ifndef MY_LIB_H_1EF0DD15_D807_4ed9_85EE_02962DBCBAA4
C-09\编译预处理的更多相关文章
- Verilog学习笔记基本语法篇(十二)········ 编译预处理
h Verilog HDL语言和C语言一样也提供编译预处理的功能.在Verilog中为了和一般的语句相区别,这些预处理语句以符号"`"开头,注意,这个字符位于主键盘的左上角,其对应 ...
- c语言编译预处理和条件编译执行过程的理解
在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令.预处理命令属于C语言编译器,而不是C语言的组成部分.通过预处理命令可扩展C语言程序设计的环境. 一.预处理的工作方式 1.1. ...
- 编译预处理 -- 带参数的宏定义--【sky原创】
原文:编译预处理 -- 带参数的宏定义--[sky原创] 如有转载请注明出处 编译预处理 -- 带参数的宏定义 前面为输出文件,后面为输入文件 gcc -E -o test.i test.c ...
- C++的编译预处理
C++中,在编译器对源程序进行编译之前,首先要由预处理对程序文本进行预处理.预处理器提供了一组预编译处理指令和预处理操作符.预处理指令实际上不是C++语言的一部分,它只是用来扩充C++程序设计的环境. ...
- C预编译, 预处理, C/C++头文件, 编译控制,
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的 ...
- C语言条件编译及编译预处理阶段(转)
一.C语言由源代码生成的各阶段如下: C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件 其中 编译预处理阶段,读取c源程序,对其中的 ...
- VerilogHDL编译预处理
编译预处理语句 编译预处理是VerilogHDL编译系统的一个组成部分,指编译系统会对一些特殊命令进行预处理,然后将预处理结果和源程序一起在进行通常的编译处理.以”`” (反引号)开始的某些标识符是编 ...
- 【转】C语言条件编译及编译预处理阶段
原文: http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html 1. 宏定义(宏代换,宏替换,宏: 宏定义是C语言提供的3中预处理功能 ...
- C++——多文件结构和编译预处理命令
[toc] 一.多文件结构 1.一个工程可以划分为多个源文件 类声明文件(.h文件) 类实现文件(.cpp文件) 类的使用文件(main函数所在的.cpp文件) 2.利用工程来组合各个文件 //Poi ...
- C语言进阶——编译预处理指令
编译预处理指令 • #开头的是编译预处理指令 • 它们不是C语⾔的成分,但是C语⾔程序离不开它们 • #define⽤来定义⼀个宏 #define • #define <名字> <值 ...
随机推荐
- AR空间音频能力,打造沉浸式声音体验
随着元宇宙的兴起,3D虚拟现实广泛引用,让数字化信息和现实世界融合,目前大家的目光主要聚焦于视觉交互层面,为了在虚拟环境中更好的再现真实世界的三维空间体验,引入听觉层面必不可少,空间音频孕育而生. 空 ...
- Go实现栈与队列基本操作
@ 目录 一 前言 二 实现栈与队列基本操作 2.1 栈基本操作 2.2 队列基本操作 三 用栈实现队列 3.1 理论 3.2 算法题 3.3 思路 3.4 代码部分 四 用队列实现栈 4.1 理论 ...
- 修改input标签里面的提示文字(placeholder)的样式
使用 ::-webkit-input-placeholder 伪类 input::-webkit-input-placeholder{ // 修改字体大小 font-size:12px; // 修改文 ...
- (C++) std::move std::forward及使用
概念 std::ref :针对std::thread,需要把实参显式转换为引用类型: std::move :无条件把参数转换为右值:但是右值赋值给新变量时,实际还要看是否满足右值条件,如const s ...
- 获取联通光猫PT952G的管理员密码
前言 普通用户的帐号和密码在光猫的背面 输入光猫网关即可跳转到登录界面 但是没有什么权限操作东西,所以我找到了管理员界面 输入 网关+cu.html 即可跳转到管理员界面 例如我这里是http://1 ...
- O-MVLL代码混淆方式
在介绍O-MVLL之前,首先介绍什么是代码混淆以及基于LLVM的代码混淆,O-MVLL项目正是基于此而开发来的. 有关O-MVLL的概括介绍以及安装和基本使用方式,可参见另一篇随笔 https://w ...
- VUE项目无法启动NODE版本与NODE-SASS、SASS-LOADER版本不兼容解决方案
一.错误分析 在VUE项目开发中,我们经常会遇到报错: Node Sass version 7.0.1 is incompatible with ^4.0.0. 网上解决方案也千奇百怪,最终操作下来, ...
- XCTF分站赛ACTF——Crypto
impossible RSA: 没啥好说的,跟我之前文章有道题类似,虽然如此还是花费了很长时间,原因令人落泪,把q = inverse(e,p)的数学式写成了eq mod p导致数学式推导及其困难(能 ...
- 2022年7月13日,第四组 周鹏 JAVA认识的第一天,附加一个用JS写的计算器代码
心情:╭(╯^╰)╮ ╮(╯﹏╰)╭ (╯﹏╰)b 罒ω罒 |*´Å`)ノ ( Ĭ ^ Ĭ ) (ㄒoㄒ) o(╥﹏╥)o /(ㄒoㄒ)/~~ (〒︿〒) ┭┮﹏┭┮ ε(┬┬﹏┬┬)3 ε(┬┬﹏┬ ...
- [深度学习] tf.keras入门5-模型保存和载入
目录 设置 基于checkpoints的模型保存 通过ModelCheckpoint模块来自动保存数据 手动保存权重 整个模型保存 总体代码 模型可以在训练中或者训练完成后保存.具体文档参考:http ...