預處理指令就是我們程式開頭以#字元開頭的指令。為什麼叫預處理指令?因為這些指令是在編譯時的第一步就執行了的,不會轉為彙編碼。
編譯器編譯代碼的步驟:
- 預處理。處理#include,#define等指令并删除注釋,是以無論怎麼寫都不會再第一步CE。
- 編譯。真編譯會分析代碼文法(開了O2還會進行優化)并生成彙編檔案。
- 彙編。将彙編碼轉為機器碼。
- 連結。根據電腦情況進行重定位,連結庫等,生成最終可執行檔案
使用
-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
-
替換字元串時會在兩端加上空格
我們都知道
會因為>>被識别為右移而CE是以必須補空格。但是如果這樣寫:vector <pair<int,int>>
#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定義常量。