咱們知道編譯的時候(假如編譯器是VS),是以源文件cpp文件爲單位,編譯成一個個的obj文件,而後再經過連接器把不一樣的obj文件連接起來.ios
簡單的說,若是一些變量或函數的定義是內鏈接的話,連接器連接的時候就不會拿它們去與obj比較看有重複定義不,一個源文件中的extern聲明的變量或函數也不能使用另一個源文件中的內鏈接的變量或函數.而若是是外鏈接的話則須要在不一樣的obj中比較是否有重定義的.除了作這樣的檢查外,連接器還會查看經過extern修飾的變量或函數聲明在其餘obj中的定義.c++
咱們知道C++支持多種編程範式,能夠徹底用採用面向過程,不去用類,固然了不多有人這樣作,通常是結合面向過程與面向對象.編程
那假若有一些處在不一樣源文件中的變量和函數(不是類中的變量成員或成員函數),咱們要使用不一樣源文件中的變量或函數時咋整呢? 在面向對象中咱們使用一個個類的時候天然是用頭文件引用下就OK.但如今沒有類,只是一個個變量和函數,也能用引用頭文件嗎 ? 這得看狀況,有時能夠,有時不行.若是頭文件中只有外部聲明沒有任何定義,那引用頭文件徹底沒有問題.若是頭文件中有定義的話,若是隻被一個cpp文件引用則沒問題,若是被多個cpp文件引用就會出現重複定義的錯誤.(注:編譯器是以cpp文件爲單位編譯,若是某個h頭文件沒有被引用的話至關於被拋棄不用了.引用頭文件時預編譯時只是簡單的把頭文件複製到引用它的cpp文件中.)函數
假如在有one.cpp和two.cpp兩個源文件.spa
//one.cpp中.net
---------------------------------------------------設計
#include <iostream>對象
using namespace std; blog
int number = 123; //number的定義 .或者寫成extern number = 123;當有賦值時,實際上extern失去了應有的做用.因此加不加沒影響.ip
void Print() {cout<<"hi,i am one.cpp"<<endl;}
//two.cpp中
--------------------------------------------------------
#include <iostream>
using namespace std;
extern int number;//這就是所謂的外部聲明,此處extern不可省.另外此處絕對不能賦值.若是寫成extern int number = 88;會報錯是重複定義.
extern void Print(); //此處extern能夠省略.
cout<<number; //結果爲123
Print(); //輸出i am one.cpp
在two.cpp中是怎麼獲得one.cpp中的number的值的呢,因爲用extern int number這樣聲明瞭下,代表number在其餘源文件中有定義,連接器就會幫助去其餘源文件中找的.
假如把上面two.cpp中的extern關鍵字去掉. 編譯時不會出錯.但連接時出錯了,重複定義了.由於one.cpp中已經定義了個number,不能再定義一個相同的了.
上面的例子中咱們知道one.cpp和two.cpp中同時寫上int number會出錯,說重複定義了.但若是這樣
//one.cpp中
static int number = 123;
//two.cpp中
static int number; //沒顯式賦值,會默認賦予0
此時卻不會出錯.由於定義變量時默認是外部鏈接的.而加上關鍵字static表示是靜態變量,是內部鏈接,連接器不會去看不一樣cpp編譯成的obj文件中有重名的靜態變量不.
當用static修飾後就不能再使用extern修飾了.
//one.cpp中
static int number = 123;
//two.cpp中
extern int number;
cout<<number;
此時會出錯,由於extern聲明的number找不到定義.由於one.cpp的number用static修飾代表是內鏈接了.
//one.cpp中
const int number = 123;
//two.cpp中
const int number = 321;
這裏達到的效果與static同樣,都屬於內部鏈接,因此不會出錯.惟一不一樣的時const表示常量,定義時必須顯式賦予值,且賦值後不能再改變它的值.
不過const還有另一個特性就是能夠和extern一塊兒用.
好比在two.cpp中這樣寫
extern const int number;
cout<<number; //運行會報錯,會說找不到定義.須要把one.cpp中改成extern const int number = 123;才行.
//正確輸出的值是one.cpp中的number值123
//one.cpp中
void Test() { }
//two.cpp中
void Test() { }
這樣編譯時會報錯,重複定義了.但若是把上面的兩個void Test都改爲inline void Test() { }或者static void Test() { }則不會出錯.//注意這裏講的inline函數指的是全局函數,不是類裏面的inline函數.
因此函數跟通常變量差很少.沒任何修飾的就默認是外部鏈接,有static修飾的則是內部鏈接.另外沒有const函數這一說,只有在類中才能夠在函數後面加個const來修飾
轉自 http://blog.csdn.net/weiwenhp/article/details/8598342
類定義總有內部鏈接,而非inline類成員函數定義總有外部鏈接,不論這個成員函數是靜態、虛擬仍是通常成員函數,類靜態數據成員定義總有外部鏈接。
1.類的定義有內部鏈接。若是不是,想象一下你在4個cpp文件中include定義了類Base的頭文件,在4個編譯單元中的類Base都有外部鏈接,在鏈接的時候就會出錯。
看下面的例子:
//main.cpp { static int s_i; //靜態類成員聲明,內部鏈接 public: void foo() { ++s_i;} //類inline函數,內部鏈接 };
struct D { void foo(); //類成員函數聲明,內部鏈接 };
void D::foo() //類成員函數定義,外部鏈接 { cout << "D::foo in main.cpp" <<endl; }
{ B b; D d; return 0; }
|
在這個例子中,main.cpp與a.cpp中都有class B和class D的定義,但在編譯這兩個cpp文件時並不發生link錯誤。
2.類的非inline成員函數(通常,靜態,虛擬都是)總有外部鏈接,這樣當你include了某個類的頭文件,使用這個類的函數時,就能鏈接到正確的類成員函數上,繼續以上面爲例子,若是把a.cpp中的struct D改成
struct D //類定義 { int d; void foo(); //類成員函數聲明 };
void D::foo() //類成員函數定義,外部鏈接 { cout << " D::foo in a.cpp" <<endl; } |
這時main.cpp與a.cpp中的D::foo都有外部鏈接,在鏈接就會出現multiply defined symbols錯。
3.類的靜態數據成員有外部鏈接,如上例的B::s_i,這樣當你在main.cpp中定義了類靜態數據成員,其它編譯單元若使用了B::s_i,就會鏈接到main.cpp對應編譯單元的s_i。
d)inline函數總有內部鏈接,不論這個函數是什麼函數
// main.cpp class Bar //類定義,內部鏈接 { public: static int f() { return 2;} //inline 類靜態函數,內部鏈接 int g(int i) { return i;} //inline 類成員函數,內部鏈接 };
public: inline int k(); //類成員函數聲明,內部鏈接 };
int main(void) { return 0; } |
若是你的Base類是定義在Base.h中,而Base的inline 函數是在Base.cpp中定義的,那麼在main.cpp中include "Base.h"編譯不會出現問題,但在鏈接時會找不到函數k,因此類的inline函數最好放到頭文件中,讓每個包含頭文件的cpp都能找到inline函數。 如今對c++中的鏈接有了一個認識,能清楚的知道是什麼緣由產生鏈接時錯誤。當你在鏈接時產生鏈接不到的錯誤,這說明全部的編譯單元都沒有這個實體的外部鏈接;當你在鏈接時發現有多個鏈接實體,這說明有多個編譯單元提供了同名的有外部鏈接的實體。同時,在進行程序設計時,也要注意不要使只有本編譯單元用到的函數、類、變量等有外部鏈接,減小與其它編譯單元的鏈接衝突。