C++ #include和#define命令

预处理命令就是我们程序开头以#字符开头的命令。为什么叫预处理命令?因为这些命令是在编译时的第一步就执行了的,不会转为汇编码。

编译器编译代码的步骤:

  1. 预处理。处理#include,#define等命令并删除注释,所以无论怎么写都不会再第一步CE。

  2. 编译。真编译会分析代码语法(开了O2还会进行优化)并生成汇编文件。

  3. 汇编。将汇编码转为机器码。

  4. 链接。根据电脑情况进行重定位,链接库等,生成最终可执行文件

使用-E-S-c可以选择只执行第1步,12步,13步。如果对本文的知识有疑惑,您可以选择使用g++ -E 1.cpp -o 1.i来获取预处理后的.i文件来体会。另外-S也可以用于获取汇编码。

#符号应该是这一行的第一个非空字符。不过,也可以打\把内容移到下一行,就跟注释一样。

#define pi 3.14159 \
26535
//This is an \
example

这样就把下一行内容上移了。

常见的预处理命令如下:

#include 包含头文件
#ifdef 或 #if defined 如果定义了一个宏, 就执行操作
#ifndef 或 #if !defined 如果没有定义一个宏,就指执行操作
#define 定义一个宏
#undef 删除一个宏
#pragma 自定义编译器选项,指示编译器完成一些事

这里介绍#include#define

#include

这是最常见的文件包含命令。

无论你再厉害,即使你什么东西都手写,也需要#include <cstdio>

命令本质是把指定的文件中的函数,变量,宏等全部导入,可以理解成把那个文件全部内容复制粘贴到你的代码里的这一行了。

也正因为只是单纯的“复制粘贴”,命令有可能出现重复包含,应避免此现象的发生

#include使用尖括号和引号

#include命令不一定要使用尖括号,使用引号也是完全可以的。

区别在于引号会优先在要编译的文件目录下找,没找到才会调用标准库里的文件。

当然对于OIer来讲,#include <cstdio>#include "cstdio"就没有任何区别了,但是此时尖括号更为规范。

往往用尖括号括起来代表编译器目录的标准文件,用引号括起来代表同目录文件等自定文件。

为什么引用标准库的头文件时不加.h?

在C语言中其实是要加的,只能写#include <stdio.h>或者#include <math.h>

C++里把这些老文件的后缀名去掉并在前面加了一个c比如#include <cmath>,代表原老版本的库。只是仍保留了#include <math.h>等写法,两套文件的内容是一样的。但是对于C++的新内容(比如iostreamstack)就不能加.h了。

有人试了,#include <string.h>能用!但是string.h对应的是C语言里的cstring库而不是C++新增的那个string。使用前者是定义不了string类型的。cstring库是提供一些内存操作的函数和char数组的函数比如memset,memcpy,strlen。

万能头文件真的万能吗?

现在的NOIP已经支持万能头文件#include <bits/stdc++.h>。(关于斜杠:Unix系统的目录名分隔符为/(斜杠);而Windows下默认为\(反斜杠),但是同时也支持正斜杠,因此正斜杠是通用的。虽然Windows下可以用反斜杠,但反斜杠往往用作转义字符开头,在某些场合需要写成\\,否则会出错)

里面包含的很多初学阶段能用到的头文件,缺点是会大幅增加编译时间。

使用万能头文件不要用的变量名:y1, next, time, rand

包括很多极常见单词最好都不用,有些Windows可以,但在其他系统下无法通过编译。

#define

命令#define 叫做宏定义,用于代码中的字符串替换。是最常见的预处理指令之一

1. 不带参数的宏

#define MAX 10000
if (9874 > MAX)
 	return 0;

上述代码定义宏MAX,这句以后的"MAX"就代表10000。if中的式子为false。

该方法可用于替代const定义常量,而且只做了编译时代码替换,运行时不占用空间。也可以用于简化标准库里名字超长的函数。

另外如果这个常量需要多次进行运算(比如模数),据说写成const是更快的,经过个人不完全测试的确是这样的,但是效率差别很小,所以也不必过多在意,还是看自己更喜欢哪种写法。

注意:

  • define不会替换字符串和注释中的宏(废话)_

  • 替换宏时需要完全匹配,如定义宏“super”后,“supermarket”不会被部分替换。_

2. 带参数的宏

宏跟函数一样,可以带有参数。

例:用圆的半径求其周长和面积。

