為什麼c++要「在頭文件中聲明,在源文件中定義」?
就是這樣,不是很理解這段話:
另外請大神們給小弟解釋一下c++工程中不同的.cpp文件和.h文件相互之間的關係以及調用的原理,感激不盡!
因為如果你在頭文件裡面寫普通函數的實現,那麼這個實現就會被#include給複製到不同的cpp文件裡面,到時候就變成了你的exe裡面一個函數重複實現了若干次,這顯然是不行的。
但是C++的類除外,類默認是inline的,而且人類有義務保證這個類在每一個cpp裡面看到的東西都是一樣的。所以如果你在兩個cpp裡面,寫了兩個名字一樣的類,函數名也一樣,只是實現不一樣,那麼編譯是能夠通過的。只是C++編譯器完全可以在每一次調用這個類的時候,隨機挑選不同的實現來運行。
但是在正常情況下,我們的不同的cpp看到的同一個類都是一樣的(所以要注意你的頭文件不要被各種#ifdef干),所以隨機挑選哪一個都是一樣的,因此沒有任何問題(逃
因為C沒有模塊的概念,編譯結果裡面沒有編程介面。如果你想讓你的內容模塊化,就只能把編程介面寫在一個文件里,並稱之為頭文件。
沒人強迫你這麼做,只是為了看得清楚,你把所有代碼都寫進一個文件也是可以的
另外順便說模板的定義和實現不能分開,只能寫進同一個地方首先,我來回答末尾的「 .cpp文件和.h文件相互之間的關係」。
講到.h就必然會提到#include。#include是什麼?一條預處理指令。於是你就需要搞清楚預處理在c++程序的編譯過程中大約發生在什麼環節。以下引用 cppreference.com:
The preprocessor is executed at translation phase 4, before the compilation. The result of preprocessing is a single file which is then passed to the actual compiler.
c++的預處理器在編譯之前執行,它看到#include指令,就會把那個文件的內容替換到當前位置。其它的預處理指令例如#define, #ifdef, ##等也在這個階段被執行、併產生相應的內容。
預處理器執行完成後,所有的預處理指令都會被移除。其結果是一個單個頭的大文件(我猜測這文件只存在於內存里),這個文件才會被進一步傳給編譯器做編譯。
搞清楚這一點很重要。例如為什麼有人說,頭文件多了拖慢編譯速度。因為一個.h被多個cpp include,每個cpp編譯的時候都要重新編譯一遍這個.h啊。所以VS允許預編譯頭文件,也有些別的編譯器支持類似功能。
其次,為什麼要把函數、全局變數的聲明放到頭文件里,把定義放在cpp里呢?
因為c++發明那會兒考慮到與c的兼容性,c的一些規則也被帶了過來,比如先聲明後使用。因此,如果你定義了一個函數:
return_type function(parameter_type param1, ...) { ... }
你要在別的cpp里使用,你必須先聲明一下:
return_type function(parameter_type param1, ...);
這主要是告訴編譯器:我知道這麼個函數,它簽名長這樣,它的定義在別的地方。現在我可以用它了吧?
然後問題來了:假如你這個函數到處都要用到,你總不能每個cpp里貼上這麼一行吧。
答案自然就是把它放在頭文件里咯。這就是你書里那句話的意思。
至於cpp自己發明的那套,class什麼的,又不完全遵守c的那一套了。比如class里的function A調用B,並不要求B必須寫在A前面。
以上。
一個事情,下次貼圖麻煩把圖正過來,簡直治好多年頸椎病。。。
因為C這麼做,所以C++就得這麼做....
會被包含到各個cpp里,由於函數有外部鏈接性,在鏈接的時候會因為重定義報錯。編譯沒事,鏈接有問題。
從現代的角度來看,這種設計是傻逼的,而且大多數新發明的語言都沒有什麼頭文件源文件這種亂七八糟的概念。但因為C++是一門很老的語言,為了繼續支持當年那些老不死的代碼,只能把這些東西保留到現在。
所以沒辦法,抖m吧,騷年(手動滑稽)
要從幾個部分解釋一下。
第一,預編譯指令#include的作用是將所包含的文件全文複製到#include的位置,相當於是個展開為一個文件的宏。
第二,C++允許多次聲明,但只允許一次實現。比如int foo();就是一次聲明,而int foo(){}就是一次實現。
如果編譯時有多個.cpp文件中#include了同一個含有函數實現的.h,這時候鏈接器就會在多個目標文件中找到這個函數的實現,而這在C++中是不允許的,此時就會引爆LNK1169錯誤:找到一個或多個重定義的符號。
因此為了讓函數可以在各個.cpp中共享,正確的做法就是在.h中只聲明函數,並在另【 一個(重點)】.cpp中實現這個函數。這樣就不會衝突了。碰巧最近也在研究這個問題,來說說自己的一點見解(純屬個人體會)
1.便於查找使用。在一個項目中,會涉及到許多函數,定義與聲明分開可以更快的找到所需要的函數(別說看文檔,效率太低),同時定義可能需要成千上萬行代碼,而聲明只需要幾十行,這意味著等待代碼載入的時間大大縮短了。(這一點感觸最深刻,10MB+的源代碼,在手機上載入直接未響應,自此我徹底認同這種做法了。)
2.為閉源提供方便。只提供介面,不提供函數具體定義。
3.便於團隊合作,每個人只用關心自己負責的部分即可,寫完後提供介面,別人直接用就好了。
4.同一個聲明,可以換上不同的定義(編譯時選擇不同的文件),便於快速比較演算法性能等神奇的用法。
同時如果關鍵參數也在放在小巧的頭文件中,意味著改參數再也不用等上個數分鐘載入龐大的源文件,不用從數千個函數中尋找數百個參數中的某幾個參數。
以上都是我最近幾天體會出來的,不保證一定對,如果有誤,請多多指教,感激不盡。
怎麼寫都可以,你可以寫個hpp,把聲明都寫在裡面,cpp里再寫實現,也完全可以不要hpp,全都寫在cpp裡面。前者的好處是方便查看聲明,你可以把注釋都寫在hpp裡面,告訴用戶怎麼調用函數。後者的好處是……寫的東西少一點。我個人還是喜歡後者。
其實只是一種編碼規範而已,並不是不能放在一起。頭文件主要用於對外聲明,告訴用戶你的類實現了什麼功能,提供了什麼介面,可以讓其它程序調用和對接。源文件就是具體的實現代碼,這部分是不對外展示的,而只是提供編譯好的庫文件。開源軟體就別拿來說了,那是別人自願給你展示源代碼而已。
知乎的攻城獅們是從來不和人共同開發嗎?頭文件聲明後給別人在他的code里調用,然後別人鏈接不需要源碼的lib啊?!
同意某答主 說的 這是個傻逼到極點的設計,
但是老的東西嘛,我可以理解,最可恨的是,C++那幫人也沒想 做出任何改變。
目測是 還想用到死的節奏。
其實只要能夠兼容老代碼不就好了。新的代碼完全沒必要啊。
至於有些人說的 沒有模塊化 不方便交互,這啥年代了?這點支持還不是小意思?
C++ 也就這樣了, 就等一個新的語言來代替之。只要能夠有良好的C C++交互能力 ,例如 Kotlin native。
大致瀏覽了大家的回答,簡單說說我的想法:
首先了解一下C++的編譯原理,有很多規則需要遵循,比如說兩個class相互依賴,需要怎麼做才能讓編譯器通過,於是乎出現了"class A;"這種語法。同樣頭文件中聲明也是一樣的道理,說白了就是要遵循規則。有很多人可能說,不一定,我經常在頭文件裡面寫類函數的定義或者inline函數也沒問題啊。從語法角度來說,不可否認只要編譯過怎麼做都可以。同樣,我們可以對比兩種做法的優缺點:
(1)源碼修改
定義在頭文件, 所有包含該頭文件的cpp都需要重新編譯
定義在源文件,只有被修改的源文件被編譯
所有在vc的stdafx文件或者一些注釋當中會說明, 儘可能避免頭文件改動。
(2)源碼閱讀
定義在頭文件,穿插函數的定義的函數聲明列表
定義在源文件,清晰的函數聲明列表
對比一下就知道那種更容易閱讀了。
暫且到此結束,有空繼續噴!
C++是一門那麼實在的語言,又沒有VM那些東西,編譯過程又那麼直白,預處理就是字元串複製粘貼替換,當然需要額外的機制來保證程序員不亂搞。頭文件和實現文件地位是不同的,了解一下翻譯的過程你會發現,當代碼編譯到庫之後,名字要靠頭文件引入來指導翻譯,實現文件隔離出來也方便分發。
大家是不是有點無腦奶輪子哥,明明回答的一般卻有那麼多贊,我今天學習也查了這個問題好久,後面有些答案解釋的很不錯,但沒什麼人贊。我不是針對輪子哥,我挺崇拜他的。
在我看來,cpp並不需要嚴格地遵從在頭文件中聲明,源文件中定義這一最標準的準則!完全可以在頭文件中進行申明和定義(不僅僅是模版類,內聯函數,另外數據結構,類中類等等它們的聲明和定義都可以在頭文件中進行;另外,也可以完全在源文件中進行申明定義,然後別的模塊去include這個源文件!唯一需要保證的是,在一個工程的link階段,函數或者變數只有一次定義!另外寫cpp的時候,頭文件也不必要用.h來作為後綴,源文件也不必非要用cpp為後綴!cpp和h文件的分離最大的目的,在我看來是為了便於組織管理,以及方便二次開發時的調用!
我不是來回答的、我想說這個問題問得好,問出來大多初學者的疑惑
說白了這就是無腦 include 的原罪!
因為這個語言設計比較落後
推薦閱讀: