【C++】C++中的分離式編譯

在C++中隨着程序愈來愈複雜,咱們但願把程序的各個部分分別儲存在不一樣的文件中。C++支持的分離式編譯(separate compilation)容許咱們把程序分割到幾個文件中去,每一個文件獨立編譯。

頭文件以.h爲後綴,主要包含類和函數的聲明;實現文件以.cpp爲後綴。能夠這樣理解,頭文件中包含就是一些接口聲明,而實現文件就是對這些接口進行定義。
例如:ios

 

文件:Num.h函數

class Num{
    private:
        int num;
    public:
        Num();
        Num(int);
        int getNum();
};

文件:Num.cpp測試

#include "Num.h"

Num::Num() : num(0){}
Num::Num(int n) : num(n){}
int Num::getNum(){
return num;
}

文件:NumTest.cppspa

#include <iostream>
#include "Num.h"

using namespace std;

int main(int argc,char **argv){
    Num n(20);
    cout << n.getNum() << endl;
return 0;
}

而後使用以下命令進行編譯:命令行

g++ NumTest.cpp Num.cpp -o NumTest

注意:必須在任何使用Num類的地方添加上 #include "Num.h" ,上面的案例中 NumTest.cpp 和 Num.cpp 文件都使用到了Num類,應此都必須在文件頂部引入 Num.h 文件。code

 

#ifndef

有時咱們可能會屢次包含同一個文件,在C++中出現重複聲明是不容許的。在上面的案例中 Num.cpp 和 NumTest.cpp 文件都包含了Num.h文件,但Num.cpp和NumTest.cpp是分開編譯的,因此不會出現重複定義。
爲了演示該錯誤,咱們從新定義一個文件。對象

 

文件:Foo.hblog

#include "Num.h"

class Foo{
 public:
  Num n;
};

注意:這裏沒有Foo.cpp文件,由於Foo.h頭文件沒有什麼須要被實現的。

接下來進行測試接口

 

文件:NumFooTest.cppget

#include <iostream>
#include "Num.h"
#include "Foo.h"

using namespace std;

int main(int argc,char **argv){
    Num n(20);
    cout << n.getNum() << endl;

    Foo f;
    cout << f.n.getNum() << endl;
return 0;
}

當筆者試圖使用 g++ NumFooTest.cpp Num.cpp -o NumFooTest 命令進行編譯時,出現以下的錯誤信息:

In file included from Foo.h:1:0,
 from NumFooTest.cpp:3:
Num.h:1:7: error: redefinition of ‘class Num’
In file included from NumFooTest.cpp:2:0:
Num.h:1:7: error: previous definition of ‘class Num’
main.cpp: In function ‘int main()’:
main.cpp:13:13: error: ‘class Foo’ has no member named ‘num’ 

 

從錯誤信息中能夠看出錯誤緣由是Num類重複定義了,爲了解決這種問題可使用#ifndef,#ifndef的功能可描述爲以下:「若是宏語句未定義語句1則執行程序2,不然執行程序3」。
例如:

#ifndef NUM_H
#define NUM_H
<define class or whatever else>
#endif 

 

當首次編譯時 NUM_H 未被定義,因此 #ifndef NUM_H 條件爲真,而後會執行 #define NUM_H 定義 NUM_H 而且執行咱們定義的其餘操做。若其後再次編譯到該文件,因爲 NUM_H 已經被定義了,因此 #ifndef NUM_H 條件爲假,也就不會執行 #ifndef 到 #endif 間的任何代碼。在知道了 #ifndef 的原理後,咱們知道只須要將 #ifndef 語句應用到上面的 Num.h 文件,則會解決重複定義的問題。

 

文件:Num.h

#ifndef NUM_H
#define NUM_H
class Num{
    private:
        int num;
    public:
        Num();
        Num(int);
        int getNum();
};
#endif

 

#pragma once

除了是使用#ifndef語句避免重複定義,還能夠將#pragma once添加到文件的開頭,也能夠完成一樣的功能。Visual Studio默認使用的就是#pragma once。

 

文件:Num.h

#pragma once
class Num{
    private:
        int num;
    public:
        Num();
        Num(int);
        int getNum();
};

 

分離編譯

完成分離式編譯的步驟:
1.將.cpp文件編譯爲對象文件,該對象文件包含.cpp文件的機器碼。
2.將對象文件連接到可執行文件。

 

將.cpp編譯爲對象文件,可使用g++加上命令行選項 -c

g++ -c NumFooTest.cpp Num.cpp

這行命令會產生 NumFooTest.o 和 Num.o 對象文件。

而後將它們連接爲可執行文件,咱們再次使用g++命令:

g++ NumFooTest.o Num.o -o NumFooTest

這樣就會產生能夠執行文件NumFooTest。

 

 

若是改變了NumFooTest.cpp文件,咱們只須要從新編譯NumFooTest.cpp文件就好了

g++ -c NumFooTest.cpp

獲得NumFooTest.o對象文件。

而後連接爲可執行文件:

g++ NumFooTest.o Num.o -o NumFooTest

只編譯變更過的文件能夠爲咱們節約編譯時間,大多數的IDE會自動幫助咱們完成這一部分功能。

 

make命令

在一個很大的項目中很難去手動編譯不少文件。若是你使用IDE的話,這些命令能夠由IDE代勞。但若是未使用IDE的話,那麼必須手動去完成。在大型項目中,使用 g++ 手動連接成百的文件,這是不可能的。
這時可使用make命令,首先建立一個文件Makefile,而後在其中添加咱們編譯須要依賴的信息。

 

文件:Makefile

CFLAGS = -O
CC = g++
NumTest: NumTest.o Num.o
    $(CC) $(CFLAGS) -o NumTest NumTest.o Num.o
NumTest.o: NumTest.cpp
    $(CC) $(CFLAGS) -c NumTest.cpp
Num.o: Num.cpp
    $(CC) $(CFLAGS) -c Num.cpp
clean:
    rm -f core *.o

而後輸入 make 命令,Linux就會自動去解析 Makefile 文件。

關於Makefile文件須要注意兩點:

第一,Makefile 文件的名稱是固定的(不能改變,沒有後綴);

第二,縮進是Tab不是space。

 

接下來解析一下上面的命令:

NumTest: NumTest.o Num.o 

表示 NumTest 由 NumTest.o 和 Num.o 生成。

 

$(CC) $(CFLAGS) -o NumTest NumTest.o Num.o

其中 $(CC) 會被替換爲 g++ , $(CFLAGS) 會被替換爲 -0 ,替換後的命令會生成 NumTest 文件。文件中的其餘命令也是相似的道理,最後的clean表示清理文件, rm -f core *.o 會刪除全部以o結尾的文件(也就是生成 NumTest 的全部中間文件——對象文件)。

 

 

上面的文件內容依然比較複雜,若是換成以下的命令,除了可使用上面那種格式,還能夠在Makefile中使用下面這種格式:

CFLAGS = -O
CC = g++
SRC = NumTest.cpp Num.cpp
OBJ = $(SRC:.cpp = .o)
NumTest: $(OBJ)
  $(CC) $(CFLAGS) -o NumTest $(OBJ)
clean:  
  rm
-f core *.o

 


參考文章:C++ Separate Header and Implementation Files

相關文章
相關標籤/搜索