#define pi 3.14159
#define AREA(i) i*i*pi

double d;

int main()
{
    cin >> d;
    cout << AREA(d)<< endl ;
    return 0;
}

我们把宏写成AREA这种像函数的形式,之后出现AREA(i)时,
先发现括号里为2,即i=2,然后再做替换。

由于只做字符串替换,所以#define不仅可以定义常量,还可以定义表达式,函数,甚至代码段。

#define sum(a,b,c) (a)+(b)+(c)
#define max(a,b) (a>b)?(a):(b)
#define fors(a,b) for(int i=(a);i<=(b);i++)

利用宏定义可以使代码更加简洁易懂,同时用#define定义max等函数。速度快于函数,但也没快多少。

  • 命令#define命令后第一个单词为宏,其余为宏体。

    #define int long long
    #define abc def ghi \
    jkl
    #define register
    

    在第一句中,第一个int为替换体,即以后int代表long long。

    在第二句中,只有abc作为宏体,之后的abc被替换为def ghi jkl,反斜杠只有换行作用。

    在第三句中,程序里所有的register会被删除,可以用于调试。

  • 替换字符串时会在两端加上空格

    我们都知道vector <pair<int,int>>会因为>>被识别为右移而CE所以必须补空格。但是如果这样写:

    #define pii pair<int,int>
    vector <pii> a;
    

    却可以正常通过编译,这是因为替换时自动加上了空格。

    两个运算符构成新运算符加空格:<< >> -> ++ && += >=

    这样可以解决一些宏直接的字符串替换带来的问题

3. 宏的高级应用

##:连接左右两端的字符串

#: 把后面的参数变为一个字符串(即强行加上"")

#define a(x) p##x
#define b(x) #x

int p1 = 3, p2 = 4;
  
int main()
{
	printf("%d %d\n",a(1),a(2));
	puts(b(qwqwq));
}
//Output:
//3 4
//qwqwq

这个比较常见的就是用来缩写for,避免因b改变带来的问题,每次循环里终止变量都是另一个名字

#define F(i, a, b) for(int i=(a),end##i=(b); i<=end##i; i++)

#ifdef 如果定义了宏

#ifndef 如果没定义宏

#endif以上两句的终止句(相当于右括号)

在标准库中,每包含一个头文件,这个头文件里就会define一个表示这个文件已被包含的宏,如果这个文件第二次被包含,#ifndef为假不再执行,就会跳过文件,这样就可以避免重复包含导致CE。

#ifndef xxx //如果还没包含过
#define xxx //设定宏,下次遇到本文件将跳过
...
#endif

用宏来管理调试

  • 有些宏是在不同编译环境里就定义好的,利用这些就可以做些趣事。

    #ifndef ONLINE_JUDGE
    	freopen("testdata.in","r",stdin);
    	freopen("testdata.out","w",stdout);
    #endif
    //很多OJ(包括洛谷)都有这个宏
    
    
  • NDEBUG宏,定义NDEBUG宏表示“不调试”,此时程序的assert语句将不起作用。

    #define NDEBUG
    assert()//不再起作用
    

其他预定义的宏(便于输出调试信息):

__cplusplus //C++版本号
__FILE__ //文件名
__DATE__ //编译日期
__TIME__ //编译时间
__LINE__ //这一行的行号

4. 宏的撤销

能定义的宏就能取消,使用#undef直接接宏名就可以撤销宏。

#define sum(a,b) a+b
#define e 2.718
int a=sum(9,6);
double b=e*3;
#undef sum(a,b)
#undef e
#undef __cplusplus

5. 宏替换的注意事项

宏虽然方便易用,但使用不当可能不会产生期望的结果

  • 在语句两端加上括号

    #define DEF 2+3
    int a = DEF+5;
    int b = DEF*7;
    
    

    DEF以2+3的形式直接带入,没有转化为5

    在A的定义中,a将被解释为“2+3+5”,其值为10.

    但B将被解释为“2+3*7”,乘法先算,值为23,不是我们希望的35.

    解决方法就是在参数左右加上括号

  • 将量指为我们希望的类型

    #define MAX 1e6
    int a[MAX];
    
    

    此时会CE。因为1e6是一个double类型,数组大小只能用int,由于MAX是文本替换导致这里并不会转换类型。

    这是可以在前面加上(int),或者使用const定义常量。

原文地址:https://www.cnblogs.com/ofnoname/p/11621345.html