• 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定义常量。

  • 相关阅读:
    162. Find Peak Element
    475. Heaters
    字符串统计
    数据的交换输出
    偶数求和
    青年歌手大奖赛_评委会打分
    蟠桃记
    素数判定
    多项式求和
    出现Presentation Error的解决方法
  • 原文地址:https://www.cnblogs.com/ofnoname/p/11621345.html
Copyright © 2020-2023  润新知