標頭檔、編譯與連結

專案建置[6]

by 鹽亞倫

標頭檔

Headers

我們來看看上禮拜的code

可以發覺

宣告、實作與測試

全部都混再一起了

程式變的又臭又長

事實上,一個比較好專案當中

應該要把不同功能的程式碼放在不同的檔案當中

 所以要怎麼做ㄌㄟ˙

假設今天有一個class a

其實我可以將a的實作部分另外放在一個檔案當中

class A{
    int x, y, z;
    void print();
}

int main() {
    A a;
    cin >> a.x >> a.y >> a.z;
    a.print();
}

void A::print() {
    cout << "("
    cout << this->x << ", ";
    cout << this->y << ", ";
    cout << this->z << ")\n";
}

 所以要怎麼做ㄌㄟ˙

假設今天有一個class a

其實我可以將a的實作部分另外放在一個檔案當中

class A{
    int x, y, z;
    void print();
}

void A::print() {
    cout << "("
    cout << this->x << ", ";
    cout << this->y << ", ";
    cout << this->z << ")\n";
}
class A{
    int x, y, z;
    void print();
}

int main() {
    A a;
    cin >> a.x >> a.y >> a.z;
    a.print();
}

main.cpp

a.cpp

要注意的是,宣告必須在兩個地方都存在

可是假設我今天linked list的宣告那麼長

又要在很多個檔案裡面使用它

一直複製好麻煩?

因此,C/C++出現了標頭檔

標頭檔

  • 副檔名 .h或是.hpp,幾乎沒有差別
  • 一個把所有提前宣告的東西一起寫完的地方
  • 用 #include 的方式把它 include 進來
  • 例如:
    • #include <iostream>
    • #include "person.h"
  • ​其中,用<>的話代表是系統標頭檔
    • ​例如電腦/usr/bin/include中會有一個iostream.h
  • ​用 "" 的則是你自己寫的標頭檔,要放在同一個目錄底下

來把上禮拜的Linked List的宣告變成標頭檔吧!

來研究一下我寫的haeder檔

#ifndef LINKEDLIST_H
#define LINKEDLIST_H


.......


#endif

這個是什麼?

這是為了防止被重複引用

利用巨集指令,確保只會被一個cpp檔案引用一次

才不會重複宣告

其他細節

extern -- 外部變數關鍵字

如果在一個.h檔或是.cpp檔中,

需要用到在另外一個.cpp檔案中宣告的全域變數

需要使用extern關鍵字代表你要引用他

Ex.

main.cpp (實際宣告處)

a.cpp (引用處)

int GlobleTime;
extern int GlobleTime;

編譯與連結

弄完剛剛的東西

你可能會發覺你不知道該怎麼編譯執行那麼多檔案了

所以現在來講講編譯器吧

編譯器工作流程

四大步驟:

  • 預處理
  • 編譯
  • 組譯
  • 連結

g++ 基本用法

g++ 的 flags

  • -o
  • -c
  • -s
  • -shared

g++ CPP_NAME.cpp

執行檔檔名會是 a.out 或是 a.exe

一個一個慢慢來講吧

  • -std
  • -D
  • -g
  • -I
  • -l
  • -L

多檔案:

g++ FILE1.cpp FILE2.cpp FILE3.cpp

直接變成執行檔

首先是基本參數

基本上一定會加上這些

除錯用參數

-g

允許gdb相關功能

-Wall

顯示所有錯誤訊息

版本參數

-std

選擇c++版本,例如:

-std=c++11

巨集參數

-D

從這裡#define

東西,例如:

-DAaW

(相當於#define AaW)

我的常見g++用法:

g++ -g -Wall -DAaW -std=c++17 -o OUTPUTNAME.out FILENAME.cpp

檔名參數

-o

更改輸出檔名

Object Files

  • 副檔名.o
  • 編譯以及組譯完之後會產生的二進位檔
  • 通常如果有多個cpp檔,會先一個一個編譯成.o之後,再連結成執行檔

如何生成object files

用 -c (小寫)

g++ -c -o haha.o hahaha.cpp

將hahaha.cpp編譯成haha.o

如果沒有用-o預設會和cpp檔同名

g++ -c hahaha.cpp

(生成hahaha.o)

將多個object file變成一個執行檔

g++ -o hahaha.exe a.o b.o c.o

透過以上方法就可以將多個檔案編譯並執行了

引用標頭檔

#include <> 只能include系統標頭檔

#include "" 只能include同個資料夾的

啊我如果想要include別的資料夾的怎麼辦?

.
├── build
├── include
│   ├── a.h
│   └── b.h
├── lib
└── src
    └── a.cpp

用 -I 參數

例如:

g++ -I./include -o haha.exe a.cpp

這樣就可以在程式碼中用<>了

補充:編譯成組合語言

-s 參數

可以先把cpp檔變成組合語言

然後之後組合語言可以再用-c參數

組合成object files

我不會組合語言 去問溫室菜

library

Library

有一些程式碼,每一次要使用時都還要慢慢編譯,實在是太慢了

例如,整個STL加起來可能有幾萬行

不可能每次重新編譯

因此有個東西叫做函式庫(library)

本身就是二進位模式,編譯時可以直接和object files 連結,以減少重新編譯時間

靜態vs動態函式庫

動態共享連結函式庫

(Shared library)

  • 在程式開始執行時才載入的
  • 具有三個優點
    1. 減少執行檔大小
    2. 更新函式庫時無須重新編譯其他程式
    3. 可在程式執行時修改函式庫

靜態函式庫

(Static library)

  • 檔案名稱以 lib 開頭,而副檔名則為 .a。
  • 在程式變為執行檔時便載入完成
  • 具有三個優點
    • 較高的執行速度
    • 只要保證使用者有程式對應的函式庫,便能執行
    • 避免因為程式找不到.dll而無法執行(dll地獄)

編譯成靜態函式庫

編譯成動態函式庫

-shared

例如

$ gcc -c -o hello.o hello.c
$ gcc -shared -o libhello.so hello.o
$ gcc -c -o sum.o sum.c
$ ar -rcs libsum.a sum.o

-l 和 -L 參數

使用靜態函式庫

-L(大寫):資料夾

-l(小寫):檔案名

假設資料夾長這樣(pwd在build)

.
├── build
│   └── a.o
├── include
│   ├── a.h
│   └── b.h
├── lib
│   └── libhahaha.a
└── src
    └── a.cpp

而我想要將a.o和hahaha函式庫編譯在一起

$ gcc -o eee.exe -L../lib -lhahaha a.o

$ gcc -o eee.exe ../lib/libhahaha.a a.o

或是

實作時間

寫一個build.sh來練習編譯吧

下課

是不是有人還沒備明天ㄉ演算法啊

話說

寒訓報名倒數三天!!!