National Taiwan University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

2

Local AI

--

Uedu Code

--

台灣大學 AQI 70 24°C PM2.5 17
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 16

命名空間

定義命名空間、:: 與 using
Interaction實作 15 minute(s)

Learning Objectives

  • 說明命名空間解決的問題:不同程式庫裡的同名函式與變數
  • 定義命名空間,用範圍解析運算子 :: 指定名稱屬於哪一個命名空間
  • 使用巢狀命名空間與 C++17 的 namespace a::b 簡寫
  • 分辨 using 宣告與 using 指令,說明為什麼標頭檔裡不要寫 using namespace std
  • 用 ::name 指定全域命名空間裡的名稱

名稱衝突

程式越寫越大,或用了別人寫的程式庫,就很容易出現同名的東西:你的程式有一個 play() 播放音樂, 另一個程式庫也有一個 play() 播放影片。兩個同名、參數也相同的函式放在一起,編譯器無法分辨,會直接報錯。

命名空間(namespace)替名稱加上一個「姓氏」,把它們分在不同的區域:

namespace music {
    void play() { ... }
}
namespace movie {
    void play() { ... }
}

music::play();   // 音樂的 play
movie::play();   // 電影的 play

:: 叫做範圍解析運算子(scope resolution operator),music::play 讀作「music 裡的 play」。

你其實從第一課就在用命名空間了:cout、string、vector 都放在標準函式庫的命名空間 std 裡,完整的名字是 std::cout、std::string。 程式開頭的 using namespace std; 讓你可以省略 std::,本課會說明它到底做了什麼,以及什麼時候不該用。

用命名空間區分同名的變數與函式

這個範例沒有寫 using namespace std;,所以輸出都寫成 std::cout。右邊最上方是全域變數區,兩個 volume 顯示成 music::volume 和 movie::volume,程式還沒進入 main 就先初始化好了。函式框架的標題只顯示 play,看程式碼的高亮行就能分辨是哪一個 play。

定義與巢狀命名空間

定義命名空間的寫法:

namespace 名稱 {
    // 變數、函式、類別……
}                    // 結尾的大括號後面不用分號

幾個規則:

  • 命名空間可以分很多次寫:在不同地方、甚至不同檔案寫兩次 namespace music { ... },裡面的東西會合在同一個命名空間。
  • 命名空間可以巢狀:namespace city { namespace library { ... } },裡面的名稱從外面要寫 city::library::名稱。 C++17 起可以簡寫成 namespace city::library { ... }。
  • 在命名空間裡面使用名稱時,先找自己這一層,找不到再往外層找,所以 city::library 裡可以直接用 city 的名稱。
  • 名字太長時可以取別名:namespace lib = city::library;,之後寫 lib::info()。(本課的直譯器不支援別名,在自己的電腦上可以使用。)

巢狀命名空間

city 裡面有 library 和 gym 兩個命名空間,各有一個 openHour。第 16 行用 C++17 的簡寫把 gym 加進 city。注意第 11 行:info 在 city::library 裡,只寫 name 也能找到外層的 city::name。

想一想::: 與巢狀

1.

下面的程式會印出什麼?

namespace a { int x = 1; }
namespace b { int x = 2; }
int x = 3;

int main() {
    int x = 4;
    cout << a::x << b::x << ::x << x;
}
2.

下面的程式會印出什麼?

namespace outer {
    int v = 1;
    namespace inner {
        int v = 2;
        int get() { return v; }
    }
    int get() { return v; }
}

int main() {
    cout << outer::inner::get() << outer::get();
}
3.

程式沒有寫 using namespace std;,也沒有任何 using 宣告。下列哪一行可以正確印出 1?

第 1 次作答

using 宣告與 using 指令

每次都寫全名很長,using 有兩種寫法可以省略:

寫法 名稱 效果
using std::cout; using 宣告(using-declaration) 只把一個名稱帶進目前的範圍
using namespace std; using 指令(using-directive) 讓命名空間裡的所有名稱都可以不寫前綴

兩種都可以寫在檔案最上面(整個檔案有效),也可以寫在函式裡(只在那個函式裡有效)。

using 宣告比較精確:用到什麼就帶進什麼,不會意外引進其他名稱。using 指令最方便,但會把一大堆名稱一次攤開, 如果兩個命名空間都被攤開、又剛好有同名的東西,只寫名稱時編譯器就不知道你指的是哪一個,會報錯說這個名稱有歧義(ambiguous),這時仍然要寫全名。

using 宣告與 using 指令

第 3、4 行用 using 宣告帶進 cout 和 endl,所以整個檔案都不用寫 std::。useDeclaration 只帶進 circleArea 一個名稱;useDirective 用 using 指令帶進整個 geometry。

std 與 using namespace std

標準函式庫的所有名稱都在 std 裡,而且數量非常多:除了 cout、string,還有 count、find、max、min、swap、distance…… 寫了 using namespace std;,這些名稱全都攤開在你的程式裡。如果你自己也定義了同名的東西,就可能出問題:

#include <iostream>
#include <algorithm>       // 裡面有 std::count
using namespace std;

int count = 0;             // 自己的全域變數也叫 count

int main() {
    count++;               // g++ 報錯:count 有歧義(是 ::count 還是 std::count?)
}

小程式、練習題寫 using namespace std; 很方便,本課程的範例也這樣寫。但有兩個場合要避免:

  • 標頭檔(.h)裡不要寫 using namespace std;:標頭檔會被 #include 到別人的程式裡,等於替所有引用它的人都攤開了 std, 他們完全不知情,卻可能因此遇到名稱衝突。標頭檔裡一律寫全名 std::string、std::vector。
  • 大型專案通常規定寫全名,或只用 using std::cout; 這種 using 宣告,一眼就看得出每個名稱來自哪裡。

全域命名空間:不屬於任何命名空間的名稱(例如寫在所有函式外面的全域變數)屬於全域命名空間。 被區域變數或其他名稱遮住時,可以用前面不加名稱的 ::name 指定全域的那一個。

區域、命名空間與全域的同名變數

三個變數都叫 level:全域的 1、game 裡的 10、show 裡的區域變數 100。看第 11~13 行分別找到哪一個。

想一想:using 的使用

1.

下面的程式用 g++ 編譯會怎樣?

#include <iostream>
#include <algorithm>
using namespace std;

int count = 0;

int main() {
    count++;
    cout << count;
}
2.

為什麼不建議在標頭檔(.h)裡寫 using namespace std;?

3.

下列哪一行只把 std::string 一個名稱帶進目前的範圍?

第 1 次作答

動手寫:兩個 area 函式

定義兩個命名空間 circle 和 square,各有一個 double area(double) 函式:圓的面積是 3.14159 * r * r,正方形的面積是 side * side。main 已經寫好,執行後的輸出要和預期相同。

你的輸出
預期輸出

重點回顧

  • 命名空間替名稱分區,讓不同程式庫裡的同名函式、變數可以共存
  • namespace 名稱 { ... } 定義命名空間,可以分次寫、可以巢狀;C++17 可寫成 namespace a::b { ... }
  • :: 是範圍解析運算子:a::x 是 a 裡的 x,::x 是全域命名空間的 x
  • 在命名空間裡找名稱時,先找自己這一層,再往外層找
  • using std::cout;(using 宣告)只帶進一個名稱;using namespace std;(using 指令)帶進全部名稱,可能造成名稱衝突
  • 標準函式庫都在 std 裡;標頭檔裡不要寫 using namespace std;,一律寫全名
這一課完成了!
Next lesson:多執行緒
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。