include怎麼關聯標頭檔案?標頭檔案如何關聯原始檔?標頭檔案被重複包含的危害?怎麼防止重複包含?
標頭檔案(.h)的功能是一般用來進行宣告的(函式,類或變數引用,巨集定義)。
原始檔(.cpp)是用來進行定義的(函式,類定義,變數定義)。
#include 是在編譯器進行編譯之前,即在預編譯時把它後面所寫的那個標頭檔案的內容,完完整整地、 一字不改地包含到當前的原始檔中來。
1.原始檔如何根據#include來關聯標頭檔案
A) #include <>
B) #include “xxx.h”
使用者自定義的檔案用雙引號括起來,編譯器首先會在使用者目錄下查詢,然後在到C++安裝目錄(比如VC中可以指定和修改庫檔案查詢路徑,Unix和Linux中可以通過環境變數來設定)中查詢,最後在系統檔案中查詢。
2. 標頭檔案如何來關聯原始檔
這個問題實際上是說,已知標頭檔案“a.h”聲明瞭一系列函式,“b.cpp”中實現了這些函式,那麼如果我想在“c.cpp”中使用“a.h”中宣告的這些在“b.cpp”中實現的函式,通常都是在“c.cpp”中使用#include “a.h”,那麼c.cpp是怎樣找到b.cpp中的實現呢?
把下面的總結一哈:
https://blog.csdn.net/speargod/article/details/84342992
一個工程生成.exe檔案需要經歷 編譯和連結 兩大步
而編譯又可以分為 預處理 編譯 彙編
預處理就是把 #include 這些玩意的程式碼 copy替換進來
編譯就是 編譯器對經過預處理之後的每一個原始檔 進行語法 詞法分析 檢查一哈程式碼對不對 不對就報錯, 對就把程式碼優化一下生成組合語言的程式. 注意 原始檔 #include 的不都是標頭檔案嘛 ,而標頭檔案裡都是 函式,變數的宣告 沒有寫函式, 變數的具體怎麼實現,也就是定義.
然後是彙編 ,彙編就是 彙編器 把彙編程式碼 轉為二進位制 機器程式碼
再是連結
在link的時候,需要在makefile裡面說明需要連線哪個.o或.obj檔案(在這裡是b.cpp生成的.o或.obj檔案),此時,聯結器會去這個.o或.obj檔案中找在b.cpp中實現的函式,再把他們build到makefile中指定的那個可以執行檔案中。
不過在VS中,一幫情況下不需要自己寫makefile,只需要將需要的檔案都包括在project中,VC會自動幫你把makefile寫好。 通常,編譯器會在每個.o或.obj檔案中都去找一下所需要的符號,而不是隻在某個檔案中找或者說找到一個就不找了。因此,如果在幾個不同檔案中實現了同一個函式,或者定義了同一個全域性變數,連結的時候就會提示“redefined”。
連結就是把.obj檔案連結在一起形成可執行檔案 .exe
我自己想哈 一個工程下面可以有很多原始檔 但是 只能有一個原始檔下 有那個main 函式 然後 生成可執行檔案的過程就是 把那個有那個main函式的原始檔main.cpp 裡面用到的函式啊 變數啊 都在 別的 .obj檔案裡 找到 然後把找到的.obj 都跟 main.obj 搞到一起 生成 .exe
其實.cpp和.h檔名稱沒有任何直接關係,很多編譯器都可以接受其他副檔名。比如偶現在看到偶們公司的原始碼,.cpp檔案由.cc檔案替代了。
在Turbo C中,採用命令列方式進行編譯,命令列引數為檔案的名稱,預設的是.cpp和.h,但是也可以自定義為.xxx等等。 譚浩強老師的《C程式設計》一書中提到,編譯器預處理時,要對#include命令進行“檔案包含處理”:將file2.c的全部內容複製到#include “file2.c”處。這也正說明了,為什麼很多編譯器並不care到底這個檔案的字尾名是什麼----因為#include預處理就是完成了一個“複製並插入程式碼”的工作。 編譯的時候,並不會去找b.cpp檔案中的函式實現,只有在link的時候才進行這個工作。我們在b.cpp或c.cpp中用#include “a.h”實際上是引入相關宣告,使得編譯可以通過,程式並不關心實現是在哪裡,是怎麼實現的。原始檔編譯後成生了目標檔案(.o或.obj檔案),目標檔案中,這些函式和變數就視作一個個符號。在link的時候,需要在makefile裡面說明需要連線哪個.o或.obj檔案(在這裡是b.cpp生成的.o或.obj檔案),此時,聯結器會去這個.o或.obj檔案中找在b.cpp中實現的函式,再把他們build到makefile中指定的那個可以執行檔案中。
在Unix下,甚至可以不在原始檔中包括標頭檔案,只需要在makefile中指名即可(不過這樣大大降低了程式可讀性,是個不好的習慣哦^_^)。在VC中,一幫情況下不需要自己寫makefile,只需要將需要的檔案都包括在project中,VC會自動幫你把makefile寫好。 通常,編譯器會在每個.o或.obj檔案中都去找一下所需要的符號,而不是隻在某個檔案中找或者說找到一個就不找了。因此,如果在幾個不同檔案中實現了同一個函式,或者定義了同一個全域性變數,連結的時候就會提示“redefined”。
3. 標頭檔案被重複包含的危害
https://www.zhihu.com/question/26872913
A) 當檔案中有對變數的定義時,多次包含該檔案這個變數就會被多次定義,這就會報錯。
如下: 當檔案中有一個對變數的定義
int value = 10;
當多次包含該檔案時,就變成了
int value = 10;
int value = 10;
··· ···
int value = 10;
這樣就會出錯。
B) 標頭檔案中有定義static變數,就會在每個檔案中產生靜態變數,邏輯就亂了。
4.怎麼防止重複包含
方式一:
#ifndef COMPARE_H
#define COMPARE_H
.......... //標頭檔案具體宣告語句
#endif
方式二:
#pragma once
... ... //標頭檔案具體宣告語句
第一種方式就是定義一個巨集,巨集名一般取標頭檔案名 比如標頭檔案叫compare.h 那就叫 COMPARE_H
這種巨集是為了防止一個編譯單元(cpp檔案)重複包含同一個標頭檔案。它在預處理階段起作用,前處理器發現a.cpp內已經定義過A_H 這個巨集的話,在a.cpp中再次發現#include "a.h"的時候就不會把a.h的內容替換進a.cpp了。
而當你寫成
#ifndef XXX #define XXX
int a=1;
#endif
包含兩次就是
#ifndef XXX #define XXX
int a=1;
#endif
#ifndef XXX #define XXX
int a=1;
#endif
第一次中,由於沒有定義XXX,所以做了兩件事,定義XXX,然後int a;
第二次中,由於已經定義XXX,所以啥都不做
而“#pragma once”是預處理階段的,保證一個cpp中一個這個類,防止重複匯入同一個類,但是你是多了cpp當然多次匯入了,每個cpp單獨工作生成,就是有限聯絡而已
是一個比較常用的C/C++雜注,只要在標頭檔案的最開始加入這條雜注,就能夠保證標頭檔案只被編譯一次。
“#pragma once”的用處就是:
ww.h
#pragma once
const int s = 12;
然後你再ww.cpp中
#include "stdafx.h"
#include "ww.h"
#include "ww.h"
#include "ww.h"
#include "ww.h"
沒有#pragma once的話,s會重定義 多次,有了#pragma once就保證標頭檔案只被編譯一次,就只有一個s。
比較
#ifndef的方式依賴於巨集名字不能衝突,這不光可以保證同一個檔案不會被包含多次,也能保證內容完全相同的兩個檔案不會被不小心同時包含。當然,缺點就是如果不同標頭檔案的巨集名不小心“撞車”,可能就會導致標頭檔案明明存在,編譯器卻硬說找不到宣告的狀況。
#pragma once則由編譯器提供保證:同一個檔案不會被編譯多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。帶來的好處是,你不必再費勁想個巨集名了,當然也就不會出現巨集名碰撞引發的奇怪問題。對應的缺點就是如果某個標頭檔案有多份拷貝,本方法不能保證他們不被重複包含。當然,相比巨集名碰撞引發的“找不到宣告”的問題,重複包含更容易被發現並修正。
方式一由語言支援所以移植性好,方式二 可以避免名字衝突
#pragma once方式產生於#ifndef之後,因此很多人可能甚至沒有聽說過。目前看來#ifndef更受到推崇。因為#ifndef受語言天生的支援,不受編譯器的任何限制;而#pragma once方式卻不受一些較老版本的編譯器支援,換言之,它的相容性不夠好。也許,再過幾年等舊的編譯器死絕了,這就不是什麼問題了。