深入淺出 C++:#include Directive PART 1
除了基本語法外,使用 C++ 提供的標準庫、型別定義等,都需要使用 #include 引入 header file,寫法如下:
#include <iostream>
#include <vector>
#include <string>
#include 在 C++ 屬於 preprocessing directive,他不算是程式執行指令的一部分,其功能是對 compile 過程、第一步的 preprocessor 下指令。當 preprocessor 看到 #include,他會將該行置換為 #include 所欲包含的檔案內容。若該檔案內又有 #include,則會層層展開,引入的檔案越多,程式碼越大,compile 的時間就越長。
為何需要 Header File
為了讓程式更模組化、提高可讀性、加強複用性等,除了單一函式程式碼不要太長、通常也會避免單一檔案的程式碼太多。將一個大功能,拆成好幾個檔案,每個檔案的取名就能看出是對應其中的哪個子功能,這是常規操作。
為了使用其他 cpp 檔案裡定義的函式,在使用之前,必須先宣告他的存在。下面範例中,main.cpp 宣告了 Factorial() 與 SuperFactorial() 兩個函式,而他們的程式碼,實際上是寫在 factorial.cpp:
// main.cpp
int Factorial(int n); // 函式宣告
int SuperFactorial(int n); // 函式宣告
int main()
{
// 使用 Factorial( 與 SuperFactorial()
}
// factorial.cpp
int Factorial(int n)
{
// 程式碼實現
}
int SuperFactorial(int n)
{
// 程式碼實現
}
將兩個 cpp 檔案,compile 成一個可執行檔案的指令如下:
[email protected]:~/cpp/c2$ clang++ -std=c++17 -stdlib=libc++ --pedantic-errors -o factorial main.cpp factorial.cpp
C++ compiler 首先根據每個 cpp file,轉換成平臺對應的指令、進行優化,產生 object file。當 compile main.cpp 時,看到了兩個函式的宣告、但沒找到定義,先以 undefined symbol 來表示他們。當兩個 cpp file 各自產生 object file 後,linker 會將其合成最後的可執行檔案,過程中就會嘗試尋找 undefined symbol 定義在哪個 object file,如果找到了,就將其替換為實際函式的地址,這樣執行時,就能呼叫到該函式。
透過這種將程式碼分類到不同 cpp 的方式,可以提升模組化,通用的程式碼就不用每個 cpp 都重寫一次,增加程式碼體積、造成維護困難。然而,如果我們現在開發較大型的程式,可複用的函式一定很多,難不成每個 cpp 檔案,都得寫一大串的函式宣告?這想想也挺麻煩的,如果這些大串的函式宣告,也能寫一次、共用到各個 cpp 檔案,豈不美哉?有鑑於此,C/C++ 引入了 header file 的概念,用以存放共通的函式、型別宣告。
針對上面範例,一般是寫一個如下的 header file:
#ifndef FACTORIAL_EXAMPLE_H
#define FACTORIAL_EXAMPLE_H
int Factorial(int n);
int SuperFactorial(int n);
#endif
然後 main.cpp 裡只要 #include 它就行了。
Include Guard
開發大型程式時,由於 #include 檔案相當多,且 header file 也可以再 #include 其他 header file,層層展開下,不免導致程式碼重複。函式宣告重複出現就算了,型別宣告可不能重複。為了更細緻的理解,我們用下面程式碼實驗,假設他們就是多層 #include,被 preprocess 展開的結果:
class MyClass { /* ... */ };
class MyClass { /* ... */ };
enum MyEnum { /* ... */ };
enum MyEnum { /* ... */ };
typedef int size;
typedef int size;
typedef float length;
typedef double length;
#define SIZE(m) m->size
#define SIZE(m) m->size
#define SIZE(m) m->computeSize()
int function1();
int function1();
int function1(int parameter);
int function1(double parameter);
int main()
{
}
從下面的報錯,可看到,class、enum 重複定義其內容是不允許的。而 typedef、#define 只要內容還是相同,可以允許重複定義,但若內容不同,還是會報錯。同樣的函式宣告,是可以重複定義的,即使引數變了也允許,因為 C++ 有 function overloading 機制,函式名稱相同、但引數不同,compiler 會視為不同的函式。
[email protected]:~/cpp/c3$ clang++ -std=c++17 -stdlib=libc++ --pedantic-errors -o test test.cpp test.cpp:2:7: error: redefinition of 'MyClass' class MyClass { /* ... */ }; ^ test.cpp:1:7: note: previous definition is here class MyClass { /* ... */ }; ^ test.cpp:4:6: error: redefinition of 'MyEnum' enum MyEnum { /* ... */ }; ^ test.cpp:3:6: note: previous definition is here enum MyEnum { /* ... */ }; ^ test.cpp:8:16: error: typedef redefinition with different types ('double' vs 'float') typedef double length; ^ test.cpp:7:15: note: previous definition is here typedef float length; ^ test.cpp:13:9: error: 'SIZE' macro redefined [-Werror,-Wmacro-redefined] \#define SIZE(m) m->computeSize() ^ test.cpp:12:9: note: previous definition is here \#define SIZE(m) m->size ^ 4 errors generated.
為解決大型程式可能遇到的、在多個 #include 內容導致重複定義的問題,常用的技巧是在 header file 的開始與結束加入下面 #ifdef、#define、#endif,第一次引入這個 header file,就 #define 了 FACTORIAL_EXAMPLE_H 這個 macro,這樣在後續重複引入時,preprocessor 發現 FACTORIAL_EXAMPLE_H 已經被定義過了,所以就不再展開 #ifndef 到 #endif 之間的部分了。此技巧是 C/C++ 的常規操作,稱為 include guard:
#ifndef FACTORIAL_EXAMPLE_H
#define FACTORIAL_EXAMPLE_H
int Factorial(int n);
int SuperFactorial(int n);
#endif
Include guard 定義 macro 名稱時,最好選擇有點複雜度的名字,例如內容包含公司、專案等名稱,在大型程式引用多種庫時,才不至於跟其他的 header file 使用同樣的 macro 名稱,導致該展開的程式碼沒展開。
此外,有些程式設計師會在 include guard 的 macro,選擇單底線或雙底線開頭的名字,例如:
#ifndef __LIBCPP_ISTREAM
#define __LIBCPP_ISTREAM
// ...
#endif
筆者第一次看到這種寫法時,覺得很特別,有段時間 include guard 就這麼依樣畫葫蘆耍帥,後來才知道這不是允許的寫法,只是 compiler 沒檢查這項。C++ 標準規定,雙底線開頭、或單底線開頭加上首字母大寫的取名方式,是保留給各平臺實現 C++ 標註庫用的,不是給一般應用開發者用的。咱們看下原文吧,這個規則到目前 C++17 仍是成立:
— Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use. — Each identifier that begins with an underscore is reserved to the implementation for use as a name in the global namespace.
Preprocessor 如何處理 #include directive
前面提到,preprocessor 無非就是將引入的內容展開。我們以下面的程式來說明。在這個例子中,factorial.h include 了 BigNumber.h,而 main.cpp 除了 include factorial.h,又將 BigNumber.h 引入了一次,這是常見的情況,引入別的庫的 header file,通常不會管他裡面已經引入了什麼:
// BigNumber.h
#ifndef BIG_NUMBER_H
#define BIG_NUMBER_H
class BigNumber final
{
public:
BigNumber();
BigNumber(const BigNumber& rhs);
~BigNumber();
BigNumber& operator = (const BigNumber& rhs);
private:
char* value_;
};
BigNumber operator + (const BigNumber& lhs, const BigNumber& rhs);
BigNumber operator - (const BigNumber& lhs, const BigNumber& rhs);
BigNumber operator * (const BigNumber& lhs, const BigNumber& rhs);
#endif
// factorial.h
#ifndef FACTORIAL_H
#define FACTORIAL_H
#include "BigNumber.h"
BigNumber Factorial(int n);
BigNumber SuperFactorial(int n);
#endif
#include "factorial.h"
#include "BigNumber.h"
int main()
{
}
我們可以用 clang 的 -E 選項,只執行 preprocessor,就能觀察如何展開了:
[email protected]:~/cpp/c3$ clang++ -std=c++17 -E -o factorial.txt main.cpp
本例中,產生的 factorial.txt 內容如下:
# 1 "main.cpp"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 402 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.cpp" 2
# 1 "./factorial.h" 1
# 1 "./BigNumber.h" 1
class BigNumber final
{
public:
BigNumber();
BigNumber(const BigNumber& rhs);
~BigNumber();
BigNumber& operator = (const BigNumber& rhs);
private:
char* value_;
};
BigNumber operator + (const BigNumber& lhs, const BigNumber& rhs);
BigNumber operator - (const BigNumber& lhs, const BigNumber& rhs);
BigNumber operator * (const BigNumber& lhs, const BigNumber& rhs);
# 5 "./factorial.h" 2
BigNumber Factorial(int n);
BigNumber SuperFactorial(int n);
# 2 "main.cpp" 2
int main()
{
}
__has_include (C++17 新增)
開發大型程式時,可能會需要切換不同的庫引入、也可能需要引入不同版本,此時就需要檢查 header file 是否存在。C++ 17 引入了 __has_include 語法,協助在 macro 中判斷,格式如下:
__has_include {"filename"}
__has_include {<filename>}
以下是 C++ 標準給出的使用範例:
#if __has_include(<optional>)
# include <optional>
# define have_optional 1
#elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define have_optional 1
# define experimental_optional 1
#else
# define have_optional 0
#endif
__has_include 就只是檢查檔案存不存在,並不檢查是否引入後有任何語法或 compile 錯誤。
#pragma once
許多 C++ compiler,如 GCC、Clang、Microsoft Visual C++、Intel C++ Compiler、Comeau C/C++ 等,都提供這個非標準、但廣泛使用的 directive,一般寫在 header file 第一行,讓這個 header file 在一個 object file 中只引入一次,目的類似 include guard:
// factorial.h
#pragma once
int Factorial(int n);
int SuperFactorial(int n);
而原理不同之處,在於 #pragma once 是直接比對檔案路徑來避免重複引入;include guard 則是檢查 macro 是否重複定義,來決定是否引入程式碼。
但 #pragma once 使用上也是有風險的。在開發大型程式時,有可能檔案因為使用 symbolic link、hard link 等機制,導致看似不同路徑、不同名稱的檔案,實際上是同一份內容,這對 compiler 的實現是一種挑戰。
#pragma once 畢竟不是標準支援的寫法。如果考慮到程式碼需跨平臺允許,還是乖乖使用 include guard 為佳。
相關推薦
深入淺出 C++:#include Directive PART 1
除了基本語法外,使用 C++ 提供的標準庫、型別定義等,都需要使用 #include 引入 header file,寫法如下: #include <iostream> #include <vector> #include <s
深入淺出 C++:與程式終止相關的函式 PART 1
C/C++ 程式,一般是藉由 main() 的返回值呼叫 exit() 函式以正常結束程式。除了程式崩潰、或使用者強制結束程式外,C++ 亦提供數個函式,允許呼叫以立即終止程式,本文將一一介紹這些函式。 不過,在進入主題前,需提醒讀者:撰寫程式時,儘可能使程式
深入淺出 C++:與程式終止相關的函式 PART 3
Markdown 編輯器真是不好用,這個文章裡,好幾個程式輸出的地方,# 開頭的都被識別成標題了。如果在 # 前面加上 \,看起來似乎能解決,但好幾行一改,又變成能在文章內看到 \ # 開頭了。哎,試了半個小時,懶得再試了,客官們擔待些,反正對理解正文沒影響便是
深入淺出 C++:與程式終止相關的函式 PART 2
quick_exit() 與 at_quick_exit() (C++11新增) [[noreturn]] void quick_exit(int status) noexcept; quick_exit() 為 C++11 引入的函式,如果程式有特殊理
C++:構造函數1——普通構造函數
創建 c++編譯 clu namespace 我們 這一 () 一次 ret 前言:構造函數是C+中很重要的一個概念,這裏對其知識進行一個簡單的總結 一、構造函數的定義 1.類中的構造函數名與類名必須相同 2.構造函數沒有函數的返回類值型說明符 [特別註意]: a.構造函數
C#:執行緒(1):什麼是執行緒?我們為什麼要使用執行緒?
最近在看公司上一個專案的原始碼,讓我感覺非常困惑的是,原始碼中使用了很多多執行緒的內容,所以給我的感覺是執行緒一直跳來跳去的,讓我感覺到很困惑。於是我就寫了這篇部落格,希望能夠更好的理解執行緒有關的內容。 一:什麼是執行緒 執行緒是和程序經常放在一起比較的兩個概念。按照我的理解,執行緒和程序
深入淺出 C++:main()
main() 是 C/C++ 程式執行的進入點,作業系統執行程式時,首先會執行 Runtime Library 內的函式進行必要的初始化,接著才呼叫 main() 轉移控制權,當 main() 返回時,再根據 main() 的返回值呼叫 exit() 結束程式。
C語言代碼編程題匯總:顯示表達式1*2+3*4+...+99*100的表示形式(采取交互的形式)
stdio.h tdi input 字符型 6.0 tro vc++6.0 text class 顯示表達式1*2+3*4+...+99*100的表示形式(采取交互的形式) 程序源代碼如下: 1 /* 2 2017年6月8日08:03:38 3 功能
C語言代碼編程題匯總:顯示表達式1*2+3*4+...+9*10的表示形式
clas ron urn ++ class align int c語言代碼 程序 顯示表達式1*2+3*4+...+9*10的表示形式 源程序代碼如下: 1 /* 2 2017年6月7日22:54:51 3 功能:實現1*2+3*4+...+9*10
程序發布出現: 服務器無法處理請求--->無法生成臨時類(result = 1)。 錯誤CS2001:未能找到源文件“C: Windows TEMP lph54vwf.0.cs”
win 臨時 生成 color 無法 添加 權限 web windows 服務器上發布的web服務程序出錯: 服務器無法處理請求--->無法生成臨時類(result = 1)。錯誤CS2001:未能找到源文件“C:\ Windows \ TEMP \ l
C++ and OO Num. Comp. Sci. Eng. - Part 1.
nim num 內容 general -o 編譯時間 增加 radi gpo 本文參考自 《C++ and Object-Oriented Numeric Computing for Scientists and Engineers》。 序言 書中主要討論的問題是面向對象的
2017.2.8-9 “PL part C:OOP”
。。 eth 發現 就是 moment 成了 program blog log 雖然以前在python中也接觸過OOP,但是不系統,而且自己寫python肯定也是不會寫成OOP風格的。 現在相對系統的學習OOP的概念,感覺。。。很難受! 有點像一開始學ML時候的感覺,就是接
持續集成與持續部署寶典Part 1:將構建環境容器化
成熟 curl命令 設置 doc 包括 探討 完成 2.7 mage 介 紹隨著Docker項目及其相關生態系統逐漸成熟,容器已經開始被更多企業用在了更大規模的項目中。因此,我們需要一套連貫的工作流程和流水線來簡化大規模項目的部署。在本指南中,我們將從代碼開發、持續集成
Django 教程 Part 1:請求與響應
arm pattern 處理 指導 一個 接收 網絡通信 生成 star 版本說明: 因為在撰寫本教程的時候,正逢Django從1.11向2.0轉變的時期,而教程的編寫是從17年8月開始的,前後共花了5個月左右的時間,所以使用的是1.11版本,局面非常尷尬。 實際上Djan
安卓:Could not read cache value from'C:\Users\Username\.gradle\daemon\1.12\registry.bin'
android studio在載入專案的時候報錯: Error:Could not read cache value from'C:\Users\Username\.gradle\daemon\1.12\registry.bin' 參考stack overflow上的一個解決方法,刪除
C#執行緒系列講座(1):BeginInvoke和EndInvoke方法
開發語言:C#3.0 IDE:Visual Studio 2008 本系列教程主要包括如下內容:1. BeginInvoke和EndInvoke方法 2. Thread類 3. 執行緒池 4. 執行緒同步基礎 5. 死鎖 6. 執行
PAT:1005 繼續(3n+1)猜想(25 分)C語言
PAT 1005 繼續(3n+1)猜想(25 分) C語言 #include<stdio.h> int main() { int n; scanf("%d", &n); //輸入n個整數 int zs[n]; for(in
(1) C++:過載、覆蓋與隱藏
C++之中的過載、覆蓋、隱藏 過載 覆蓋 過載與覆蓋的區別 相關程式碼 隱藏 過載 過載是指函式不同的引數表,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式。在同一可訪問區域內被宣告的幾
C++ 錯誤提示:無法將引數1從const char [8] 轉換為char *
#include <iostream> using namespace std; void test(char * p) { cout << p << endl; } int main(void) { test("geerniya")
深入淺出etcd系列Part 1 – etcd架構和程式碼框架
1、緒論 etcd作為華為雲PaaS的核心部件,實現了PaaS大多陣列件的資料持久化、叢集選舉、狀態同步等功能。如此重要的一個部件,我們只有深入地理解其架構設計和內部工作機制,才能更好地學習華為雲Kubernetes容器技術,笑傲雲原生的“江湖”。本系列將從整體框架再細化到內部流程,對etcd的程式碼和設計