C语言 预处理详解(二) #命令行定义 #条件编译 #文件包含 #其他预处理指令


前言

路漫漫其修远兮,吾将上下而求索;


一、命令行定义

许多C的编译器提供了一种能力,允许命令行中定义符号,用于启动编译的过程

例如,当我们根据同一个源文件要编译出一个程序的不同版本的时候,便可以利用到命令行;假设在一个数组中声明了某个长度的数组,如果机器的内存有限,此时就需要一个比较小的数组,但是倘若该机器的内存比较大,那么所需的数组就应该大一些;

注:在VS编译器下不好演示其效果,此处最好的在Linux 环境中的gcc 编译器来演示;

Linux 的命令行:

  • ls : list  -->  列出当前目录下的文件;
  • ls -l :将文件列举出来,像在ls 命令之后再跟一个 "-l" 这样的参数,此参数便叫作命令行参数;
  • gcc test.c -D  中命令行参数-D 可以定义一个符号

倘若代码中有些符号未定义,例如 SZ, 我们便可以在编译期间在命令行利用命令参数来指定该未定义符号的大小,如若你要利用命令行指令将SZ 赋值为100 ,便可以利用: gcc test.c -D SZ=100 

命令行定义在什么情况下用的比较多?

  • 我们写了一段代码,此代码会运行在不同的机器上,所以会针对不同的机器写出不同的代码,总是更改代码会很麻烦,于是乎便可以利用命令行来根据该机器针对性地给数据;

我们要如何才能写出一段代码而编译成不同的版本呢?

  • 在编译此版本的时候,将其参数部分进行配置即可(在编译的同时,为其参数指定一个值来配置);

此处利用命令行来确定数组的大小与变长数组有何区别?

  • 特别强调不可将此处利用命令行的指令来设置数组当作变长数组;此处的SZ这个符号的值是利用命令行定义的,而变长数组的大小是利用变量来指定的;

二、条件编译

什么叫做条件编译?

  • 在某些条件下的编译,即条件满足便去编译,条件不满足便不编译

倘若在编译一个程序的时候我们如果想要将一条语句(一组语句)编译或者放弃便就要利用到条件编译;

例:为数组赋值以将其值打印到屏幕的方式来观察此数组是否赋值成功:

但是,如果我们不想要打印数组元素的这个功能的话,可以将打印的这条代码删除,但是如果用来“调试”的代码很多,那么处理起来就很麻烦,因为你要将这些代码均找出来,况且也不敢保证下次就不会使用该代码,故而真的删除了也很可惜,于是乎此处便可以用到条件编译:

#ifdef symbol 

代码

#endif 

注:当symbol 为,即symbol 被#define 定义的时候,代码便会被执行;

当symbol 为假,即symbol 未被#define 定义的时候,代码便不会被执行;

此处#ifdef 判断的标准是 symbol 是否被#define 定义过,与其值是多少无关;

使用如下图:

#ifdef __DEBUG__ --> 如果#define 定义过__DEBUG__ ,那么便会参与编译;反之,倘若#define 没有定义 __DEBUG__ ,那么 #ifdef __DEBUG__ 与 #endif 之间的代码便不会执行;

#endif 后面注释的 __DEBUG__  有什么用?

  • 可以明确地告诉我们与#ifdef __DEBUG__  是一对;因为在正常写代码的过程中,存在很多的条件编译,并且可以进行嵌套;倘若写的 #ifdef 多了,那么对应的 #endif 便也多了,谁和谁相匹配便难以分清;而此时倘若在对应的 #endif 上加上注释,我们便可显然得知其匹配的情况,增加了代码的可读性;

常见的条件编译指令:

f

具体使用如下:

例一:

使用

看到这,你可能会问是否可以利用当#if 的常量表达式为假的时候, 利用其来进行注释?

  • 不推荐,因为倘若使用这种方式来进行注释,当别人看到此代码的时候联想到的是条件编译,会让别人难以理解此代码的含义,降低了代码的可读性;

注:#if 后面跟的一定是常量表达式!

条件编译的指令在预处理阶段便会被处理掉,而变量是在程序运行函数栈帧的开辟而创建的;故而#if 的表达式不能为变量,必须为常量表达式

例二:

使用:

具体使用如下:

 

我们看一下此程序预处理之后的结果,如下图:

例三:

使用:

