【C语言入门教程】2.8 C 语言的预处理命令
预处理命令是在程序编译阶段进行执行的命令,用于编译与特定环境相关的可执行文件。预处理命令扩展了 C 语言,本节将选择其中一些常用的预处理命令进行讲解。
2.8.1 宏替换命令
宏替换命令的作用类似于对源代码文件进行文本替换操作,但是其形式更为灵活丰富。编译器每次遇到宏替换命令所定义的标识符时,都会用其后的字符串替换该标识符。该命令的一般形式为:
#define 标识符 字符串
该语句结束时没有分号,所有预处理程序亦如此。在标识符和字符串之间可以有任意个空格,字符串一旦开始,仅由一新行结束。例如,如希望 TRUE 取值 1,FALSE 取值 0,可说明两个宏:
#define TRUE 1 // 使用 TRUE 表示 1
#define FALSE 0 // 使用 FALSE 表示 0
这样再程序中就能直接使用标识符来代表被宏替换的串,C 语言通过这种方法定义符号常量。另一个用法是作为宏代换。宏名可以取参量数,每次遇到宏名时,与之相连的形式参数均由程序中的实际参数代替。例如:
#define MAX(a, b) (a >= b) ? a : b // 定义宏代换 MAX(),当 a 大于或等于 b 时,返回 a 值,否则返回 b main() // 主函数
{
int x = 190, y = 106; // 定义整型变量 x、y 并赋值
printf("MAX: %d", MAX(x, y)); // 输出 x 和 y 中较大的一个,使用宏代换
}
当编译程序时,MAX()定义的表达式被替换。程序第 5 行被转换为如下形式。
printf("MAX: %d", (x >=y) ? x : y);
用宏代换代替简单的函数能加快程序执行速度,因为不存在函数调用的开销,同时也提高了代码的可读性。
命令 #undef 用作取消已定义的宏名替换。一般形式为:
#undef 标识符
其主要目的是将宏替换限定在一个代码块内。如下例代码所示:
#define SUCCESS 1 // 定义宏 SUCCESS 表示 1
printf("%d", SUCCESS); // 输出宏 SUCCESS 所代表的数值
#undef SUCCESS // 取消宏 SUCCESS 的定义
在使用 #undef 命令取消宏 SUCCESS 之前,宏 SUCCESS 是有效的,所以代码第 2 行能正确地输出宏 SUCCESS 所代表的数值。
2.8.2 终止编译命令
在调试程序时,为了提高调试速度,通常在源代码的适当位置加入终止编译命令 #error。它的一般形式是:
#error 错误信息字符串
错误信息字符串不用双引号包围,当程序编译到 #error 指令时,错误信息被显示。如下例所示:
#eror MANUAL_STOP // 停止编译,并使编译器提示编译错误信息 MANUAL_STOP
当编译器编译到这条代码时,就停止工作,并将字符串 MANUAL_STOP 作为错误提示。次命令与条件编译命令配合使用,使之在特殊的条件下生效。
2.8.3 文件包含命令
文件包含命令常用于在编译时插入另一个源程序的内容。被包含文件的名字必须用一组双引号("")或一对尖括号(<>)包围,例如:
#include "filename.h"
#include <stdio.h>
这两行代码都使编译器读入并编译 头文件 或 源代码 文件。双引号用于包含指定相对路径的文件,若未指明相对路径,则会在当前源文件所在的目录检索。如果文件没找到,则检索标准目录,不检索当前工作目录。尖括号用于包含标准函数库文件和用户在编译指令里所指明的函数库文件,系统会在这些函数库中搜索指定文件。
被包含的文件中也有 #include 命令是允许的,这种方式被称为嵌套的嵌入文件,嵌套层次依赖于具体实现。
2.8.4 条件编译命令
条件编译命令是编译阶段的逻辑控制结构,通常利用条件编译命令将同一源代码编译为多个不同需求的程序版本。
1. #if、#else、#elif 及 #endif
#if 命令的意义为,如果 #if 后面的常量表达式为真,则编译它 与 #endif 之间的代码,否则跳过这些代码。如下例所示:
#define X 190 // 定义整型常量 X 并赋值
#define Y 106 // 定义整型常量 Y 并赋值
#if X > Y // 条件编译命令,如果条件成立,则编译后面的语句
printf ("MAX: %d", X); // 输出提示符和X的值
#endif
上例中,因为 X 的值大于 Y,所以条件编译块中的语句被编译。如果将 #else 加入到
#if 块中,那么当 #if 后面的常量表达式为真时,则执行 #if 到 #else 间的语句,否则执行 #else
到 #endif间的语句。如下例所示:
#define X 190 // 定义整型常量 X 并赋值
#define Y 106 // 定义整型常量 Y 并赋值
#if X <= Y // 条件编译命令,如果条件成立,则编译后面的语句
printf("MIN: %d", X); // 输出提示符 和 X 的值
#else // 当前面的 #if 条件判读为假时,从这里开始编译
prinft("MIN: %d", Y); // 输出提示符和 Y 的值,此语句被编译
#endif
此代码中,因为 #if 后的表达式 “X <= Y”为结果假,所以编译器不会编译输出 X 的值的语句,而编译输出 Y 值的语句。#elif 可实现分支条件,在条件编译语句中加入 #elif 和条件表达式后,当 #if 条件为真时,编译 #if 到 #elif 间的语句。当 #if 条件为假时,判读 #elif 的条件,如果 #elif 条件为真,执行其后的代码。条件编译语句中可以有多个 #elif 语句,它们可以依次判读,但只要有一个为真,则编译完相关代码后会跳出条件编译语句。但如果 #if 和 #elif 的条件都为假,那么看代码中是否有 #else 相关的语句编译,否则不编译任何语句。如下例所示:
#define X 106
#define Y 106
#if X < Y
printf("MIN: %d", X);
#elif X == Y
printf("X equal Y");
#else
printf("MIN: %d", Y);
#endif
上例中,因为 X 和 Y 的值相等,所以 #elif 分支内的代码被编译。另外条件编译命令可以在其条件编译块中嵌套另一组条件编译命令,能够嵌套多三层并没有限制。
2. #ifdef 与 #ifndef
#ifdef 命令用于判读某个宏名是否已定义,如果已定义则执行 #ifdef 与 #endif 间的代码块。#ifndef 命令用于判读某个宏名是否未定义,与前者相反。在头文件中可大量见到这组命令,它们解决了头文件循环
嵌套时反复加载同一段定义的风险。#ifdef 和 #ifndef 的命令格式为:
#ifdef 标识符
代码块
#endif
#ifndef 标识符
代码看
#endif
其中,标识符是使用 #define 所定义的宏名。#ifdef 的作用是,当其后的标识符不存在时,执行相关代码。而 #ifndef 的作用相反,当其后标识符不存在时,则执行相关代码。如下例所示:
#ifndef BASIC_ELEMENT // 判读标识符 BASIC_ELEMENT 是否被定义,未定义则编译下例语句
#define BASIC_ELEMENT // 定义标识符 BASIC_ELEMENT,该标识符没有值
#define H 1 // 条件编译块内代码
#define C 12
#define O 16
#endif // 条件编译块结束 #ifdef BASIC_ELEMENT // 如果标识符 BASIC_ELEMENT 被定义,则编译下例语句
printf("基本化学元素已定义"); // 条件编译块内代码
#endif // 条件编译块结束
这段代码首先判读是否存在 BASIC_ELEMENT 标识符。如果不存在,则将定义该标识符的语句和其他需要编译的语句一并编译。该标识符并没有任何值,它的作用纯粹是用来指示条件编译的动作,这种方式在定义头文件时很常见。代码第 7 行 使用 #ifdef 判读标识符是否存在,因前面已定已过,所以其后的相关代码会被编译。另外,#ifdef 与 #ifndef 命令也能嵌套使用。
2.8.5 修改行号命令
修改行号命令 #line 可修改编译器中所标识的源文件行号和文件名信息。编译器在编译时会为源代码的行数编号,以便编译时统计行数和指明警告或错误的行号,同时将源代码文件在文件系统中的文件名作为被编译文件的文件名信息。每行的行号由编译器预定义的宏 __LINE__ 表示,文件名信息由预定义 __FILE__ 表示,使用 #line 命令可修改这些信息。#line 命令的格式为:
#line 行号["文件名字符串"]
其中行号可以是整型常量,文件名为任意有效文件标识符。如下例所示:
#line 200[COUNT] // 将文件起始行号改为 200,文件名改为 COUNT
main() // 此行成为 201 行
{ // 此行成为 202 行
printf("%s : %d", __FILE__, __LINE__); // 输出文件名和行号
}
此程序输出的结果为 “COUNT:203”。使用修改行号可分割一个较大的源代码文件,或者在编译时使多个文件可以拥有连贯的信息。
2.8.6 编译指示命令
在使用 C 语言开发 Linux 程序时,编译指示命令 #pragma 非常有用。 #pragma 命令是预处理命令中最复杂的一个,其作用是设定编译器的状态,或指示编译器完成一些特定的动作。#pragma 指令对每个编译器给出了一个方法,在保持与 ANSI C 标准完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的,其格式一般为:
#pragma 参数
常见的参数如下表所示:
编译指示命令参数列表 | |
---|---|
命令 | 说明 |
message |
该参数指定编译器输出相应的信息,常用于源代码信息控制。编译信息输出窗使用方法为
当编译器遇到这条指令时,就在编译输出窗口中将消息文本打印出来 |
code_seg |
该参数可设置程序中函数代码存放的代码段,在开发驱动程序的时候常被用到。其格式为
|
once | 将此参数放在头文件的第一行可保证头文件被编译一次 |
hdrstop | 该参数表示预编译头文件到此为止,后面的头文件不进行预编译 |
resource |
将指定的文件名加入到项目中。格式为
|
warning |
该参数用于管理编译器的警告信息,如 #pragma warning( disable: 4507 )可屏蔽编号为 4507 的警告信息,
|
comment |
该指令将一个注释记录放入一个对象文件或可执行文件中,格式为
|
2.8.7 预定义的宏名
ANSI C 标准有 5 个预定义的宏名,分别是 __LINE__、__FILE__、__DATE__、__TIME__、__STDC__。
__LINE__:
代表所处的行在源代码文件中的行号;
__FILE__:
代表其所处的源代码文件的名称;
__DATE__:
代表源代码被编译成可执行文件的日期;
__TIME__:
代表源代码被编译成可执行文件的时间;
__STDC__:
用于指示编译器是否执行 ANSI C 标准,如果是,其值为 1。
2.8.8 注释
注释的作用是在源代码加便于理解其意义的信息,或者是将暂时不需要使用的代码屏蔽起来。C 语言有两种注释方法,单行注释和多行注释。单行注释用双斜杠 “//”表示注释开始,同一行中处于 “//”后的文本被当作注释。多行注释用 “/*”表示注释的开始,“*/”表示注释的结束,其间的文本被当作注释。被注释的代码会被编译器忽略,不会编译到可执行文件中。其用法如下例所示:
// 单行注释,双斜杠后的文本均被当作注释
/* 多行注释
这一行也被作为注释信息,
在本行结束注释 */
【C语言入门教程】2.8 C 语言的预处理命令的更多相关文章
- 《Ruby语言入门教程v1.0》学习笔记-01
<Ruby语言入门教程v1.0> 编著:张开川 邮箱:kaichuan_zhang@126.com 想要学习ruby是因为公司的自动化测试使用到了ruby语言,但是公司关于ruby只给了一 ...
- C语言入门教程-(5)格式化输入输出
1.输入和输出 在程序的使用中,我们经常可以看的这么一个场景:用户需要输入数据,经过程序运算,得到结果后输出.在C语言中,输入数据和输出数据都是由库函数完成的,通过语句来输入/输出. 2.格式化输出— ...
- Go语言入门教程(十)之函数
Hello 各位小伙伴大家好,我是小栈君,假期一眨眼就过去了.不知道大家玩的是否开心呢? 上次我们讲到了关于Go语言的流程控制,小栈君也希望小伙伴跟着小栈君一步一个脚印的敲一下代码,相互进步.本期我们 ...
- c语言该怎么入门?C语言入门教程(非常详细)
C语言是一门面向过程的编译型语言,它的运行速度极快,仅次于汇编语言.C语言是计算机产业的核心语言,操作系统.硬件驱动.关键组件.数据库等都离不开C语言:不学习C语言,就不能了解计算机底层. 这套「C语 ...
- 《JavaScript语言入门教程》记录整理:入门和数据类型
目录 入门篇 js介绍 历史 基本语法 数据类型 概述 null 和 undefined 数值 字符串 对象 函数 数组 本系列基于阮一峰老师的<JavaScrip语言入门教程>或< ...
- 《JavaScript语言入门教程》记录整理:运算符、语法和标准库
目录 运算符 算数运算符 比较运算符 布尔运算符 二进制位运算符 void和逗号运算符 运算顺序 语法 数据类型的转换 错误处理机制 编程风格 console对象和控制台 标准库 Object对象 属 ...
- 《JavaScript语言入门教程》记录整理:面向对象
目录 面向对象编程 实例对象与 new 命令 this关键字 对象的继承 Object对象的方法 严格模式(strict mode) 本系列基于阮一峰老师的<JavaScrip语言入门教程> ...
- Go 语言入门教程,共32讲,6小时(已完结)
Go语言入门教程,共32讲,6小时(完结) 在B站:https://www.bilibili.com/video/BV1fD4y1m7TD/
- ABP入门教程14 - 更新多语言
点这里进入ABP入门教程目录 设置语种 新增语种 数据库操作 打开多语言表AbpLanguages,添加一条记录. 程序操作 在基础设施层(即JD.CRS.EntityFrameworkCore)的\ ...
- c语言入门教程 / c语言入门经典书籍
用C语言开始编写代码初级:C语言入门必备(以下两本书任选一本即可) C语言是作为从事实际编程工作的程序员的一种工具而出现的,本阶段的学习最主要的目的就是尽快掌握如何用c语言编写程序的技能.对c语言的数 ...
随机推荐
- 对iOS中Delegate的理解
首先 协议protocol 和委托delegate 是两个完全不同的概念 放在一起说 是因为我们总是在同一个头文件里看到它们: 首先解释一下什么是委托 :举个例子 ,我工作的时候给你打电话,让你帮我 ...
- Mysql配置项的简单优化
preface 众所周知,Mysql已经成为广泛使用的开源数据库了,so 公司阿里云的数据库为Mysql,在最开始的时候是直接yum安装,配置项都是默认的,没有任何调整,这次有时间就参考各路大神的资料 ...
- 算法与设计模式系列1之Python实现常见算法
preface 常见的算法包括: 递归算法 二分法查找算法 冒泡算法 插入排序 快速排序 二叉树排序 下面就开始挨个挨个的说说原理,然后用Python去实现: 递归算法 一个函数(或者程序)直接或者间 ...
- UVA10054The Necklace (打印欧拉路)
题目链接 题意:一种由彩色珠子组成的项链.每个珠子的两半由不同的颜色组成.相邻的两个珠子在接触的地方颜色相同.现在有一些零碎的珠子,需要确定他们是否可以复原成完整的项链 分析:之前也没往欧拉路上面想, ...
- Sublime Text 3 快捷键整理
选择类Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本.Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑.Ctrl+L 选中整行,继续操作则继续选择下一行, ...
- js003-基本概念
js003-基本概念 3.1 语法 3.1.1 区分大小写 ECMAScript中的一切(变量.函数名和操作符)都是区分大小写的,并且不能用关键字作为函数名:如 typeof. 3.1.2 标识符 所 ...
- WinForm------RepositoryItemCheckEdit属性介绍
//去掉第三种状态 editcheck1.OptionView.NullStyle = UnChecked
- 20145212 《Java程序设计》第9周学习总结
20145212 <Java程序设计>第9周学习总结 教材学习内容总结 一.JDBC架构 1.数据库驱动 这里的驱动的概念和平时听到的那种驱动的概念是一样的,比如平时购买的声卡,网卡直接插 ...
- 后台程序员的HTTP缓存
1.后端程序员只需要关注请求头: if-None-Match //上一次response头中的ETag的值. 响应头: Etag //是URL的Entity Tag,用于标示URL对象是否改变,区分不 ...
- SDK
IOS: iOS Application Life Cycle 应用程序生命周期 http://www.cnblogs.com/chenyg32/p/3873301.html iOS应用程序生命周期( ...