1. 概述
預(yù)處理命令就是我們程序開頭以#字符開頭的命令。為什么叫預(yù)處理命令?因?yàn)檫@些命令是在編譯時(shí)的第一步就執(zhí)行了的,不會(huì)轉(zhuǎn)為匯編碼。
編譯器編譯代碼的步驟:
預(yù)處理。處理#include,#define等命令并刪除注釋,所以無論怎么寫都不會(huì)再第一步CE。
編譯。真編譯會(huì)分析代碼語法(開了O2還會(huì)改一些)并生成匯編文件。
匯編。將匯編碼轉(zhuǎn)為機(jī)器碼。
鏈接。根據(jù)電腦情況進(jìn)行重定位,鏈接庫等,生成可執(zhí)行文件
使用-E
,-S
,-c
可以選擇只執(zhí)行第1步,1~2步,1~3步。如果對(duì)本文的知識(shí)有疑惑,您可以選擇使用g++ -E 1.cpp -o 1.i
來獲取預(yù)處理后的.i
文件深刻體會(huì)。另外-S
也可以用于獲取匯編碼。
絕大部分預(yù)處理命令在OI里用處不大,但也有功能強(qiáng)大的預(yù)處理命令。
#
符號(hào)應(yīng)該是這一行的第一個(gè)非空字符。不過,也可以打\
把內(nèi)容移到下一行,就跟注釋一樣。
#define pi 3.14159 \
26535
//This is an \
example
這樣就把下一行內(nèi)容上移了。
洛谷的編輯器不會(huì)這么顯示,但本地編輯器上你能發(fā)現(xiàn)下一行也變成了注釋或預(yù)處理樣式。
常見的預(yù)處理命令如下:
#include 包含頭文件
#ifdef 或 #if defined 如果定義了一個(gè)宏, 就執(zhí)行操作
#ifndef 或 #if !defined 如果沒有定義一個(gè)宏,就指執(zhí)行操作
#define 定義一個(gè)宏
#undef 刪除一個(gè)宏
#pragma 自定義編譯器選項(xiàng),指示編譯器完成一些事
這里介紹3個(gè)最常用的預(yù)處理命令:#include
,#define
,#pragma
這是最常見的文件包含命令。
無論你再厲害,什么東西可以手寫,也需要#include <cstdio>
命令本質(zhì)是把指定的文件中的函數(shù),變量,宏等全部導(dǎo)入,可以理解成把那個(gè)文件全部?jī)?nèi)容復(fù)制粘貼到你的代碼里了。
不過,如果是單純的粘貼,#include兩遍應(yīng)該會(huì)有重復(fù)定義CE才對(duì)。但是標(biāo)準(zhǔn)庫使用宏定義避免了這一點(diǎn)(參見后文)。自己寫頭文件時(shí)也要注意。
事實(shí)上,#include
命令不一定要使用尖括號(hào),使用引號(hào)也是完全可以的。
區(qū)別在于引號(hào)會(huì)優(yōu)先在要編譯的文件中找,沒找到才會(huì)調(diào)用標(biāo)準(zhǔn)庫里的文件。
當(dāng)然對(duì)于OIer來講,#include <cstdio>
和#include 'cstdio'
就沒有任何區(qū)別了,但是此時(shí)尖括號(hào)更為規(guī)范。
在自己用C++開發(fā)小游戲時(shí),為了便于管理,可以像標(biāo)準(zhǔn)庫一樣把用途相似的函數(shù)單獨(dú)用一個(gè)文件保存。在需要時(shí)就將其包含,此時(shí)就需要用到引號(hào)了。
在C語言中其實(shí)是要加的,只能寫#include <stdio.h>
或者#include <math.h>
C++里把這些文件的后綴名去掉并在前面加了一個(gè)c比如#include <cmath>
但是這些傳統(tǒng)的庫你如果使用老寫法,仍然是可以過編譯的,只是不規(guī)范。
但是對(duì)于C++的新內(nèi)容(比如iostream
和stack
)就不能加.h
了。
有人試了,會(huì)說#include <string.h>
能用!但是string.h
對(duì)應(yīng)的是C語言里的cstring
庫而不是C++新增的那個(gè)string
。使用前者是定義不了string
類型的。cstring
庫是提供一些內(nèi)存操作的函數(shù)和char數(shù)組的函數(shù)比如memset,memcpy,strlen。
現(xiàn)在的NOI)(P已經(jīng)支持萬能頭文件#include <bits/stdc++.h>
。
(注意是正斜杠不是反斜杠,寫錯(cuò)了有可能CE)
事實(shí)上他包含的東西你是不可能記完的,但是您能用到的東西里面絕對(duì)都有。
C++11
里還新包括了random
,unordered_map
等庫。
詳見stdc++.h原文件
雖然說不上萬能,OI里的確完全夠用了。
辟謠?。?!萬能頭文件并不會(huì)減慢程序運(yùn)行速度,內(nèi)存上的增加幾乎可以忽略。在編譯時(shí)main里沒有用到的東西就會(huì)被優(yōu)化掉。
而且你隨時(shí)帶上十幾個(gè)頭文件,又在說萬頭不好,根本沒說服力
當(dāng)然有可能增加編譯時(shí)間和源程序大小,然并卵
之前說過#include
的本質(zhì)是把指定文件復(fù)制進(jìn)這一行,所以如果是在函數(shù)內(nèi)寫的這個(gè)命令,就只對(duì)這一個(gè)函數(shù)有效。
void func()
{
#include 'test.h'
mmm();//可以使用test.h里的函數(shù)
}
int main()
{
func();
mmm()//CE。不能使用test.h里的函數(shù)。
}
但是OI里不能這么用,因?yàn)闃?biāo)準(zhǔn)庫還涉及到命名空間的問題。
按照標(biāo)準(zhǔn)的話,.h
用于存放大篇的宏定義和函數(shù),變量的聲明(也就是函數(shù)第一行的函數(shù)名和參數(shù)列表),而同名的.cpp
則存放函數(shù)的具體實(shí)現(xiàn)。.h
里寫一個(gè)#include <test.cpp>
。主程序只要包含test.h就可以使用庫里的函數(shù)了。
不過為了節(jié)省工作量,我們可以在.h
里就直接定義好函數(shù),也可以選擇在主程序里直接#include <test.cpp>
。包含命令的本質(zhì)是復(fù)制粘貼,這樣寫也是完全沒有問題的。
使用萬能頭文件不要用的變量名:y1, next, time, rand
包括很多常見單詞最好都不用,有些Windows可以,但是評(píng)測(cè)時(shí)會(huì)CE。
命令#define 叫做宏定義,用于代碼中的字符串替換。是最有用的預(yù)處理指令
#define MAX 10000
if (9874 > MAX)
return 0;
上述代碼定義宏MAX,這句以后的'MAX'就代表10000。if中的式子為false。
該方法可用于替代const定義常量,而且只做了代碼替換,運(yùn)行時(shí)不占用空間。也可以用于簡(jiǎn)化標(biāo)準(zhǔn)庫里名字超長(zhǎng)的函數(shù)。
另外如果這個(gè)常量需要多次進(jìn)行運(yùn)算(比如模數(shù)),據(jù)說寫成const是更快的,經(jīng)過個(gè)人不完全測(cè)試的確是這樣的,但是效率差別很小,所以也不必過多在意,還是看自己更喜歡哪種寫法。
注意:
1. #define不會(huì)替換字符串和注釋中的宏(廢話)
2. 替換宏時(shí)需要完全匹配,如定義宏“super”后,“supermarket”不會(huì)被部分替換。
事實(shí)上,宏跟函數(shù)一樣,可以帶有參數(shù)。
例:用圓的半徑求其周長(zhǎng)和面積。
#define pi 3.14159
#define AREA(i) i*i*pi
double d;
int main()
{
cin >> d;
cout << AREA(d)<< endl ;
return 0;
}
我們把宏寫成AREA這種像函數(shù)的形式,之后出現(xiàn)AREA(i)時(shí),
先發(fā)現(xiàn)括號(hào)里為2,即i=2,然后再做替換。
由于只做字符串替換,所以#define不僅可以定義常量,還可以定義表達(dá)式,函數(shù),甚至代碼段。
#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++)
利用宏定義可以使代碼更加簡(jiǎn)潔易懂,同時(shí)用#define定義max等函數(shù)。速度快于函數(shù),但也沒快多少。
注意:
命令#define命令后第一個(gè)單詞為宏,其余為宏體。
#define int long long
#define abc def ghi \
jkl
#define register
在第一句中,第一個(gè)int為替換體,即以后int代表long long。
在第二句中,只有abc作為宏體,之后的abc被替換為def ghi jkl,反斜杠只有換行作用。
在第三句中,程序里所有的register
會(huì)被刪除,可以用于調(diào)試。
特例(不是完全字符串替換,感謝@Black_white_Tony dalao):
我們都知道vector <pair<int,int>>
會(huì)因?yàn)?gt;>被識(shí)別為右移而CE所以必須補(bǔ)空格。但是如果這樣寫:
#define pii pair<int,int>
vector <pii> a;
卻可以正常通過編譯,這是因?yàn)槿绻鹍efine中的最后一個(gè)字符和后面第一個(gè)字符能構(gòu)成新運(yùn)算符時(shí),就會(huì)自動(dòng)加上空格。大家可以用g++ -E
指令看得更透徹一些。
兩個(gè)運(yùn)算符構(gòu)成新運(yùn)算符加空格:<< >> -> ++ && += >=
這個(gè)特例也許就是為了STL套STL的問題設(shè)計(jì)的吧。
注:C++11
里是可以直接寫vector <pair<int,int>>
的,但是你如果使用了宏定義,第一步預(yù)處理后的文件在這里仍會(huì)加上空格。
##
:連接左右兩端的字符串
#
:把后面的參數(shù)變?yōu)橐粋€(gè)字符串(即強(qiáng)行加上'')
#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
#ifdef
如果定義了宏
#ifndef
如果沒定義宏
#endif
以上兩句的終止句(相當(dāng)于右括號(hào))
在標(biāo)準(zhǔn)庫中,每包含一個(gè)頭文件,這個(gè)頭文件里就會(huì)define一個(gè)表示這個(gè)文件已被包含的宏,如果這個(gè)文件第二次被包含,#ifndef
為假不再執(zhí)行,就會(huì)跳過文件,這樣就可以避免重復(fù)包含導(dǎo)致CE。
有些宏是在不同編譯環(huán)境里就定義好的,利用這些就可以做些趣事。
#ifndef ONLINE_JUDGE
freopen('testdata.in','r',stdin);
freopen('testdata.out','w',stdout);
#endif
//很多OJ(包括洛谷)都有這個(gè)宏
或者也可以在開頭定義一個(gè)debug宏,把調(diào)試輸出的語句用#ifndef
括上,這樣刪除調(diào)試輸出就只需要注釋一行。
其他預(yù)定義的宏:(摘自cppreference)
__cplusplus //C++版本號(hào)
__FILE__ //文件名
__DATE__ //編譯日期
__TIME__ //編譯時(shí)間
__LINE__ //這一行的行號(hào)
能定義的宏就能取消,使用#undef直接接宏名就可以撤銷宏(包括預(yù)定義的)。
#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
宏雖然方便易用,但也有許多缺點(diǎn)。
#define DEF 2+3
int a = DEF+5;
int b = DEF*7;
DEF以2+3的形式直接帶入,沒有轉(zhuǎn)化為5
在A的定義中,a將被解釋為“2+3+5”,其值為10.
但B將被解釋為“2+3*7”,乘法先算,值為23,不是我們希望的35.
解決方法就是在參數(shù)左右加上括號(hào)
#define MAX 1e6
int a[MAX];
此時(shí)會(huì)CE。因?yàn)?e6是一個(gè)double類型,數(shù)組大小只能用int,由于MAX是文本替換導(dǎo)致這里并不會(huì)轉(zhuǎn)換類型。
這是可以在前面加上(int),或者使用const定義常量。
在我們尋找一道題最優(yōu)解的時(shí)候,最快的人(如果沒打表)往往會(huì)有幾十行的#pragma來卡常。那么這個(gè)命令有什么用?卡常的原理是什么呢?
#pragma
命令可以指定編譯選項(xiàng),或者讓編譯器完成一些命令。功能非常強(qiáng)大,這里只做非常淺顯的介紹。
部分內(nèi)容摘自百度百科。
添加在頭文件的開頭,可以告訴編譯器這個(gè)文件最多編譯一次,也可以用于防止重復(fù)包含頭文件。比前文#ifndef
好用,只是標(biāo)準(zhǔn)庫里沒用這個(gè)。
讓編譯器輸出括號(hào)里的字符串,配合#ifdef
,可以在編譯時(shí)就輸出一些特定的信息。
本身用于鏈接文件,OI里可以用來手動(dòng)擴(kuò)棧
#pragma comment(linker,'/STACK:1024000000,1024000000')
這個(gè)找遍全網(wǎng)也沒有準(zhǔn)確定義,大概就是將括號(hào)里的東西識(shí)別為指令。指令的速度比函數(shù)更快,借此加速。
#pragma GCC target('popcnt')
可以讓內(nèi)置函數(shù)__builtin_popcount()的速度提高一倍以上。
另外,如果你想使用指令集,也可以使用這條指令把指令集括上。
#pragma GCC target('avx,avx2,sse,sse2,sse3,sse4.1,sse4.2')
用于對(duì)齊結(jié)構(gòu)體
//#pragma pack(4)
struct Node
{
int a;
long long b;
}x;
本來一個(gè)結(jié)構(gòu)體的每個(gè)變量都會(huì)與最大的那個(gè)對(duì)齊,比如例子中int就與long long對(duì)齊了,字節(jié)數(shù)也為8。所以sizeof x = 16
。
但是如果有了那句#pragma,每個(gè)變量就會(huì)與4對(duì)齊,所以int字節(jié)數(shù)為4,long long由于本來就大于4就被忽略,sizeof x = 12
。這樣做一定程度上可以省空間。
但是對(duì)齊其實(shí)效率更高,所以x大一點(diǎn)好。
pop()可以用來取消pack()指令
將括號(hào)里的字符串帶入編譯參數(shù),相當(dāng)于可以自定義編譯參數(shù)。
如果輸入數(shù)字的話就會(huì)進(jìn)行O1/O2/O3優(yōu)化。用這個(gè)命令可以開啟編譯器自帶的優(yōu)化。
但是只能是編譯優(yōu)化方面的參數(shù),比如-o
指定文件名肯定不能加在里面。
最后附贈(zèng)網(wǎng)絡(luò)上廣泛流傳的40行優(yōu)化:
#pragma GCC target('sse,sse2,sse3,sse4.1,sse4.2,popcnt,abm,mmx,avx')
#pragma comment(linker,'/STACK:102400000,102400000')
#pragma GCC optimize('Ofast')
#pragma GCC optimize('inline')
#pragma GCC optimize('-fgcse')
#pragma GCC optimize('-fgcse-lm')
#pragma GCC optimize('-fipa-sra')
#pragma GCC optimize('-ftree-pre')
#pragma GCC optimize('-ftree-vrp')
#pragma GCC optimize('-fpeephole2')
#pragma GCC optimize('-ffast-math')
#pragma GCC optimize('-fsched-spec')
#pragma GCC optimize('unroll-loops')
#pragma GCC optimize('-falign-jumps')
#pragma GCC optimize('-falign-loops')
#pragma GCC optimize('-falign-labels')
#pragma GCC optimize('-fdevirtualize')
#pragma GCC optimize('-fcaller-saves')
#pragma GCC optimize('-fcrossjumping')
#pragma GCC optimize('-fthread-jumps')
#pragma GCC optimize('-funroll-loops')
#pragma GCC optimize('-fwhole-program')
#pragma GCC optimize('-freorder-blocks')
#pragma GCC optimize('-fschedule-insns')
#pragma GCC optimize('inline-functions')
#pragma GCC optimize('-ftree-tail-merge')
#pragma GCC optimize('-fschedule-insns2')
#pragma GCC optimize('-fstrict-aliasing')
#pragma GCC optimize('-fstrict-overflow')
#pragma GCC optimize('-falign-functions')
#pragma GCC optimize('-fcse-skip-blocks')
#pragma GCC optimize('-fcse-follow-jumps')
#pragma GCC optimize('-fsched-interblock')
#pragma GCC optimize('-fpartial-inlining')
#pragma GCC optimize('no-stack-protector')
#pragma GCC optimize('-freorder-functions')
#pragma GCC optimize('-findirect-inlining')
#pragma GCC optimize('-fhoist-adjacent-loads')
#pragma GCC optimize('-frerun-cse-after-loop')
#pragma GCC optimize('inline-small-functions')
#pragma GCC optimize('-finline-small-functions')
#pragma GCC optimize('-ftree-switch-conversion')
#pragma GCC optimize('-foptimize-sibling-calls')
#pragma GCC optimize('-fexpensive-optimizations')
#pragma GCC optimize('-funsafe-loop-optimizations')
#pragma GCC optimize('inline-functions-called-once')
#pragma GCC optimize('-fdelete-null-pointer-checks')
注意:
這類優(yōu)化的效果玄學(xué),因人而異,也與編譯環(huán)境相關(guān)。但是最壞情況也就沒有用,這些代碼不會(huì)因?yàn)榫幾g環(huán)境CE。
由于O2/O3/Ofast優(yōu)化已經(jīng)到達(dá)了改寫循環(huán),刪除多余代碼等毀天滅地的程度,很容易改變代碼的原意導(dǎo)致玄學(xué)錯(cuò)誤。使用這些優(yōu)化的時(shí)候一定要保證自己的代碼規(guī)范,否則就會(huì)有玄學(xué)問題出現(xiàn)。
并不知道NOI)(P能不能用,最好不用(你也不可能背下來)
還有一些命令,這里花上幾行介紹一下。
#error //在這一行顯示一個(gè)CE信息,并中斷編譯
#warning //在這一行顯示警告信息
#line //指定下一行的行號(hào)
#if //如果滿足則執(zhí)行,后面應(yīng)接布爾表達(dá)式,以#endif結(jié)尾
#elif //#if語句的分支
完結(jié)撒花,感謝陪伴
聯(lián)系客服