C++基础之预处理

C++基础之预处理

预处理器的主要作用就是: 把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有: 文件包含,条件编译、布局控制和宏替换4种。
常见的预处理命令:
#define 宏定义
#undef 取消宏
#include 文本包含
#ifdef 如果宏被定义就进行编译
#ifndef 如果宏未被定义就进行编译
#endif 结束编译块的控制
#if 表达式非零就对代码进行编译
#else 作为其他预处理的剩余选项进行编译
#elif 这是一种#else和#if的组合选项
#line 改变当前的行数和文件名称
#error 输出一个错误信息
#pragma 为编译程序提供非常规的控制流信息

文件包含

#include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。
使用#include命令时,如果包含的是C++系统头文件,则用一对尖括号将文件名包含起来,例如 #include<"iostream>。如果包含的是用户自己定义的头文件,则用一对双引号将文件名包含起来。目的是先搜索当前目录,再搜索系统目录。例如,#include<"io.h>是系统的io流,#include“io.h”是用户自定义的io流。

补充:#include<iostream>与#include<iostream.h>的区别
1.iostream.h只支持窄字符集,iostream则支持窄/宽字符集。
2.标准对iostream作了很多的改动,接口和实现都有了变化。
3.iostream组件全部放入namespace std中,防止了名字污染。
因此我们建议使用#include<'iostream>

宏替换

#define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
该命令有两种格式:一种是简单的宏定义,另外一种是带参数的宏定义。
(1)简单的宏定义。 #define 宏名 字符串
例如:#define pi 3.1415926
(2) 带参数的宏定义  #define <宏名>(<参数表>) <宏体>
  其中, <宏名>是一个标识符,<参数表>中的参数可以是一个,也可以是多个,视具体情况而定,当有多个参数的时候,每个参数之间用逗号分隔。<宏体>是被替换用的字符串,宏体中的字符串是由参数表中的各个参数组成的表达式。
  例如: #define SUB(a,b) a-b
  若程序中出现SUB(2,3) 则可以替换为2-3
若在程序中出现SUB(x-1,y+1)则可以替换为 x-1-y+1

补充:带参数的宏定义与带参数的函数的区别
  (1) 在带参的宏定义中,形式参数不是变量,只是一个符号,不分配内存单元,不必定义其形式参数的类型。发生宏"调用"时,只是将实参替换形参。而在函数中,形参和实参是完全独立的变量,它们均有自己的作用域。当调用发生时,实参传递给形参的过程(值传递或引用传递)完全不同于简单的宏替换。
  (2) 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
宏替换发生的时机

在C++实际通过了预处理、编译、汇编和链接几个过程。其中预处理器产生编译器的输出,它实现如下的功能:
(1) 文件包含
能够把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2)条件编译
预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,一般把排除在外的语句转换成空行。
(3)宏展开
预处理器将源程序文件中出现的对宏的引用展开成相应的宏定义,即本文所说的#define的功能,由预处理器来完成。
通过预处理器处理的源程序与以前的源程序有全部不一样, 在这个阶段所进行的工做只是纯粹的替换与展开,没有任何计算功能,因此在学习#define命令时只要能真正理解这一点,这样才不会对此命令引发误解并误用。
通过以上分析可以知道宏替换是发生在预处理阶段,并且替换只是简单的字符串替换

宏使用过程中的问题解析

1.在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引发误解和误用。以下例:

#define N 3+3
void main()
{
   int a=N*N;
   printf(“%d”,a);
}

(1)出现问题
在此程序中存在着宏定义命令,宏N表明的字符串是3+3,在程序中有对宏N的使用,通常同窗在读该程序时,容易产生的问 题是先求解N为3+3=6,而后在程序中计算a时使用乘法,即NN=66=36, 其实该题的结果为15,为何结果有这么大的误差?

(2)问题解析
宏展开是在预处理阶段完成的,这个阶段把替换文本只是看做一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串3+3来代替N,并不会增添任何的符号,因此对该程序展开后的结果是a=3+3*3+3,计算后=16,这就是宏替换的实质。

(3)解决办法
/将宏定义写成以下形式/
#define N (2+2)
/这样就可替换成(2+2)(2+2)=16*/

  1. 带参数的宏定义出现的问题
    在带参数的宏定义的使用中,极易引发误解。例如咱们须要作个宏替换能求任何数的平方,这就须要使用参数,以便在程序中用实际参数来替换宏定义中的参数。通常容易写成以下形式:
