C++预处理

预处理指令

 

一,预处理

1.1.定义

在编译器正式编译源代码之前,会先进行预处理操作。

在C++中,所有的预处理指令都由“#”开头。预处理指令结尾不写分号。

1.2.预处理了什么

把注释去掉,把include的头文件引用进来,define的内容进行替换。

1.3.有哪些预处理指令?

#include

#define

#if

#else

#endif

 

二,include

用法1:#include<文件>

用法2:#include“文件”

用法1用于包含标准库所在目录的头文件,用法2表示包含当前目录下的头文件。

说白了,include就是把另外一个文件的所有内容不管三七二十一放进来就是了。

问题:如果想要引用其他文件夹的头文件?

回答:加上目录不就行了。例如bits/stdc++.h,表示在标准库目录下bits目录下的stdc++.h文件。

问题:引用的文件有类型的要求吗?(必须是.h?)

答案:没有要求,只要内容是文本就行(例如txt)。#include<iostream>,iostream就是一个没有扩展名的文件。

 

三,define指令

define指令用于定义宏。

3.1.无参数宏

define在这类场合下,可以起到常量定义的效果。

 

#include<bits/stdc++.h>
#define PI 3.14159
using namespace std;
int main(){
  cout<<PI<<endl;
}

 

 

在运行第5行的时候,预处理器会把PI替换为3.14159。

3.2.带参数宏

格式:#define 宏名(参数列表) 内容

我们知道,直接定义函数,函数由于要有调用——返回的过程,执行速度相对较慢。如果使用宏,速度会有提高。

示例:swap函数。

 

#include<bits/stdc++.h>
#define Swap(a,b) {int temp=a;a=b;b=temp;}
using namespace std;
int main(){
  int m=2,n=3;
  Swap(m,n);
  cout<<m<<n;
}

 

程序会把swap(m,n)按照define替换为:int temp=m;m=n;n=temp;

 

3.3.参数宏的局限性

例如:计算平方。

#include<bits/stdc++.h>
#define func(a) a*a
using namespace std;
int main(){
  cout<<func(2+3);
}

 

 

输出:11

 

我们观察一下,func(2+3)替换成了:

2+3*2+3,2+3没有打上括号,结果等于2+6+3=11。

因此,我们需要进行修改。

 

#include<bits/stdc++.h>
#define func(a) ((a)*(a))
using namespace std;
int main(){
 cout<<func(2+3);
}

 

这样就解决了。但是还有问题,这是避免不了的。

#include<bits/stdc++.h>
#define func(a) ((a)*(a))
using namespace std;
int main(){
    int n=4;
    cout<<func(++n);
}

 

输出:36

这是因为,++n执行了两次。这是由于宏的局限性所致的。对于这类的函数,我们不能用define,必须使用普通函数或者Inline函数。

 

三,条件编译

3.1.条件编译

有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。条件预处理的结构与if 选择结构很像。请看下面这段的代码:

#ifdef DEBUG
cout<<a<<endl;
#endif

 

这句话说明,如果定义了debug宏,那么执行下面的语句。在调试程序的时候,可以这样编译,不调试的时候只要取消定义debug即可。

 

3.2.示例:重复引用头文件问题

比较常见的例子是重复引用头文件的问题。例如,程序中有:

#include<a.h>

#include<b.h>

不巧的是,b.h的开头也有个include<a.h>,这样把a.h引用两遍。如果变量或者函数定义两遍会出错,因此,头文件一般都有一个避免重复引用的机制,如下:

 

#ifndef _a_h

#define _a_h 1

头文件内容

#endif

 

第一次引用时候,没有定义a_h,因此把头文件引用进来。第二次,已经定义了a_h,就跳过整个头文件。这样的设计就可以保护头文件不被重复引用。自己编写自己的头文件时,也必须要注意这一点。

 

四,#和##运算符

4.1.token

C++的token包括关键字,标识符,括号,符号等。

我们所有的define替换都是对token进行替换的(并非按字符替换)。例如:#define f 1

那么:cout<<ff<<endl;

不会把ff替换为11。(因为ff是一个token)

 

4.2.#运算符

#运算符用于把一个token转换为字符串。

 

举例:assert模拟。assert(x)的作用是检测x是否为0。

例如,assert(a+2),如果a=-2,那么a+2等于0,输出:“assertion a+2 failed”

assert(x==3),如果x不等于3,输出“assertion x==3 failed”

分析:如果直接用普通函数assert(x),只能判断x是否为0,无法输出表达式。(因为输出的内容有assertion 表达式 failed,用普通变量只能输出assertion 0 failed)

因此,使用#运算符先转换为字符串,再输出。

程序:

#define Assert(expression) Assert_Sub(#expression,expression)
void Assert_Sub(char *s,int n){
  if(n==0)cout<<“assertion”<<s<<“failed”;
}

 

#expression表示把expression这个token转换为字符串。因此,我们可以完美解决这个问题。

 

4.3.##运算符

##运算符用于把两个token连接为一个token。

举例:

#include<bits/stdc++.h>
#define cat(a,b) a##b
using namespace std;
int main(){
  int ab=3;
  cout<<cat(a,b);

}

 

a和b连接起来就是标识符ab,也就是变量。

posted @ 2021-07-26 18:13  计算机知识杂谈  阅读(301)  评论(0编辑  收藏  举报