前言
extern 關鍵字:
首先還是先看一下 extern 關鍵字的作用:extern關鍵字可以置於變量或函數前,以標示變量或函數的定義在別的文件中,提示編譯器遇到此變量或函數時在其他模塊中尋找其定義。
通常情況下,比如我們在頭文件 "b.h" 中聲明了一個函數,然后在 "b.cpp" 中實現了該函數,當在 "main.cpp" 中調用 "b.h" 中聲明的函數時,只需要在 ""main.cpp" 中 #include "b.h" 就可以了。例子如下:
1 //b.h 2 #ifndef _B_ 3 #define _B_ 4 5 #include<iostream> 6 using namespace std; 7 8 void test(); 9 10 #endif // _B_
1 #include "b.h" 2 void test() 3 { 4 cout << "test" << endl; 5 }
1 #include<iostream> 2 #include"b.h" 3 4 using namespace std; 5 6 int main() 7 { 8 test(); 9 system("pause"); 10 return 0; 11 }
除了通過 #include "b.h" 這樣的方式能調用到 "b.h" 中的函數外,還有下面這種方式也能調用到"b.h" 的函數。
1 #include<iostream> 2 //#include"b.h" //在這里,不注釋掉也是可以得,但是在對於 變量 來說時就必須注釋掉了 3 4 using namespace std; 5 6 extern void test(); //告訴編譯器test()函數聲明在其他文件中 7 8 int main() 9 { 10 test(); 11 system("pause"); 12 return 0; 13 }
上面是對於函數而言,那么要是在 "b.h" 中定義了一個全局變量 int x,(記住是全局變量哦!)現在我們想在 "main.cpp" 中訪問變量 x, 那該怎么辦呢? 會不會 #include "b.h" 后就可以直接訪問了呢?我們先試一下:
1 //b.h 2 #ifndef _B_ 3 #define _B_ 4 5 #include<iostream> 6 using namespace std; 7 8 int x = 10; 9 10 void test(); 11 12 #endif // _B_
1 //main.cpp 2 #include "b.h" 3 4 int main() 5 { 6 cout << "x=" << x << endl; 7 system("pause"); 8 return 0; 9 }
我們在 "main.cpp" 中輸出變量 x, 在vs2017中會報錯。 (上一個增量鏈接沒有生成它;正在執行完全鏈接,error LNK2005: "int x" (?x@@3HA) 已經在 b.obj 中定義)
通過 #include "b.h" 這種方式就想訪問到變量 x 太天真了,是訪問不到的,因為 "b.h" 的全局變量 x 的訪問作用域是文件作用域,它只能在 "b.h" 這個文件中進訪問,記住是只能在 "b.h" 中進行訪問,在 "b.cpp"中通過 #include "b.h" 你也是不能訪問的。 那么現在我們有沒有其他方式能在 "main.cpp" 中訪問到變量 x 呢?當然有,通過 "extern" 關鍵字能達到目的。用法如下:
1 //main.cpp 2 //#include "b.h" //一定要注釋掉 3 #include <iostream> 4 using namespace std; 5 extern int x; 6 7 int main() 8 { 9 cout << "x=" << x << endl; 10 system("pause"); 11 return 0; 12 }
說明:如果在一個文件中使用extren引入外部變量,在這個文件中修改這個變量,就等於修改了該外部變量。
說了那么多廢話,終於把 extern 關鍵字說清楚了。接下來這個才是extern "C" 。
extern "C"
要說清楚 extern "C" 是怎么一回事,還得先說說C++函數重載的那些事。C++有函數重載,而C語言是沒有函數重載的。函數重載是指兩個或多個函數之間函數名相同,參數列表不同,參數列表不同可以是參數的個數不同,或者是參數的個數相同但參數類型不同,需要注意的是如果函數名相同,參數列表完全相同但返回值類型不同是不能構成函數重載的。C++有函數重載是因為當生成obj中間文件/目標文件的時候,C++編譯器把原函數名與參數信息結合,產生了一個獨特的內部名字,比如有兩個函數 void foo(int x) 和 void foo(void) ,最終產生的內部名字就是 _foo_int 和 _foo_void (實際產生的內部名字的命名規則應該不是這樣的,這里我們並不關心它的命名規則是怎樣的,只需要體會這個意思就可以了),通過這樣編譯器就能區分 void foo(int x) 和 void foo(void)這兩個函數了。但是在C語言里面,並不會產生這樣的內部名字,如果C語言里面有兩個函數 void foo(int x) 和void foo(void),那么當生成obj中間文件/目標文件的時候,產生的名字就是 _foo 和 _foo 這樣兩個名字相同,C編譯器就不能將兩個函數區分開了,所以C語言里面也就沒了函數重載。
正是由於C++編譯器 和 C編譯器對函數名處理方式的不同,當我們的 C++ 程序調用 C 程序或者 C 程序調用 C++程序時就會存在問題。 有了問題那當然就得解決,於是就有了 extern "C" 的出現。
所以說到底 extern "C" 的作用是用來解決名字匹配問題,實現 C 與 C++ 的混合編程。擺這么一句話在這里顯得很蒼白無力,還是舉例說明一下。
C++中函數重載,編譯器的命名規則是
1 int fun(int a, int b) 2 { 3 return a + b; 4 } 5 char fun(char a, char b) 6 { 7 return a + b; 8 } 9 10 void main() 11 { 12 cout<<fun('A','B')<<endl; 13 cout<<fun(10, 20)<<endl; 14 }
上述C++的兩個重載函數被編譯器命名為下:
1 int fun(int a, int b, char c) 2 { 3 return a + b; 4 }
命名會變為
可以得出,C++編譯器的函數命名規則


1 extern "C" int fun(int a, int b) 2 { 3 return a + b; 4 } 5 extern "C" char fun(char a, char b) 6 { 7 return a + b; 8 } 9 10 void main() 11 { 12 cout<<fun('A','B')<<endl; 13 cout<<fun(10, 20)<<endl; 14 }
上述C++程序中,加一個extern "C"沒問題,加兩個就會報錯了,加了extern "C"就等於用C語言的方式編譯,函數的命名規則會變成C語言的,然而C語言不允許函數的重載。
函數重載的另一個問題
1 int fun(int a, int b) 2 { 3 return a + b; 4 } 5 char fun(int a, int b) 6 { 7 return a + b; 8 }
如果按照C++編譯器的命名,這兩個函數的命名也是不同的,為什么不能重載呢?
這時候並不是不行,而是調用一個函數,可以調用一個,也可以調用另一個,然后編譯器並不知道要調到哪個,出現了二義性,而不同的函數參數,在編譯時就決定了調用哪個,編譯器也不知道要將返回值給哪個類型,所以僅靠返回值是不能用來重載函數的。