#define area(x) x*x
/*这在使用中是很容易出现问题的,看以下的程序*/
void main()
{
   int y = area(2+2);
   printf(“%d”,y);
}
按理说给的参数是2+2,所得的结果应该为4*4=16,可是错了,由于该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了, 在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那若是遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否能够呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,能够解决, 可是对于area(2+2)/area(2+2)又会怎么样呢,有的学生一看到这道题立刻给出结果,由于分子分母同样,又错了,仍是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个宏体上再加一个括号,即 #define   area(x) ((x)*(x)), 不要以为这不必,没有它,是不行的。
要想可以真正使用好宏定义,那么在读别人的程序时, 必定要记住先将程序中对宏的使用所有替换成它所表明的字符串,不要自做主张地添加任何其余符号,彻底展开后再进行相应的计算,就不会写错运行结果。
若是是本身编程使用宏替换,则在使用简单宏定义时,当字符串中不仅一个符号时,加上括号表现出优先级,若是是带参数的宏定义,则要给宏体中的每一个参数加上括号,并在整个宏体上再加一个括号。看到这里,不由要问,用宏定义这么麻烦,这么容易出错,可不能够摒弃它, 那让咱们来看一下在C语言中用宏定义的好处吧。

如:

#include <iostream.h>
#define product(x)    x*x
int main()
{
    int i=3;
    int j,k;
    j = product(i++);
    cout<<"j="<<j<<endl;
    cout<<"i="<<i<<endl;
    k = product(++i);
    cout<<"k="<<k<<endl;
    cout<<"i="<<i<<endl;
    return 0;
}
依次输出结果:
j=9;i=5;k=49;i=7

条件编译

通常,源文件中所有内容都要参加编译,但是在某些时候,可能希望源文件中某些部分在满足某些条件的情况下才进行编译,这就是所谓的“条件编译”。
条件编译有以下三种方式。
1:
#ifdef 标识符
程序片段1
#else
程序片段2
#endif
上述语句的意思就是如果标识符已被#define命令定义过,则对程序片段1进行编译;否则
对程序片段2进行编译。其中,程序片段2可以为一个空的程序段,那么#else就没有什么
实际的意义了,这时可以将其省略。下面看这段示例代码。

#include <iostream>
#define DEBUG
using namespace std;
void main()
{
     #ifdef DEBUG 
         cout << "Begin..." << endl;
     #else
         cout << "Cannot Begin!" << endl;
     #endif
     int array[10];
     for(int i = 0;i < 10;i++) {
       array[i] = i;
       #ifdef DEBUG 
          cerr << "i = " << i << endl;
          cerr << "array[i] = " << array[i] << endl;
       #endif
    }
}

2:
#ifndef 标识符
程序片段1
#else
程序片段2
#endif
形式2和形式1的区别在于#ifdef关键字换成了#ifndef关键字,其功能是如果标识符未被#define命令定义过,则对程序片段1进行编译;否则对程序片段2进行编译,这与形式1的功能正好相反。
3:
#if 表达式
程序片段1
#else
程序片段2
#endif
该形式完成的功能是如果表达式的值为真,则对程序片段1进行编译;否则对程序片段2进行编译。因此可以使程序在不同条件下完成不同的功能。

条件编译还常用于防止包含于头文件中的类或者函数被被重复定义多次。在一个程序中
多次包含某个头文件会导致类和函数的重定义问题。可以通过使用条件编译来防止该问
题的发生。下面看这段代码。

#ifndef FA_H
#define FA_H
// ... some declaration codes
#endif // #ifndef FA_H

布局控制

#progma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。
#pragma指令的作用是:用于指定计算机或操作系统特定的编译器功能。C 和 C++ 的每个实现均支持某些对其主机或操作系统唯一的功能。 例如,某些程序必须对将数据放入的内存区域进行准确的控制或控制某些函数接收参数的方式。 在保留与 C 和 C++ 语言的总体兼容性的同时,#pragma 指令使每个编译器均能够提供特定于计算机和操作系统的功能。

根据定义,#pragma指令是计算机或操作系统特定的,并且通常对于每个编译器而言都有所不同。 #pragma指令可用于条件语句以提供新的预处理器功能,或为编译器提供实现所定义的信息。

语法是:

#pragma  token-string
__pragma( token-string )	   //__pragma 关键字是特定于 Microsoft 编译器的

token-string 是一系列字符,这些字符提供了特定的编译器指令和参数(如果有)。 数字符号"#" 必须是位于包含#pragma指令行上的第一个非空白字符;空白字符可以分隔数字符号和词“pragma”。 在 #pragma 之后,编译转换器可分析为预处理标记的所有文本。 #pragma 的参数受宏展开的约束,如果编译器发现它无法识别的杂注,则它会发出警告并继续编译。
#pragma的具体内容请详见《#pragma用法解析》
###其他预编译指令
1.#line
#line的语法如下:

#line number filename
例如:#line 30 a.h 其中,文件名a.h可以省略不写。

这条指令可以改变当前的行号和文件名,例如上面的这条预处理指令就可以改变当前的行号为30,文件名是a.h。初看起来似乎没有什么用,不过,他还是有点用的,那就是用在编译器的编写中,我们知道编译器对C++源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行分析。
2.#error
#error语法如下:

#error info
例如:

#ifndef UNIX
#error This software requires the UNIX OS.
#endif

这条指令主要是给出错误信息,上面的这个例子就是,如果没有在UNIX环境下,就会输出This software requires the UNIX OS.然后诱发编译器终止。所以总的来说,这条指令的目的就是在程序崩溃之前能够给出一定的信息。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值