天天看點

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++的新内容(比如

iostream

stack

)就不能加

.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定義常量。

下一篇: 求原根