在c/c++語言中的頭文件其實是為了搜尋對應的類型和函數信息的
比如在頭文件中可以聲明一個函數,但這個函數可能定義在任何地方
比如一個靜態庫或者動態導入庫lib中,或者可以直接以原代碼的方式寫在cpp/c文件中
頭文件提供的服務叫做類型映射(phototype)
函數在c/c++語言中也是一種類型
函數在聲明的時候其實僅僅是說明了對應的函數調用協議,函數名稱和參數類型
這樣就可以明確的指導編譯器如何創建這個函數對應的調用代碼
而尋找這個函數的工作交給了鏈接器
注意 編譯器在vc里邊叫做cl,鏈接器在vc里邊叫做link。
cl負責生成obj,每一個cpp/c文件會生成一個obj文件
這個obj里邊包含了直接由c/cpp源程序所生成的匯編代碼,這個c/cpp文件需要查找的符號(這個后面再說)
link其實和咱們上微機原理匯編課的時候用的Link很像
他負責將每一個obj中的符號查找表中的東西轉換為一個地址
這個地址就是最后編譯完成后的exe文件的函數對應這個函數的入口地址。
符號,就是這個函數或者對象通過編譯器后所產生的名稱
在c語言中一個符號由這個函數或者這個對象的名稱和這個函數的調用協議組成
這也是為什么c語言不支持重載的原因
(還記得重載吧?重載就是參數類型或個數不同,
參數和個數都相同的叫做重寫,重寫只能用在類的函數的繼承中)
而c++會在每一個函數的前后添加一堆用于表示這個函數的調用方法所屬類型,名字空間和調用參數類型等的大量字符
用來區分每一個函數
比如一個函數
int Test(); 根據不同的命名方法可能被不同的命名
如果到定義了extern "C" 如下:
extern "C" int Test();
這個函數就被vc編譯器按c語言的方式命名為 _Test
其中的前劃線 _表示這個函數的調用協議為__cdecl
Test就是這個函數的名稱
如果使用vc編譯器直接編譯這個函數int Test();
他就被當作int __cdecl Test(void); 編譯成 ?Test@@YAHXZ
其中的? 和 @@YAHXZ都是編譯器加上去的
? 和 @@YAH 是用來表示調用協議的
其中的H為返回值是int
X表示沒有參數。
Z是函數名稱結束修飾
調用協議
指函數的參數傳遞使用的方式
有__cdecl __fastcall __stdcall __thiscall 等
__cdecl 是c語言的調用方式
__fastcall 使用寄存器傳遞參數
__stdcall 使用棧傳遞參數,并且其壓棧順序為從右到左,由被調用函數來清除棧
__thiscall 是類對象的方法調用方式,這種調用方式不能直接由程序指定
如果一個函數的是被實現在cpp或者c文件中的時候,就必須保證這個cpp或者c文件所產生的符號與這個函數的聲明所產生的
符號相同,否則鏈接器在鏈接的時候就會發生無法找到符號的錯誤
如何保證這個符號是相同的呢?
只要在cpp或者c文件中包含這個頭文件
或者如果能保證所生成的符號相同則根本就不用h文件也能工作
比如如下程序
// 這是main.cpp文件的東西
int Test();
int main()
{
Test();
}
// 這是test.cpp文件的東西
int Test()
{
return 1;
}
這里就完全沒有用頭文件
注意事項:
1。如果是使用了c文件作為函數的載體而編譯器為微軟的vc,
需要在h文件中添加extern "C"通知編譯器使用c的命名規則
最好使用判斷
#ifdef __cplusplus
extern "C" {
#endif
...
函數聲明
...
#ifdef __cplusplus
}
#endif
這樣在c編譯器上也能使用這個頭文件了
2。c/cpp文件與頭文件名稱無關
3。如果一個頭文件中定義了一個結構或者類,那么在h文件中最好使用防止多次包含的
#ifndef __XXX__H__
#define __XXX__H__
...
//類或結構定義
class a
{
...
};
...
#endif
這里的XXX就是這個頭文件的名稱,這個名稱是可以隨便起的,只要不起重。
但c/cpp文件中不需要添加這些東西
或者如果使用了vc6作為編譯器它支持
#pragma once
在h文件的最前面寫上這句話就可以保證這個頭文件只會被處理一遍