要明白的幾個概念: 1、編譯:編譯器對源文件進行編譯,就是把源文件中的文本形式存在的源代碼翻譯成機器語言形式的目標文件的過程,在這個過程中,編譯器會進行一系列的語法檢查。如果編譯通過,就會把對應的CPP轉換成OBJ文件(其實編譯好像只是生成一個中間文件,再由匯編器生成obj文件)。 2、編譯單元:根據C++標准,每一個CPP文件就是一個編譯單元。每個編譯單元之間是相互獨立並且互相不可知。 3、目標文件:由編譯所生成的文件,以機器碼的形式包含了編譯單元裡所有的代碼和數據,還有一些期他信息,如未解決符號表,導出符號表和地址重定向表等。目標文件是以二進制的形式存在的。 我打算以例子來分析編譯鏈接: 例子一: A.cpp: [cpp] <span style="font-size:18px;">int a</span> B.cpp: [cpp] <span style="font-size:18px;">int a</span> 當這兩個放在一起編譯的時候,均沒問題(因為編譯是單獨編譯,彼此沒有聯系),但是鏈接的時候會出現問題: B.obj : error LNK2005: "int a" (?a@@3HA) already defined in A.obj 分析:兩個a均為全局變量,並且編譯器會幫我們自動給它進行初始化為0,相當於進行了定義,而不僅僅是聲明,在編譯的時候,他們都會放入到.obj文件的符號表中,執行鏈接時,兩個.obj文件的導出符號表均有a這個符號,所以會報錯。大家注意到上面紅體字會發現:定義和聲明是不一樣的。如果把其中之一改成聲明,那就OK了。 如:A.cpp:extern int a; 這個extern關鍵字就是告訴編譯器n已經在別的編譯單元裡定義了,在這個單元裡就不要定義了。a其實是放入到A.obj的“未解決符號表”,也就是unresolved symbol table。當鏈接成.exe文件時,只有全部的unresolved symbol table中的符號都能在別的編譯單元中的導出符號表中找到,鏈接才能通過。 和這個例子相似的是在一個編譯單元中聲明函數並使用函數,但是在另一個編譯單元中定義該函數。 例子二: A.cpp: [cpp] <span style="font-size:18px;">static int a;</span> B.cpp: [cpp] <span style="font-size:18px;">static int a;</span> 你會發現這麼鏈接也沒問題。 分析:如果該關鍵字位於全局函數或者變量的聲明前面,表明該編譯單元不導出這個函數或變量,因些這個符號不能在別的編譯單元中使用(內部鏈接)。如果是static局部變量,則該變量的存儲方式和全局變量一樣,但是仍然不導出符號。 內部鏈接和外部鏈接的區別:看這個符號是不是寫入到.obj文件中。 對於const變量,默認內部鏈接。所以你可以像寫static變量那樣定義const變量,但必須進行初始化。 例子三: [cpp] <span style="font-size:18px;">int a; void func() { int a; }</span> 大家都會寫出這樣的代碼。而且非常正確。 分析:全局變量a在前面解釋過,放入到導出符號表中,但是局部變量a呢?它由棧管理,並不放入導出符號表中。 下面來分析一個有難度的:頭文件和實現文件 例子四: c.h: [cpp] <span style="font-size:18px;">class A { public: const int a; static int b; public: A(int num):a(num){} void test(); };</span> 在這裡探討const成員沒有任何意義,只是為了熟悉類中const成員變量和引用成員變量(intialization list中定義)。 先來看static int b吧。這只是一個聲明(內部鏈接)。需要在別的文件中進行定義,但這個定義是外部鏈接。什麼意思呢? A.cpp: [cpp] <span style="font-size:18px;">int A::b=1;</span> B.cpp: [cpp] <span style="font-size:18px;">int A::b=2;</span> 執行鏈接的時候會報錯。重復定義A::b。 再來看看void test(),一般我們都是在別的文件中實現這個方法,如: d.cpp: [cpp] <span style="font-size:18px;">void A::test() { ...... }</span> 如果我們需要用到A的話,加入#include "c.h"就可以了。但一切來的這個簡單的原因是什麼呢? 分析:頭文件只是一個聲明,聲明一個類A,類A中聲明一系列東西。d.cpp文件對test()方法進行了定義,test符號寫入到d.obj導出符號表中。別的文件使用A a;a.test()就可以鏈接到了,不會報錯。 大家都知道A.cpp :#include "c.h"代表什麼意思:編譯的時候c.h中內容出現在A.cpp中,因為此時頭文件中的東西是內部鏈接(沒有定義),所以在B.cpp:#include "c.h"是不會出現編譯鏈接問題的。 但如果我們不把實現放入到別的文件中呢?而是放入到頭文件中呢?如: [cpp] <span style="font-size:18px;">class A { public: const int a; static int b; public: A(int num):a(num){} void test(); }; void A::test() { cout<<"A類鏈接過來了..."<<endl; }</span> A.cpp :#include "c.h" B.cpp:#include "c.h" 那麼這樣就會出現問題了。因為定義是外部鏈接, A.cpp中包含了test,B.cpp中也包含了test : B.obj : error LNK2005: "public: void __thiscall A::test(void)" (?test@A@@QAEXXZ) already defined in A.obj 解決辦法:將其聲明為內聯函數inline void test(); 並且內聯函數要定義在頭文件中,因為編譯時編譯單元之間互不知道,如果內聯被定義於.cpp文件中,編譯其他使用該函數的編譯單元的時候沒有辦法找到函數的定義,因些無法對函數進行展開。所以如果內聯函數定義於.cpp裡,那麼就只有這個.cpp文件能使用它。 通過對編譯鏈接的學習,才對那些.lib,.dll文件有了進一步的認識,為什麼要這些東西。這些文件中實際上就是我們頭文件中聲明的函數定義等,我們僅僅引入頭文件是不行的,找不到定義也完全沒用。那麼api基本上都是通過靜態庫和動態庫的方式提供給我們。 下面我們來看看模版編譯問題: 讓我們從一個簡單的例子開始吧: 2.h: [cpp] template<class T> class A { public: int test(T a); }; 3.cpp: [cpp] #include<iostream> #include "2.h" template<class T> int A<T>::test(T a) { std::cout<<"A test "<<a<<std::endl; return a; } 1.cpp: [cpp] #include<iostream> #include "2.h" void main() { A<int> a; a.test(4); } 很遺憾的告訴你,鏈接出現問題: [plain] view plaincopy 1.obj : error LNK2001: unresolved external symbol "public: int __thiscall A<int>::test(int)" (?test@?$A@H@@QAEHH@Z) 找不到test的定義?怎麼會呢?我們一般的類不都是這樣寫的麼?注意這裡是模版類,和一般類編譯是不一樣的。 分析:1.cpp中肯定是有test()的聲明,尋找test()的定義是在鏈接的時候發生的。那可以肯定的是3.obj中肯定沒有test()的定義<----這一切都是我們推出來的。那為什麼沒有test()的定義呢? 根據C++標准,當一個模板不被用到進它就不應該被具體化-----什麼意思呢? 3.cpp裡面如果沒有用到A<int>::test()的話,A<int>::test()函數的二進制代碼就不會被編譯到3.obj文件中去。 如果還是不明白的話,可以來實踐一下: 3. cpp: [cpp] #include<iostream> #include "2.h" template<class T> int A<T>::test(T a) { std::cout<<"A test "<<a<<std::endl; return a; } A<int> a; int g_a=a.test(4); 你再運行一下,會發現什麼問題都沒有了。。這下肯定理解了那句話是什麼意思了吧。 你現在會抱怨了,這多不專業啊!其實我們可以采用包含編譯模式: 將模版定義也放在類中: 2.h: [cpp] #include<iostream> template<class T> class A { public: int test(T a); }; template<class T> int A<T>::test(T a) { std::cout<<"A test "<<a<<std::endl; return a; } 這有點像我們的inline函數。 編譯鏈接就OVER了。。