<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 頭文件與之實現(xiàn)文件的的關(guān)系

          頭文件與之實現(xiàn)文件的的關(guān)系

          作者: 時間:2016-11-26 來源:網(wǎng)絡 收藏
          你理解簡單的含義嗎?

          關(guān)于兩者以前的關(guān)系,要從N年以前說起了~ long long ago,once aupon a time .......
          那是一個被遺忘的年代,在編譯器只認識.c(.cpp))文件,而不知道.h是何物的年代。
          那時的人們寫了很多的.c(.cpp)文件,漸漸地,人們發(fā)現(xiàn)在很多.c(.cpp)文件中的聲明語句就是相同的,但他們卻不得不一個字一個字地重復地將這些內(nèi)容敲入每個.c(.cpp)文件。但更為恐怖的是,當其中一個聲明有變更時,就需要檢查所有的.c(.cpp)文件,并修改其中的聲明,啊~簡直是世界末日降臨!
          終于,有人(或許是一些人)再不能忍受這樣的折磨,他(們)將重復的部分提取出來,放在一個新文件里,然后在需要的.c(.cpp)文件中敲入#includeXXXX這樣的語句。這樣即使某個聲明發(fā)生了變更,也再不需要到處尋找與修改了---世界還是那么美好!
          因為這個新文件,經(jīng)常被放在.c(.cpp)文件的頭部,所以就給它起名叫做“頭文件”,擴展名是.h.
          從此,編譯器(其實是預處理器)就知道世上除了.c(.cpp)文件,還有個.h的文件,以及一個叫做#include命令。

          本文引用地址:http://www.ex-cimer.com/article/201611/322063.htm

          雖然后來又發(fā)生很多的變化,但是這樣的用法一直延續(xù)至今,只是時日久遠了,人們便淡忘了當年的緣由罷了。
          提到了頭文件,就說說它的作用吧~
          想到了林銳GG寫的高質(zhì)量C/C++編程上頭文件的作用的簡短描述:
          (1)通過頭文件來調(diào)用庫功能。在很多場合,源代碼不便(或不準)向用戶公布,只要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調(diào)用庫功能,而不必關(guān)心接口怎么實現(xiàn)的。編譯器會從庫中提取相應的代碼。
          (2)頭文件能加強類型安全檢查。如果某個接口被實現(xiàn)或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規(guī)則能大大減輕程序員調(diào)試、改錯的負擔。


          預處理是編譯器的前驅(qū),作用是把存儲在不同文件里的程序模塊集成為一個完整的源程序.
          #include本身只是一個簡單的文件包含預處理命令,即為把include的后面文件放到這條命令這里,除此之外,沒有其它的用處(至少我也樣認為).


          我對乾坤一笑兄的觀點,十分贊同,基礎(chǔ)的東東一定要弄明白.
          我下面就乾坤一笑兄的例子做講,完備他的一些讓人迷惑不解的時候~

          例子:
          //a.h
          void foo();


          //a.c
          #include "a.h"//我的問題出來了:這句話是要,還是不要?
          void foo()
          {
          return;
          }

          //main.c
          #include "a.h"
          int main(int argc, char *argv[])
          {
          foo();
           return 0;
          }

          針對上面的代碼,請回答三個問題:
          a.c中的#include "a.h"這句話是不是多余的?
          1.為什么經(jīng)常見xx.c里面include對應的xx.h?
          2.如果a.c中不寫,那么編譯器是不是會自動把.h文件里面的東西跟同名的.c文件綁定在一起?
          3.第三個問題我給他改了一下:如果a.c中不寫include<>,那么編譯器是不是會自動把.h文件里面的東西跟同名的.c文件綁定在一起?

          下面是乾坤一笑的原話:

          從C編譯器角度看,.h和.c皆是浮云,就是改名為.txt、.doc也沒有大的分別。換句話說,就是.h和.c沒啥必然聯(lián)系。.h中一般放的是同名.c文件中定義的變量、數(shù)組、函數(shù)的聲明,需要讓.c外部使用的聲明。這個聲明有啥用?只是讓需要用這些聲明的地方方便引用。因為#include "xx.h"這個宏其實際意思就是把當前這一行刪掉,把xx.h中的內(nèi)容原封不動的插入在當前行的位置。由于想寫這些函數(shù)聲明的地方非常多(每一個調(diào)用xx.c中函數(shù)的地方,都要在使用前聲明一下子),所以用#include "xx.h"這個宏就簡化了許多行代碼——讓預處理器自己替換好了。也就是說,xx.h其實只是讓需要寫xx.c中函數(shù)聲明的地方調(diào)用(可以少寫幾行字),至于include這個.h文件是誰,是.h還是.c,還是與這個.h同名的.c,都沒有任何必然關(guān)系。
          這樣你可能會說:啊?那我平時只想調(diào)用xx.c中的某個函數(shù),卻include了xx.h文件,豈不是宏替換后出現(xiàn)了很多無用的聲明?沒錯,確實引入了很多垃圾,但是它卻省了你不少筆墨,并且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些聲明(.h一般只用來放聲明,而放不定義,參見拙著“過馬路,左右看”)也無害處,又不會影響編譯,何樂而不為呢?
          翻回頭再看上面的3個問題,很好解答了吧?
          它的解答如下:

          答:1.不一定。這個例子中顯然是多余的。但是如果.c中的函數(shù)也需要調(diào)用同個.c中的其它函數(shù),那么這個.c往往會include同名的.h,這樣就不需要為聲明和調(diào)用順序而發(fā)愁了(C語言要求使用之前必須聲明,而include同名.h一般會放在.c的開頭)。有很多工程甚至把這種寫法約定為代碼規(guī)范,以規(guī)范出清晰的代碼來。
          2.答:1中已經(jīng)回答過了。
          3.答:不會。問這個問題的人絕對是概念不清,要不就是想混水摸魚。非常討厭的是中國的很多考試出的都是這種爛題,生怕別人有個清楚的概念了,絕對要把考生搞暈。

          over!

          在此里要明確一點,編譯器是按照編譯單元進行編譯的,所謂的編譯單元,是指一個.c文件以及它所include的所有.h文件.最直觀的理解就是一個文件,一個工程中可以包含很多文件,其中有一個程序的入口點,即我們通常所說的main()函數(shù)(當然也可以沒有這個函數(shù),程序照樣能啟動,詳細見我的blog中).在沒有這個程序入口點的情況下,編譯單元只生成目標文件object file(.o文件,windows下叫做.obj).

          這個例子中總共包含了二個編譯單元,分別是a.c,main.c,按照我所說的,在編譯階段只是生成各自的.o文件.這個階段不和其它的文件發(fā)生任何的關(guān)系.
          而include這個預處理指令發(fā)生在預處理階段(早先編譯階段,只是編譯器的一個前驅(qū)處理程序).


          .h .c不見得是浮云,脫離了編譯器談這些沒有任何的意義,拋開更深層次的這些,比如說,OS如何啟動這個文件,PE結(jié)構(gòu)(linux下為elf)等等
          編譯器首先要識別這個文件才可能去編譯它,這是前提.如果你改了它的擴展名那么你的編譯器還能認識它嗎~上升到一個更高的層次上看待這個問題,XX兄說的也不錯~我想XX兄說的意思就是兩者不可因為名字相同就認為兩者有什么關(guān)系,名字是可以隨便的~
          兩者之間的聯(lián)系,我在前面說過了,是由于歷史的原因造成的,再加上人的習慣,我想誰也不想多去記那么多文件名吧.(拿我舉個例子,一個數(shù)
          據(jù)表如果多于30個字段,我就覺得頭大了,現(xiàn)在弄的表有的多達上百個字段,真希望那位高人研究出什么好的方法來~,也讓我們的世界美好一些~)

          乾坤一笑的第三個問題很有代表性,多次在網(wǎng)上看到,現(xiàn)在的編譯器絕對沒有那么智能,而且也沒有必須那么做.下面我們主要聊聊編譯器的處理過程.(我想初學者有疑問的正在于此,即是對于編譯過程.h .c(.cpp)的變化不太了解,)

          下面我說舉個簡單的例子來聊聊~
          例子如下:
          //a.h
          classA
          {
          pubic:
          intf(intt);
          };

          //a.cpp
          #include"a.h"
          intA::f(intt)
          {
          returnt;
          }

          //main.cpp
          #include"a.h"
          voidmain()
          {
          Aa;
          a.f(3);
          }
          在預處理階段,預處理器看到#include "文件名"就把這個文件讀進來,比如它編譯main.cpp,看到#include"a.h",它就把a.h的內(nèi)容讀進來,它知道了,有一類A,包含一個成員函數(shù)f,這個函數(shù)接受一個int型的參數(shù),返回一個int型的值。再往下編譯很容易就把Aa這行讀懂了,它知道是要拿A這個類在棧上生成一個對象。再往下,它知道了下面要調(diào)用A的成員函數(shù)f了,參數(shù)是3,由于它知道這個函數(shù)要一個整形數(shù)用參數(shù),這個3正好匹配,那就正好把它放到棧上,生成一條調(diào)用f(int)函數(shù)的指令(一般可能是一句call),至于這個f(int)函數(shù)到底在哪里,它不知道,它留著空,鏈接時再解決。它還知道f(int)函數(shù)要返回一個int,所以也許它也為這一點做好了準備(在例子中,我們沒用這個返回值,也許它就不處理)。再往下到文件末尾了main.cpp編譯好了,生成了main.obj。整個編譯過程中根本就不需要知道a.cpp的內(nèi)容。
          同理,編譯器再編譯a.cpp,把f()函數(shù)編譯好,編譯a.cpp時,它也不用管別的,把f()編譯好就行了。生成了a.obj。
          最后一步就是鏈接的階段了,鏈接器把項目中所有.cpp生成的所有.obj鏈接起來,
          在這一步中,它就明確了f(int)函數(shù)的實現(xiàn)所在的地址,把main.obj中空著的這個地址位置填上正確的地址。最終生成了可執(zhí)行文件main.exe。

          明白了嗎?不明白那就多說幾句了,我們在學編譯原理的時候都知道,編譯器是分階段進行的,每一個階段將源程序從一種表示轉(zhuǎn)換成另一種表示,一般情況下都進行如下順序:源程序->詞法分器->語法分析器->語義分析器->中間代碼生成器->代碼優(yōu)化器->代碼生成器->目標程序.
          其中這中間6項活動都要涉及的兩項主要活動是:符號管理器與錯誤處理器.
          歸根原因,這里有一個叫做符號表的東東在里面讓你著魔一樣不明白,其實符號表是一個數(shù)據(jù)結(jié)構(gòu).編譯器的基本一項功能就是要記錄源程序中使用的標識符并收集與每個標識符相關(guān)的各種屬性信息.屬性信息表明了該標識符的存儲位置/類型/作用域(在那個階段有效)等信息,通俗的說一下就是,當編譯器看到一個符號聲明時,例如你的函數(shù)名它就會把它放到這個符號表中去登記一下~符號表里存放著你的函數(shù)的入口地址,參數(shù)個數(shù),返回信息等等一堆東西~而在聯(lián)接階段主要是處理工程中的符號表與調(diào)用對應處理關(guān)系,即我們通常所說的解引用.
          經(jīng)過前面的,不知明白與否?

          最后引用一下XXX兄的結(jié)尾三點:
          搞清楚語法和概念說易也易,說難也難。竅門有三點:
          1.不要暈著頭工作,要抽空多思考思考,多看看書;
          2.看書要看好書,問人要問強人。爛書和爛人都會給你一個錯誤的概念,誤導你;
          3.勤能補拙是良訓,一分辛苦一分才;


          關(guān)鍵詞: 頭文件實現(xiàn)文

          評論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();