C语言的预编译,程序员必须懂的知识!【预编译指令】【预编译过程】

由“源代码”到“可执行文件”的过程包括四个步骤:预编译、编译、汇编、链接。所以,首先就应该清楚的首要问题就是:预编译只是对程序的文本起作用,换句话说就是,预编译阶段仅仅对源代码的单词进行变换,而不是对程序中的变量、函数等。

预编译指令的基本知识不作详细介绍,只稍作汇总,重点是后面的我能想到的 使用时的注意事项。

1. 基本内容

预编译指令基本分类如下

类别

指令

预定义符号__FILE__、__LINE__、__DATE__、__TIME__、__STDC__宏#define文件包含#include条件编译#if、#elif、#else、#ifdef、#ifndef、#endif

还有一些指令,名称和功能如下表:

指令功能#空指令#undef移除一个空定义#error停止编译,并生成错误信息#line修改__LINE__和__FILE__的值#progma允许编译器提供额外功能

在定义宏的时候,有两个运算符:

运算符功能#将宏参数转换为字符串##将多个符号连接成一个标识符

2. 宏定义

1. 一般在宏定义的结尾不加分号。

我们在使用的时候,要加上分号,像我们平时写语句一样。

2. 注意加括号。

在有参数的空定义中,如果含有数值运算,那么就要在“宏整体”和“宏参数”两端都要加上括号。

如:#define max(a, b) ((a)+(b));

3. 注意空格。

在有参数的宏定义中,注意“宏名称”和“参数列表”之间不能有空格。

如:#define max (a, b) ((a)+(b)); 在"max”和”(a, b)”之间不能有空格。

4. 不要使用有副作用的参数区调用宏。

常见的有副作用的参数有:a++,getchar()等。

如:宏定义为#define max (a, b) ((a)+(b)); 那么使用max(i++, j++)调用该宏,会造成 i 或 j 中的一个值增加2,而不是我们期望的 1。

5. 可以使用编译器选项 添加宏 和 移除宏。

我使用的是gcc,添加宏的指令是”-D”,移除宏的指令是”-U”。

6. 宏参数替换的时候,不会替换字符串中的字符。

即不会替换双引号之间的字符,其他的都会被替换,包括单引号之间的。

7. 可以使用#将 宏参数的值 转化为字符串。

直接使用#,是将宏参数的名称转化为字符串。利用下面的技巧(增加一个过渡宏),可以将“宏参数的值”转化为字符串(当宏参数有值时,这时的宏参数常常也是一个宏)。

[cpp] view plain copy

#include <stdio.h>#include <stdlib.h>#define NUMBER ten /* 宏名称为NUMBER,宏的值为ten */#define Str(x) #x#define XStr(x) Str(x) /* 增加的一个 过渡宏 */int main(){printf("Str(NUMBER) == %s /n", Str(NUMBER));printf("XStr(NUMBER) == %s /n", XStr(NUMBER));system("pause");return EXIT_SUCCESS;}

输出结果为:

[cpp] view plain copy

Str(NUMBER) == NUMBERXStr(NUMBER) == ten

8. 使用##运算符来实现标识符连接。

不过,不建议使用操作符##来连接标识符,因为这个容易是程序可读性大大降低。

3. 文件包含

1. 要将头文件的定义在保护条件中。

目的是为了防止重复包含头文件。如果你查看过gcc或者其他编译器的源代码,你一定对这个非常熟悉。

例如,你要编写一个头文件,myheader.h,那么你的头文件的内容形式应该为:(定义一个_MYHEADER宏)

[cpp] view plain copy

#ifndef _MYHEADER#define _MYHEADER 1/* 中间是你的头文件内容 */#endif /* _MYHEADER */

2. 注意windows系统和Unix系统的路径符号不同。

可以再#include中指定路径来包含文件,例如 #include “../head.h”。但是注意,windows中使用反斜线”/”作为路径分隔符,而Unix系统使用的是斜线”/”。

3. 可以使用 编译器选项 来设置搜索路径。

我使用的gcc,使用的-Idir选项,例如: -I"D:/Dev-Cpp/include"。

4. 条件编译

1. #ifdef等价于#if defined(),#ifndef等价于#if !defined()。

2. 在#if中可以使用逻辑操作符(&&、||、!)。在#ifdef 中是不可以使用的,这也是#if的优越点。

[cpp] view plain copy

#include <stdio.h>#include <stdlib.h>#define A 1#define B 0int main(){#if defined( A ) && defined( B )printf("test logic operation in #if /n"); /* 如果上面的逻辑判断成立,那么将打印出一句话;如果不成立,那么 就不会打印这句话 */#endifsystem("pause");return EXIT_SUCCESS;}

运行结果:

[cpp] view plain copy

test logic operation in #if

3. sizeof(int)在预编译阶段是不会被求值的。

只要知道“预编译阶段”在真正的“编译阶段”之前,就很容易理解了。预编译阶段只是对组成源代码中的字符进行作用,从某种意义上来说,它有时甚至不知道它的操作对象是什么,它只是按照既定的规则执行替换。

sizeof(int),无论是sizeof的解析,还是类型的解析,都是在“编译阶段”才开始的,编译阶段知道它的操作对象是什么。

下面的代码是错误的

[cpp] view plain copy

#if sizeof(int) == 2printf("precompile sizeof(int)");#endif

5. 额外注意

把一个预处理指令写成多行的形式,要使用符号”/”,并且在该符号后面应紧跟换行符。而非预处理指令的代码行不需要使用该符号,直接换行即可。 原因:编译阶段会自动忽略空白符,而预编译阶段不会。

原文地址:https://www.cnblogs.com/tongongV/p/10744248.html

时间: 2024-10-10 10:48:06

C语言的预编译,程序员必须懂的知识!【预编译指令】【预编译过程】的相关文章

每一个程序员必须掌握的知识,字符集与字符编码.

1. 基础知识 计算机中储存的信息都是用二进制数表示的:而我们在屏幕上看到的英文.汉字等字符是二进制数转换之后的结果.通俗的说,按照何种规则将字符存储在计算机中,如'a'用什么表示,称为"编码":反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密.在解码过程中,如果使用了错误的解码规则,则导致'a'解析成'b'或者乱码. 字符集(Charset):是一个系统支持的所有抽象字符的集合.字符是各种文字和符号的总称,包括各国家文字.标点符

PHP笔记——java程序员看懂PHP程序

PHP笔记——java程序员看懂PHP程序 php是一种服务器端脚本语言,类型松散的语言. <?php   ?>       xml风格 <script language=”php”></script>   脚本风格 <?       ?>    简短风格 <%              %>    ASP风格 以;结尾: 注释: a)         // b)         # c)         /*     */ 变量是存储数据的容器

程序员必需懂的黑化,飞升CTO就靠这个了

当前的程序猿世界"血雨腥风",如果不懂得黑话,不懂暗语,就很难在这江湖立足,今天,小编将和一起重温一下程序猿的黑话,是大鸟的可以跳过,是菜鸟的你必须要看...比你学会了<葵花宝典>的招式,不学点心法怎么行?身心合一方能立于不败之地 一,老板大BOSS的黑话 你来我办公室一下 == 老子又想到了绝妙的idea 得专注用户体验 == 界面画的好看点 产品气质不够年轻 == 饱和度通通调最高 产品气质不够成熟 == 界面通通做成黑的 产品不够大气 == 我也不知道哪不好反正就是不

黑马程序员——Java集合基础知识之Map

Map概念 要同时存储两个元素Key和Value,他们之间有映射关系,每个键不能重复,每个键只能映射到一个值. 当数据之间存在映射关系的时候,考虑使用Map集合. Map常用方法 如果添加的键原来有值,后添加的值会覆盖前面的值,并返回之前的值.put会返回来先添加的值,后添加的值会覆盖原有的值. Map tm =new TreeMap(); tm.put (key, value);//MAP没有add tm.remove (key) ;//去除一个key和对应的value,若不存在key返回nu

做一名程序员需要学哪些知识

问这个问题的人都该打,先拖出去,赏一丈红~ 如果你是计算机学生或者意欲从事IT行业的人,那么再仗50大板!! 为什么?因为你肯定想都没有想过这个问题,一个典型的伸手党,这对于从事计算机的人来说是大忌!!且看下面这2个主要原因: 1)程序不可能一帆风顺,如果有这个时候,那么你可能在做梦,总会遇到各种各样的问题,难道你每次遇到问题都张口问“哪个大牛,来帮我看看这个程序什么问题吧,怎么老是提示错误呢?”,如果真的是比较复杂的问题还好,大牛看过之后替你解决了,自己也更有经验了,如果是差一个逗号之类的问题

黑马程序员——Java集合基础知识之Collection

集合基础知识--Collection Java中集合框架由常用的Collection接口和Map接口组成,而Collection接口又有两个子接口,是List接口和Set接口,常用的集合框架由这三个类组成. List接口的功能方法 List的使用最为简单,创建集合,通过add方法添加元素,get方法获取元素,通过迭代器获取元素.List接口存放的数据无序的,添加速度快,但是查询速度慢,因为查询的时候必须遍历,每次都重头开始,效率较低.常用实现类有ArrayList,LinkedList. Lis

黑马程序员——java多线程基础知识1

多线程 进程是一个正在执行的程序. cpu在同时执行这些程序,其实是跳跃式的,做快速的切换,时间很短.一个进程可能存在多条路径.迅雷的多路径.每一个进行执行都有一个执行顺序,该顺序是一个执行路径,或这叫一个控制单元.每一个进程至少有一个线程,线程就是进程中的一个独立的控制单元,线程控制进程的执行.jvm启动的时候会有一个进程就叫做java,exe,该进程中至少有一个线程在控制Java程序的执行 ,而且该线程的执行代码在 主函数中.该线程称为主线程.虚拟机至少也有两个线程,一个主线程执行,另一个负

黑马程序员——Java多线程基础知识2

多线程协同 线程间的通讯:我们对资源的操作动作不同,比如说两个卡车一个拉煤一个装煤.但是他们共享了一个资源. 怎么样把这个资源拿出来?怎样把车装满?这个资源当然是一个类,他里面的组成元素就是对象!!现在我们就要有操作对象的思想了,我用对象把这车装满,现在一车装一个对象. 等待唤醒机制: 用的不是sleep是wait.flag标记,这是两人沟通的方式.其实每个标记就要做一次等待或者notify,判断wait,改值notify.线程池.notify唤醒里面的线程,按顺序唤醒.wait和notify必

做一名程序员需要学哪些知识?

问这个问题的人都该打,先拖出去,赏一丈红~ 如果你是计算机学生或者意欲从事IT行业的人,那么再仗50大板!! 为什么?因为你肯定想都没有想过这个问题,一个典型的伸手党,这对于从事计算机的人来说是大忌!!且看下面这2个主要原因: 1)程序不可能一帆风顺,如果有这个时候,那么你可能在做梦,总会遇到各种各样的问题,难道你每次遇到问题都张口问“哪个大牛,来帮我看看这个程序什么问题吧,怎么老是提示错误呢?”,如果真的是比较复杂的问题还好,大牛看过之后替你解决了,自己也更有经验了,如果是差一个逗号之类的问题