1. 程式人生 > >include怎麼關聯標頭檔案?標頭檔案如何關聯原始檔?標頭檔案被重複包含的危害?怎麼防止重複包含?

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 的不都是標頭檔案嘛  ,而標頭檔案裡都是 函式,變數的宣告  沒有寫函式, 變數的具體怎麼實現,也就是定義.     

編譯的時候,並不會去找b.cpp檔案中的函式實現,只有在link的時候才進行這個工作。我們在b.cpp或c.cpp中用#include “a.h”實際上是引入相關宣告,使得編譯可以通過,程式並不關心實現是在哪裡,是怎麼實現的。

然後是彙編 ,彙編就是  彙編器 把彙編程式碼 轉為二進位制  機器程式碼

再是連結

 

在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方式卻不受一些較老版本的編譯器支援,換言之,它的相容性不夠好。也許,再過幾年等舊的編譯器死絕了,這就不是什麼問題了。