【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)