【C++ 筆記】命名空間(Namespace) - part 30

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

可喜可賀可喜可賀,恭喜本系列終於來到 part 30 啦!

簡介(Introduction)

我們最熟悉的命名空間不外乎就是:

1
using namespace std;

為什麼要用這個?因為我們不想要打那麼多字,每次打一行程式碼都要加上 std:: 不是挺麻煩的嗎?

1
2
3
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.cppmonster.cpp 等檔案。

定義 namespace

namespace 作為關鍵字,後面的 name 為自定義,如同變數名一樣。

要注意的是右括號 } 沒有分號作結。

1
2
3
4
5
6
7
namespace name {
// type1 mamber1
// type2 mamber2
// type3 mamber3
. . .
. . .
}

小範例:

1
2
3
4
5
namespace nptu{
void departmentName(){
cout << "國立屏東大學 電腦科學與人工智慧學系" << endl;
}
}

巢狀 namespace 定義

註:C++ 17 開始才可以這樣做。

1
2
3
4
5
6
7
8
9
10
namespace nptu{
void schoolName(){
cout << "國立屏東大學" << endl;
}
namespace department{
void departmentName(){
cout << "電腦科學與人工智慧學系" << endl;
}
}
}

存取 namespace 成員

使用視域解析運算子(Scope resolution operator)::,就是兩個半形冒號。

name::member

  • name:命名空間的名稱。
  • member:內部成員,無論是變數、函數等皆可。

如下範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

using namespace std;

namespace nptu{
void departmentName(){
cout << "國立屏東大學 電腦科學與人工智慧學系" << endl;
}
}

int main(){
nptu::departmentName();
return 0;
}

Output:

1
國立屏東大學 電腦科學與人工智慧學系

存取巢狀 namespace

如下所示的 nptu::department::departmentName(),在多套一個 :: 就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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:

1
2
國立屏東大學
電腦科學與人工智慧學系

using 指令(using directive)

如同最開始所說的,using namespace std; 可以讓我們省掉打上 std:: 的動作。

語法:using namespace name;,name 是命名空間的名稱。

這個 using 的效果是「將整個命名空間內容引入當前作用域,可直接使用所有成員」。

如下範例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

using namespace std;

namespace nptu{
void departmentName(){
cout << "國立屏東大學 電腦科學與人工智慧學系" << endl;
}
}

using namespace nptu;

int main(){
departmentName(); // 省去打 nptu::
return 0;
}

Output:

1
國立屏東大學 電腦科學與人工智慧學系

using 宣告(using declaration)

語法:using name::member;

  • name:命名空間名稱。
  • member:內部成員。

與上一個不同的是,這個語法只會引入單一成員,而不是全部的成員。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

using namespace std;

namespace nptu{
void departmentName(){
cout << "國立屏東大學 電腦科學與人工智慧學系" << endl;
}
}

using nptu::departmentName;

int main(){
departmentName();
return 0;
}

Output:

1
國立屏東大學 電腦科學與人工智慧學系

using 不能濫用

為什麼會這麼說呢?因為 using 指令會使所有成員變得可存取,這樣很容易發生命名衝突的事件,如果使用宣告方式會減少此類事件發生。

那些內建的 namespace

std namespace

std namespace 是 STL(standard library)的一部分,其中有一大部分的標準函數、物件和類別,如 cincoutvector 等。

這部分其實我們都很熟悉了,所以就這樣。

global namespace

先來看以下的例子:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

using namespace std;

int n = 5;

int main(){
int n = 2;
return 0;
}

int n = 5 既沒有被 namespace 的定義給包住,也沒有放到任何函數裡面,這種就屬於 global namespace,也稱為全域變數。

另外他也是可以被 :: 存取到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

using namespace std;

int n = 5;

int main(){
int n = 2;

cout << "全域 n : " << ::n << endl;
cout << "局域 n : " << n << endl;

return 0;
}

Output:

1
2
全域 n : 5
局域 n : 2

extending namespace

一樣來看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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:

1
2
國立屏東大學
電腦科學與人工智慧學系

正如其名,延伸的命名空間,可以幫這個同名的命名空間新增函數、變數等,為這個命名空間做一個延伸,不管他是在哪個地方定義的。

建立 namespace 的別名

語法:namespace nn = namespace_name;

  • namespace_name:命名空間的原名。
  • nn:命名空間的別名。

那既然都說是命名空間的別名了,所以原名也可以直接使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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:

1
2
3
4
國立屏東大學
電腦科學與人工智慧學系
國立屏東大學
電腦科學與人工智慧學系

inline namespace

使用 inline 關鍵字加在 namespace 前面,裡面的成員不用 :: 就可以直接存取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;

inline namespace nptu{
void schoolName(){
cout << "國立屏東大學" << endl;
}
void departmentName(){
cout << "電腦科學與人工智慧學系" << endl;
}
}

int main(){
schoolName();
departmentName();
return 0;
}

Output:

1
2
國立屏東大學
電腦科學與人工智慧學系

匿名命名空間(anonymous namespace)

所謂匿名命名空間就是沒有名字的命名空間,其內部定義的函數、變數等識別字的作用域僅限於該檔案內,其他檔案無法存取這些識別字,以達到內部連結(internal linkage)的效果。

:::info
在 C++ 中,「連結(linkage)」是指一個名稱(變數、函數等)在不同翻譯單位(translation unit)之間是否可以被辨識與共用。
:::

註:翻譯單位是編譯過程的基本單位。一個翻譯單位 = 一個 .cpp 檔案 + 它所 #include 的所有標頭檔內容

而內部連結指的是某個符號(名稱)只能在定義他的原始檔案中(翻譯單位內)被使用,其他檔案看不到也不能存取。

C-style 常見的做法是使用儲存類別 static,到了 C++ 就是匿名命名空間。

anonymous namespace 範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

using namespace std;

namespace{
void func(){
cout << "呵呵呵,看不到我吧" << endl;
}
}

int main(){
func(); // 匿名命名空間內的成員可直接存取
return 0;
}

Output:

1
呵呵呵,看不到我吧

總結

命名空間 (Namespace) 是什麼?

命名空間是一種將程式碼邏輯分組的機制,用來避免不同函數、變數、類別等識別字名稱衝突。

他提供一個作用域範圍,讓同名的識別字可以在不同命名空間中共存,不會互相干擾。

如:std 是 STL 的命名空間,裡面包含了 cout、vector 等標準物件和函數。

為什麼需要命名空間?

避免命名衝突:如多個函數或變數重名,namespace 可區隔使用。

模組化設計:利於大型系統的架構與維護。

定義命名空間

1
2
3
namespace name {
// 成員:函數、變數、類別等
}

右括號 } 後不需加分號。

可定義巢狀命名空間(C++17 起支援):

1
2
3
4
5
namespace nptu {
namespace department {
void departmentName() { ... }
}
}

存取命名空間成員

使用視域解析運算子(Scope resolution operator)::,如:

1
2
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

簡介名稱空間

C++ Inline Namespaces and Usage of the “using” Directive Inside Namespaces - GeeksforGeeks

Namespace in C++ - GeeksforGeeks

C++ 命名空间 | 菜鸟教程