其具体使用如下:

注:条件编译在底层原码中用得很多,因为在底层原码中要处理跨平台、各种各样得细节问题,而针对各种各样得问题要进行适配;

三、文件包含

在前面得学习中我们得知,#include 指令使得编译程序将另一源文件嵌入到带有 #include 的源文件之中;在预处理阶段:先删除这条指令,然后用包含得文件的内容替换;

那么倘若一个源文件被包含了10次,那么实际中便会被编译10次;

什么叫做文件包含?

  • 在使用库函数时,我们通常要此库函数对应的头文件进行包含的操作;以及我们自己写的头文件也要在源文件中进行包含;

(一)、本地文件包含

#include "filename"

(二)、库文件包含

#include<filename.h>

注:头文件如果在源文件中被多次包含会使得我们的代码非常冗长

如何做才能避免头文件被多次包含呢?

即即使头文件被多次包含也不会极大地增加源代码的长度?

方法一:利用条件编译:

分析:

方法二:利用 #pragma once

想必仔细观察VS编译器的你会发现,当你创建一个头文件的时候,在此头文件中会自动包含 #pragma once ~

这种方法是一种比较现代的解决方法,在一些新的编译器下可用(在一些古老的编译器下不可使用此方法,eg. vc 6.0 不支持此法);

头文件的包含利用的是 < >,而当要包含自己的头文件的时候需用 " ", 这两种写法到底有什么区别呢?

  • < > 与 " " 的区别在于查找的策略不一样;
  • < > 的查找策略:直接在编译器所提供的库目录下去查找(或者在系统提供的库目录下去查找)
  • " " 的查找策略 : 先在自己本地所在的目录(该代码所在的路径下)下去查找,倘若找不到,便会去库目录下去查找;

故而其实也是可以用 " "  来包含库中的头文件,只不过这种方法比较慢而已,因为查了两个地方,而导致整体的效率比较慢;

日后包含头文件如何选择?到底是用 < > 还是 " " 呢?

  • 包含自己定义的头文件使用 " " , 而库目录中的头文件使用 < >  

四、其他预处理指令

预处理指令
预处理名称意义
#define宏定义
#undef撤销已经定义过的宏名
#include使得编译程序将另一源文件嵌入到带有 #include 的源文件之中
#if

#if 的一般含义是:如果 #if 后面的常量表达式为 true, 则编译它与 #endif 之间的代码

否则便会跳过这些代码;

命令#endif 标识一个 #if 块的结束;

#else 命令的功能有点像C语言中的else , #else 建立另一选择(在#if 的常量表达式为假的情

况下);

#elif 命令的意义与 else if 相同,它形成了一个 if else - if 阶梯状语句,可进行多种编

译选择

#else
#elif
#endif
#ifdef#ifdef#ifndef 命令分别表示”如果有定义“”如果无定义“ , 是条件编译的另一种方法 
#ifndef
#line

改变当前行数和文件名称,它们是在编译程序中预先定义的标识符命令的基本形式: 

#line number["filename"]

#error编译程序的时候,只要遇到 #error 就会生成一个编译错误提示信息,并停止编译
#pragma

可以设定编译程序完成一些特定的动作(可以通过编译程序的菜单设定,也可以直接写在

源代码之中),它允许向编译程序传递各种指令;例如,编译程序可能有一种选择,它支

持对程序执行的跟踪,可用 #pragma 语句指定一个跟踪选择


总结

1、命令行定义:许多C的编译器提供了一种能力,允许命令行中定义符号,用于启动编译的过程

2、条件编译:在某些条件下的编译,即条件满足便去编译,条件不满足便不编译

3、常见的条件编译指令:

1、条件编译

#if  常量表达式

            //……

#endif

//常量表达式由预处理器求值

2、多个分支的条件编译

#if  常量表达式

           //……

#elif

          //……

#else

         //……

#endif 

3、判断是否被否定

#if  defined(symbol)

        //……

#endif 

#ifdef symbol

        //……

#endif

#if  !defined(symbol)

       //……

#endif

#ifndef symbol

        //……

#endif

4、本地文件包含:#include "filename"

库文件包含:#include<filename.h>

5、即使头文件被多次包含也不会极大地增加源代码的长度?

方法一:利用条件编译 #ifndef #define #endif 

方法二: 利用 #pragma once

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值