【C++ 筆記】命名空間(Namespace) - part 30 === 目錄(Table of Contents): [TOC] --- 很感謝你點進來這篇文章。 你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧! 可喜可賀可喜可賀,恭喜本系列終於來到 part 30 啦! 簡介(Introduction) --- 我們最熟悉的命名空間不外乎就是: ```cpp using namespace std; ``` 為什麼要用這個?因為我們不想要打那麼多字,每次打一行程式碼都要加上 `std::` 不是挺麻煩的嗎? ```cpp= std::cout std::endl std::vector <int> a; ``` 但其實除了圖方便以外,命名空間也有其他功能。 假設班上有兩名同學,他們都叫陳柏鈞,為了要區分他們兩個,我們就必定要用到額外的訊息去區分,如一個比較有錢,一個普通,或是一個帥,一個醜等等。 在 C++ 中,假設有個函數叫做 func(),而在另一個 library 也叫做 func(),此時就會有命名衝突(Name conflicts)的問題,因此 namespace 就誕生了。 ### namespace 是啥? 命名空間(namespace)是一種將程式碼邏輯上分組的方法,用以避免不同程式庫或模組之間的名稱衝突。namespace 也提供了一種將相關識別字(identifier)(如變數、函數和類別)分組到單一名稱下的方法。 透過 namespace,我們可以將函數、變數、類別等符號規劃到各自獨立的區域,強化程式碼的可讀性與可維護性。 ### 我們要 namespace 幹嘛? 1. 解決命名衝突(Name Conflicts) 2. 模組化(Modularity) 模組化是一個蠻重要的觀念,比如在開發遊戲時,毫無概念的直接將所有物件都寫在同一個檔案內,那就會導致可讀性大大降低。因此可以透過分類的方式,如將玩家的相關邏輯都寫在 `player.cpp` 裡面,怪物的邏輯則為 `monster.cpp` 等,而主程式的檔案叫做 `main.cpp`,負責引入 `player.cpp`、`monster.cpp` 等檔案。 定義 namespace --- 以 `namespace` 作為關鍵字,後面的 name 為自定義,如同變數名一樣。 要注意的是右括號 `}` 沒有分號作結。 ```cpp= namespace name { // type1 mamber1 // type2 mamber2 // type3 mamber3 . . . . . . } ``` 小範例: ```cpp= namespace nptu{ void departmentName(){ cout << "國立屏東大學 電腦科學與人工智慧學系" << endl; } } ``` ### 巢狀 namespace 定義 註:C++ 17 開始才可以這樣做。 ```cpp= namespace nptu{ void schoolName(){ cout << "國立屏東大學" << endl; } namespace department{ void departmentName(){ cout << "電腦科學與人工智慧學系" << endl; } } } ``` 存取 namespace 成員 --- 使用視域解析運算子(Scope resolution operator)`::`,就是兩個半形冒號。 `name::member` - name:命名空間的名稱。 - member:內部成員,無論是變數、函數等皆可。 如下範例: ```cpp= #include <iostream> using namespace std; namespace nptu{ void departmentName(){ cout << "國立屏東大學 電腦科學與人工智慧學系" << endl; } } int main(){ nptu::departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` ### 存取巢狀 namespace 如下所示的 `nptu::department::departmentName()`,在多套一個 `::` 就可以了。 ```cpp= #include <iostream> using namespace std; namespace nptu{ void schoolName(){ cout << "國立屏東大學" << endl; } namespace department{ void departmentName(){ cout << "電腦科學與人工智慧學系" << endl; } } } int main(){ nptu::schoolName(); nptu::department::departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` ### using 指令(using directive) 如同最開始所說的,`using namespace std;` 可以讓我們省掉打上 `std::` 的動作。 語法:`using namespace name;`,name 是命名空間的名稱。 這個 using 的效果是「將整個命名空間內容引入當前作用域,可直接使用所有成員」。 如下範例所示: ```cpp= #include <iostream> using namespace std; namespace nptu{ void departmentName(){ cout << "國立屏東大學 電腦科學與人工智慧學系" << endl; } } using namespace nptu; int main(){ departmentName(); // 省去打 nptu:: return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` ### using 宣告(using declaration) 語法:`using name::member;` - name:命名空間名稱。 - member:內部成員。 與上一個不同的是,這個語法只會引入單一成員,而不是全部的成員。 ```cpp= #include <iostream> using namespace std; namespace nptu{ void departmentName(){ cout << "國立屏東大學 電腦科學與人工智慧學系" << endl; } } using nptu::departmentName; int main(){ departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` ### using 不能濫用 為什麼會這麼說呢?因為 using 指令會使所有成員變得可存取,這樣很容易發生命名衝突的事件,如果使用宣告方式會減少此類事件發生。 那些內建的 namespace --- ### std namespace std namespace 是 STL(standard library)的一部分,其中有一大部分的標準函數、物件和類別,如 `cin`、`cout`、`vector` 等。 這部分其實我們都很熟悉了,所以就這樣。 ### global namespace 先來看以下的例子: ```cpp= #include <iostream> using namespace std; int n = 5; int main(){ int n = 2; return 0; } ``` `int n = 5` 既沒有被 namespace 的定義給包住,也沒有放到任何函數裡面,這種就屬於 global namespace,也稱為全域變數。 另外他也是可以被 `::` 存取到的: ```cpp= #include <iostream> using namespace std; int n = 5; int main(){ int n = 2; cout << "全域 n : " << ::n << endl; cout << "局域 n : " << n << endl; return 0; } ``` Output: ``` 全域 n : 5 局域 n : 2 ``` ### extending namespace 一樣來看例子: ```cpp= #include <iostream> using namespace std; namespace nptu{ void schoolName(){ cout << "國立屏東大學" << endl; } } namespace nptu{ void departmentName(){ cout << "電腦科學與人工智慧學系" << endl; } } int main(){ nptu::schoolName(); nptu::departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` 正如其名,延伸的命名空間,可以幫這個同名的命名空間新增函數、變數等,為這個命名空間做一個延伸,不管他是在哪個地方定義的。 建立 namespace 的別名 --- 語法:`namespace nn = namespace_name;` - namespace_name:命名空間的原名。 - nn:命名空間的別名。 那既然都說是命名空間的別名了,所以原名也可以直接使用。 ```cpp= #include <iostream> using namespace std; namespace NationalPingTungUniversity{ void schoolName(){ cout << "國立屏東大學" << endl; } void departmentName(){ cout << "電腦科學與人工智慧學系" << endl; } } namespace nptu = NationalPingTungUniversity; int main(){ nptu::schoolName(); nptu::departmentName(); NationalPingTungUniversity::schoolName(); NationalPingTungUniversity::departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 國立屏東大學 電腦科學與人工智慧學系 ``` inline namespace --- 使用 `inline` 關鍵字加在 `namespace` 前面,裡面的成員不用 `::` 就可以直接存取。 ```cpp= #include <iostream> using namespace std; inline namespace nptu{ void schoolName(){ cout << "國立屏東大學" << endl; } void departmentName(){ cout << "電腦科學與人工智慧學系" << endl; } } int main(){ schoolName(); departmentName(); return 0; } ``` Output: ``` 國立屏東大學 電腦科學與人工智慧學系 ``` 匿名命名空間(anonymous namespace) --- 所謂匿名命名空間就是沒有名字的命名空間,其內部定義的函數、變數等識別字的作用域僅限於該檔案內,其他檔案無法存取這些識別字,以達到內部連結(internal linkage)的效果。 :::info 在 C++ 中,「連結(linkage)」是指一個名稱(變數、函數等)在不同翻譯單位(translation unit)之間是否可以被辨識與共用。 ::: 註:翻譯單位是編譯過程的基本單位。`一個翻譯單位 = 一個 .cpp 檔案 + 它所 #include 的所有標頭檔內容`。 而內部連結指的是某個符號(名稱)只能在定義他的原始檔案中(翻譯單位內)被使用,其他檔案看不到也不能存取。 C-style 常見的做法是使用儲存類別 `static`,到了 C++ 就是匿名命名空間。 anonymous namespace 範例: ```cpp= #include <iostream> using namespace std; namespace{ void func(){ cout << "呵呵呵,看不到我吧" << endl; } } int main(){ func(); // 匿名命名空間內的成員可直接存取 return 0; } ``` Output: ``` 呵呵呵,看不到我吧 ``` 總結 --- ### 命名空間 (Namespace) 是什麼? 命名空間是一種將程式碼邏輯分組的機制,用來避免不同函數、變數、類別等識別字名稱衝突。 他提供一個作用域範圍,讓同名的識別字可以在不同命名空間中共存,不會互相干擾。 如:std 是 STL 的命名空間,裡面包含了 cout、vector 等標準物件和函數。 ### 為什麼需要命名空間? 避免命名衝突:如多個函數或變數重名,namespace 可區隔使用。 模組化設計:利於大型系統的架構與維護。 ### 定義命名空間 ```cpp= namespace name { // 成員:函數、變數、類別等 } ``` 右括號 `}` 後不需加分號。 可定義巢狀命名空間(C++17 起支援): ```cpp= namespace nptu { namespace department { void departmentName() { ... } } } ``` ### 存取命名空間成員 使用視域解析運算子(Scope resolution operator)`::`,如: ```cpp= nptu::departmentName(); nptu::department::departmentName(); ``` nptu 是命名空間的名稱,`::` 後面接的是內部成員。 ### using 指令與宣告 - `using namespace name;`:將整個命名空間引入當前作用域,成員可直接使用,省略 `name::`。 - `using name::member;`:只引入單一成員。 ### 內建命名空間 - std namespace:標準函式庫所在命名空間。 - global namespace:未被任何命名空間包覆的全域範圍,可用 :: 存取。 - 延伸命名空間:同一命名空間可在多處定義,成員會合併。 - 命名空間別名: - `namespace nn = namespace_name;` - inline namespace:使用 `inline` 關鍵字,裡面成員可直接存取,無須加 `::`。 - 匿名命名空間:無名的命名空間,內部成員只在該檔案可見,達到內部連結效果。 參考資料 --- [Translation units and linkage (C++) | Microsoft Learn](https://learn.microsoft.com/en-us/cpp/cpp/program-and-linkage-cpp?view=msvc-170) [簡介名稱空間](https://openhome.cc/Gossip/CppGossip/Namespace.html) [C++ Inline Namespaces and Usage of the "using" Directive Inside Namespaces - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/cpp-inline-namespaces-and-usage-of-the-using-directive-inside-namespaces/) [Namespace in C++ - GeeksforGeeks](https://www.geeksforgeeks.org/namespace-in-c/) [C++ 命名空间 | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-namespaces.html)