一,關於編譯鏈接
編譯指的的把編譯單元生成目標文件的過程
鏈接是把目標文件鏈接到一起的過程
編譯單元:可以認為是一個.c或者.cpp文件。每個編譯單元經過預處理會得到一個臨時的編譯單元。預處理會間接包含其他文件還會展開宏調用。
每個編譯單元編譯成目標文件后會暴露自己內部的符號。
(比如有個fun函數,就會暴露出於fun函數對應的符號,其他的函數和變量也是一樣的。但是也有不會暴露出去的,比如加了static修飾的函數或變量)
每個目標文件都有自己的符號導入表和符號導出表。
鏈接器根據自己所需要的符號去找其他的目標文件。
(假如main用到了別的文件定義發fun函數,在鏈接的過程中,鏈接器知道mian需要fun符號,然后去其他的目標文件總找。如果找到了就鏈接起來。找不到就報鏈接錯誤)
二、模板函數
模板函數的代碼並不能直接編譯成二進制代碼,其中要有一個實例化的過程。模板被用到的時候才會進行實例化。
1.假設有個test.h里面聲明了模板函數。
test.cpp實現了那個模板函數。
main用到了那個模板函數。
編譯器會編譯test.cpp編譯單元和main.cpp編譯單元。
編譯test.cpp時無法給出A<int>::fun這樣的符號
main.cpp需要一個這樣的符號A<int>::fun。
在分離式編譯的環境下,編譯器編譯某個cpp文件時並不知道另外的cpp的存在,也不會去查找(當遇到未決符號時他會寄希望於鏈接器)。
這種模式在沒有模板的情況下運行良好,但是遇到模板時就不行了,因為模板僅在需要的時候才會實例化出來。
所以當編譯器只看到模板的聲明時,它不能實例化該模板,只能創建一個具有外部連接的符號,並期待鏈接器能夠將符號的地址決議找出來。
然而實現該模板的cpp文件並沒有用到該模板時,編譯器就不會去實例化。
所以整個工程當中找不到模板實例的代碼,鏈接器就找不到那個符號。就會報錯了。
3.實例:
test.h
#ifndef __CAR_H__ #define __CAR_H__ #include<iostream> using namespace std; #define IN_CPP 1 template <class T> class car { public: car(T a); void print(); public: T data; }; #if IN_CPP #else template <class T> car<T>::car(T a) { data = a; } template <class T> void car<T>::print() { cout << "data = " << data << endl; } #endif #endif // __CAR_H__
test.cpp
#include "car.h" #if IN_CPP template <class T> car<T>::car(T a) { data = a; } template <class T> void car<T>::print() { cout << "data = " << data << endl; } #endif void callTest() { car<int> a(33); a.print(); }
main.cpp
#include<iostream> #include "car.h" using namespace std; void fun() { cout << "fun() +++" << endl; car<int> a(99); a.print(); } int main() { fun(); return 0; }
分析:
IN_CPP 如果是0:就相當於聲明實現都在頭文件中。這樣main.cpp是可以編譯運行的。
IN_CPP 如果是1:說明聲明跟實現分開了。這種情況main.cpp鏈接時找不到 car構造相關的函數,也找不到模板類car中print的函數。會報兩個鏈接錯。
但是如果在test.cpp寫個函數(callTest())調用car的構造和print,相當於實例化了那兩個類模板函數。就會導出那兩個函數的符號。假如只調用一個構造,那么print就沒有實例化。main也會鏈接失敗
然后在main.cpp就可以調用到了。