c++ include 路径_程序员应如何理解include
相信很多同學(xué)在學(xué)習(xí)C/C++后都有這樣的疑問,#include這句話到底是怎么意思?這句話的背后隱含了什么?我們常用的stdio.h存放在了哪里?
這篇文章就來解答這個(gè)問題。
誰來處理頭文件
有上述疑問的同學(xué)很可能是因?yàn)椴皇煜ひ粋€(gè)叫預(yù)編譯器(preprocessor)的東西。
讓我們簡(jiǎn)單的了解一下可執(zhí)行程序的生成過程。
程序員寫的大家都可讀的代碼是不能被CPU直接執(zhí)行的,CPU可以執(zhí)行的代碼是二進(jìn)制機(jī)器指令,因此一定有某個(gè)過程將程序員寫的程序轉(zhuǎn)換為了機(jī)器指令,這就是編譯器。
以上大部分同學(xué)應(yīng)該都知道,但是你知道編譯器在將代碼翻譯成機(jī)器指令前其實(shí)還有一個(gè)步驟嗎?這個(gè)步驟就是預(yù)編譯。
那么預(yù)編譯都用來做什么呢?請(qǐng)注意,接下來是重點(diǎn):
預(yù)編譯的工作非常簡(jiǎn)單,預(yù)編譯器找到源文件中#include指定的文件,然后copy這些文件的內(nèi)容并粘貼到#include這一行所在的位置。
假設(shè)在源文件a.c的第一行有一句#include ,那么預(yù)編譯器怎么處理?
預(yù)編譯找到stdio.h,把stdio.h的內(nèi)容粘貼到a.c的第一行中。
是不是很簡(jiǎn)單,完成這一過程后才是編譯器的任務(wù)。
因此我們知道,原來#include其實(shí)是告訴預(yù)編譯器把指定的頭文件內(nèi)容粘貼到當(dāng)前include所在的位置,也就是進(jìn)行文本替換。
頭文件是不會(huì)被編譯的
從上一節(jié)中我們知道頭文件原來是被預(yù)編譯器處理的,編譯器在編譯源文件時(shí)拿到的是已經(jīng)被預(yù)編譯器處理過后的源文件,因此頭文件是不會(huì)被編譯器直接處理的。
這一點(diǎn)要能意識(shí)到。
#include可以被放到代碼的任意位置
實(shí)際上#include可以出現(xiàn)在代碼中的任意一行,只不過我們習(xí)慣了在開頭使用#include,這是因?yàn)樽兞吭诼暶髦笆遣荒鼙皇褂玫摹?/p>
但是我們已經(jīng)知道了#include其實(shí)就是告訴預(yù)編譯器做一個(gè)簡(jiǎn)單的文本替換,因此任何需要進(jìn)行文本替換的需求其實(shí)都可以通過#include來完成的,記得博主很早在閱讀C代碼看到#include用作文本替換時(shí)大吃一驚,原來#include還可以這樣使用,類似這樣:
typedef enum { #include enum1, enum2,} test_enum;實(shí)際上就是test.h中包含了一系列可以放到enum中的名字而已,預(yù)編譯器在處理時(shí)會(huì)把test.h中的內(nèi)容在這一行展開,這樣編譯器拿到的就是完整的enum定義了。
如何查看預(yù)編譯器處理后的文件
一些好奇心強(qiáng)的同學(xué)可能會(huì)問那我們能不能看到預(yù)編譯器處理后的文件嗎?
答案是可以的。
假設(shè)使用的編譯器是gcc,那么使用-E選項(xiàng)就可以,-E選項(xiàng)告訴編譯器在處理源文件時(shí)不要編譯、不要匯編和鏈接,僅預(yù)處理。
$ gcc -E test.c使用上述命令就可以看到預(yù)處理后的文件是什么樣子的。
其它編譯器肯定也能找到類似的支持。
兩種使用頭文件的方式
你一定注意到了,其實(shí)#include有兩種寫法,一種是#include<>;另一種是#include “”,即:
#include #include "code.h"那么這兩種使用方法有什么區(qū)別嗎?
注意,知道這兩種用法背后的含義對(duì)于程序員來說是非常重要的。
預(yù)編譯器要想處理頭文件首先必須要能找到這個(gè)頭文件。
如果一個(gè)頭文件放到了<>中,那么預(yù)編譯器會(huì)在系統(tǒng)頭文件所在的路徑下開始找,在Linux下這個(gè)路徑是/usr/include,讓我們來看一下/usr/include這個(gè)文件夾下都有什么:
我們可以看到這里有很多頭文件,注意劃紅線的位置,原來我們常用的#include就放在了這里,現(xiàn)在終于解答一個(gè)困擾了我們很久的問題。
實(shí)際上這里存放的就是標(biāo)準(zhǔn)庫(kù)頭文件,關(guān)于標(biāo)準(zhǔn)庫(kù)參見《程序員應(yīng)如何理解標(biāo)準(zhǔn)庫(kù)》。
接下來就簡(jiǎn)單了,如果頭文件被放到了雙引號(hào)“”中呢?
很顯然只不過就是預(yù)編譯器搜索路徑不再是系統(tǒng)頭文件所在路徑了,而是以源文件所在位置開始查找,當(dāng)然不同的編譯器策略可能稍有差別。
當(dāng)在這些路徑中找不到include的頭文件時(shí)就會(huì)拋出錯(cuò)誤“fatal error: ***.h: No such file or directory”,這是程序員經(jīng)常遇到的錯(cuò)誤,現(xiàn)在你應(yīng)該知道怎么排查這類問題了吧。
為什么要使用頭文件
最后來回答一下為什么的問題,是啊,程序員為什么要使用頭文件這種東西呢?
還記得開始學(xué)編程時(shí)用的經(jīng)典的HelloWorld程序嗎?
#include int main() { printf("hello world\n"); return 0; }在這段簡(jiǎn)單的代碼中實(shí)際上如果我們不使用printf函數(shù)打印東西的話根本就不需要stdio.h,那么程序就變成了這樣:
int main() { return 0;}注意該代碼不依賴任何頭文件,不要懷疑,該代碼可以正確的編譯運(yùn)行。
如果程序員愿意的話可以把項(xiàng)目所有實(shí)現(xiàn)代碼都放到這個(gè)文件中,就像這樣:
void funA() { ...}void funB() { ...}int main() { ... funA(); funB();}該程序不依賴任何頭文件,所有實(shí)現(xiàn)代碼都放到了一個(gè)源文件,而對(duì)于現(xiàn)在稍微有規(guī)模的軟件項(xiàng)目其代碼量都在十幾萬、甚至上百萬,你能想象一個(gè)包含十幾萬行代碼的源文件是一種怎樣的場(chǎng)景嗎?
這樣的代碼有沒有可能寫出呢?
答案是有的,只要這個(gè)項(xiàng)目的程序員對(duì)于所有使用到的輪子都從頭到尾重復(fù)打造一遍,比如自己實(shí)現(xiàn)用到的標(biāo)準(zhǔn)庫(kù)中的函數(shù),比如printf,自己實(shí)現(xiàn)各種數(shù)據(jù)結(jié)構(gòu)等等等等。
而且這樣的項(xiàng)目有沒有辦法維護(hù)呢?
答案是有的,重賞之下必有勇夫,只要開出百萬年薪必然有人入坑。
因此,我們發(fā)現(xiàn)這樣寫代碼不但要重復(fù)造輪子還極其難以維護(hù)。
所以現(xiàn)在的軟件工程一項(xiàng)原則就是復(fù)用,能復(fù)用其它人的代碼絕不會(huì)自己重復(fù)寫一份。
那么問題來了,我們?cè)撛鯓邮褂闷渌藢懞玫拇a呢?
我們?cè)撜{(diào)用哪些函數(shù),這些函數(shù)的返回值是什么?參數(shù)是什么?
頭文件幫程序員解決了上述問題。
頭文件里仔仔細(xì)細(xì)的寫好了該模塊有哪些函數(shù)可供使用者調(diào)用、返回值是什么、參數(shù)是什么,但頭文件中并不會(huì)包含實(shí)現(xiàn),這是因?yàn)镃/C++語言不要求函數(shù)的聲明和實(shí)現(xiàn)必須呆在同一個(gè)地方。
因此你會(huì)看到,頭文件的作用其實(shí)是和我們常用的說明書沒什么區(qū)別。
現(xiàn)在問題就簡(jiǎn)單了,我們?cè)僖膊恍枰粋€(gè)包含幾十萬代碼的源文件了,程序員可以將其模塊化,各個(gè)團(tuán)隊(duì)負(fù)責(zé)一個(gè)模塊,每個(gè)模塊會(huì)編寫一些頭文件供其它人調(diào)用,同時(shí)每個(gè)模塊只寫一次,其它團(tuán)隊(duì)有需要可以直接使用,最后再把各個(gè)模塊組合起來,這樣大家各司其職又能最大程度實(shí)現(xiàn)代碼復(fù)用。
最后值得注意的一點(diǎn)就是,頭文件其實(shí)是讓編譯器知道該怎樣生成調(diào)用函數(shù)的機(jī)器指令,而真正將相關(guān)代碼打包到可執(zhí)行程序的是鏈接器,因此作為程序員不僅需要指定用哪些頭文件,還要指定頭文件中函數(shù)的實(shí)現(xiàn)代碼,也就是程序員常說的庫(kù)在哪里。
總結(jié)
現(xiàn)在大家應(yīng)該對(duì)頭文件有一個(gè)全面的認(rèn)知了吧,原來include只是告訴預(yù)編譯器在當(dāng)前位置展開頭文件,同時(shí)我們也知道了兩種include的使用方法及其區(qū)別,最后我們了解了為什么要發(fā)明頭文件這種技術(shù),希望這篇文章能幫你徹底理解頭文件。
總結(jié)
以上是生活随笔為你收集整理的c++ include 路径_程序员应如何理解include的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: css 图片换行_前端学习口诀VI:ht
- 下一篇: mysql通配符_mysql通配符进行模