tags: 大一程設-下 東華大學 東華大學資管系 基本程式概念 資管經驗分享

Namespace 命名空間

前言

相信寫過了這麼多程式之後,會發現我們的程式總是跟 main 綁再一起,透過學到了上一章的拆分檔案後,我們也學會了怎麼引入自己撰寫的 library,如果今天程式有非常多的 library,想必我們的專案會越來越大,而在專案擴充的過程當中,我們不免會遇到物件、函式可能會想取同名字的情況發生,但他們想觸發的事情可能或多或少會有不同,依靠一般的 function overloading 可能有時候沒有辦法完全的解決我們「撞名」的問題,甚至是想要取同名字的變數也有可能發生。

namespace 就是為了解決這樣撞名的問題而被設計出來的一種工具。

單純看這樣介紹或許沒辦法非常直白的了解,我們馬上開始吧!

namespace

我們假設以下一個情境來邊講述 namespace 吧!

假設今天有兩間學校,兩間學校都有非常多的學生,而針對學生呢,學校的資料庫會儲存以下的資料。

  • 名字(name)
  • 年紀(age)
  • 身高(height)
  • 體重(weight)
  • 連絡電話(telephone)
  • 手機(cellphone)

如果依照我們現在所學,我們可能很快會寫這樣的類別。

class Student{ string name; int age; double height; double weight; string telephone; string cellphone; };

看起來非常正確簡單明瞭,然而經過探查我們發現,兩間學校針對資料變數型態的方式居然有些不一樣,但變數的名字居然都要求要一樣,那你可能會說,那我再宣告一個 class 就好了,這確實是一個非常有效的解法,那如果今天有第三個學校你可能也會選擇再加一個 class。

接著我們在擴充一下我們的需求,今天學校說除了記錄學生之外,希望我們也能夠幫教職員做資料的儲存,三間學校可能有三種教職員的儲存方式,你可能又會說,那建立三個類別分別給三間學校的教職員吧。

所以你可能會寫出 StudentA、EmployeeA、StudentB、EmployeeB、StudentC、EmployeeC,六個類別,別說了,看過去真的醜到開花。

今天在這樣的需求下我們可以知道,每間學校可能針對資料各有各的儲存方式,拆分成不同的類別確實也是正確的方式,基於這樣的基礎,我們其實能夠加上 namespace 來讓我們程式碼寫得看起來更好看一點。

基本語法

namespace space_name{ //something you define... }

你可以在 namespace 裡面寫所有你需要的東西。而定義在 namespace 裡面的東西,只隸屬於此命名空間,其他人如果要使用就必須取用他。

範例說明

namespace NDHU{ class Student{ string name; int age; double height; double weight; string telephone; string cellphone; }; class Employee{ string name; int age; double height; double weight; string telephone; string cellphone; }; }

這樣一看就非常明瞭,只要是東華大學下的學生跟教職員,我們會如此的定義。也不用因為類別變多就取醜醜的什麼 ABC。而如果類別的細項有所不同也沒關係,名字也是可以取好好的。

namespace NTU{ class Student{ string name; int age; double height; double weight; char telephone[11] ; char cellphone[11]; }; class Employee{ string name; int age; double height; double weight; char telephone[11] ; char cellphone[11]; } }

那你可能會說,我們前面不是學了拆分檔案嗎,那我把不同的類別拆分到各自的檔案不就沒事了嗎?

非常好的解法,但如果今天同樣的一份程式需要 include 上面這兩間學校,在沒有 namespace 的支持下進行這樣類別的命名的時候,你會有兩個 Student 跟 Employee,你的程式碼會不知道要用哪一個類別。

你如果感到疑惑一定是上一篇筆記沒認真看
Orange

但如果今天我們有了 namespace 的支持,我們就可以明確地說,今天我要使用 NDHU 的 Stduent,或是我要使用 NTU 的 Employee。諸如此類。

如何使用

我們馬上來使用命名空間吧。

在看例子之前,我們先來瞭解基本語法,如果 namespace 跟 main 放在同一個檔案裡面,不用 include 標頭檔,也不用寫 using。

#include <iostream> using namespace std; namespace NDHU{ class Student{ public: string name; int age; double height; double weight; string telephone; string cellphone; }; class Employee{ public: string name; int age; double height; double weight; string telephone; string cellphone; }; } int main(){ NDHU::Student ndhu_1; ndhu_1.age = 20; ndhu1.name = "bob"; return 0; }

你可以看到第 18 行,我們看到了久違的 :: 符號,還記得我們說過我們可以叫他 ,所以不難理解,我們這邊實體化了一個 Student,隸屬於 NDHU,所以就是 NDHU 的 Student。

不用被混淆,NDHU::Student 就是物件宣告時的類別型態而已。剩下的取屬性,取用方法都跟我們過去學的一樣。

namespace 在不同的檔案中

透過學了上一章的拆分檔案,我們來把 namespace 給拆分出去吧,都寫在 main.cpp 裡面太佔空間了。

我們假設現在有下面幾個重要的檔案。為了減少篇幅,全部的屬性我都設 public。

  • main.cpp
  • ndhu.h
  • ndhu.cpp
  • ntu.h
  • ntu.cpp

ndhu.h

#include <iostream> using namespace std; namespace NDHU{ class Student{ public: string name; int age; double height; double weight; string telephone; string cellphone; void play(); void run(); void study(); }; class Employee{ public: string name; int age; double height; double weight; string telephone; string cellphone; void work(); void walk(); void teach(); }; }

ndhu.cpp

#include "ndhu.h" using namespace std; void NDHU::Student::play(){ cout << this->name << " is playing!" << endl; } void NDHU::Student::run(){ cout << this->name << " is running!" << endl; } void NDHU::Student::study(){ cout << this->name << " is studying!" << endl; } //以下 Employee 省略

ntu.h

#include <iostream> using namespace std; namespace NTU{ class Student{ public: string name; int age; double height; double weight; char telephone[11]; char cellphone[11]; void play(); void run(); void study(); void fly(); }; class Employee{ public: string name; int age; double height; double weight; char telephone[11]; char cellphone[11]; void work(); void walk(); void teach(); }; }

ntu.cpp

#include "ntu.h" using namespace std; void NTU::Student::play(){ cout << this->name << " is playing!" << endl; } void NTU::Student::run(){ cout << this->name << " is running!" << endl; } void NTU::Student::study(){ cout << this->name << " is studying!" << endl; } void NTU::Student::fly(){ cout << this->name << " is flying!" << endl; } //以下 Employee 省略

NTU 跟 NDHU 兩個最大的差異就是 NTU 的 Student 我加了一個 fly,以及兩者 phone 的基礎型態不同,學過繼承的你可能有另外的解決方式,所以我想說,解沒有絕對,完全看你怎麼組合你的工具!這邊只是為了仔細說明 namespace。

而你也可以看到在 ndhu.cppntu.cpp 我們寫了 NDHU::Stduent::play() / NTU::Student::play(),主要就是在定義該類別底下的 function。

那如果你覺得這樣寫很麻煩,就要來使用 using 了。

相信 using namespace std 這句話困擾大家很長一段時間,我們先透過 ndhu 跟 ntu 來看看這奇怪的東西。

我們把 ndhu.cpp 修改一下。

#include "ndhu.h" using namespace std; using namespace NDHU; void Student::play(){ cout << this->name << " is playing!" << endl; } void Student::run(){ cout << this->name << " is running!" << endl; } void Student::study(){ cout << this->name << " is studying!" << endl; } //以下 Employee 省略

我們在第三行加了 using namespace NDHU,而在這個 namespace 底下有兩個類別,因此在這個 cpp 檔裡面,我們可以不用再寫那麼長的 NDHU::Student 這樣的前綴。

至於剩下的 include 就跟前面的筆記說的一模一樣,只是在過往學的東西再套上 namespace 這個新東西而已。

所以究竟 using namespace std 是甚麼,不難想像,有一個叫做 std 的命名空間,裡面有我們想要用的工具,所以我們需要 using 他,我們最常使用的 cin、cout 其實就被定義在裡面,所以如果今天我們沒有 using std 這個命名空間,我們的 cin、cout 該怎麼寫?

就是 std::cinstd::cout

#include <iostream> int main(){ int a = 0; std::cin >> a; std::cout << a; }

有忽然覺得茅塞頓開嗎?希望有拉XD
Orange

接著我們來看看最後的 main.cpp,假設我們想同時使用 NDHU 跟 NTU,按照前面學的,你可能會寫。

#include <iostream> #include "ndhu.h" #include "ntu.h" using namespace NDHU; using namespace NTU; using namespace std;

然而今天 NDHU 跟 NTU 這兩個命名空間都有 Student 跟 Employee,如果你直接宣告下面這樣會報錯。

Student ndhu_1; Student ntu_1;

他說 Student 是模糊的,NTU 也有一種 Student,代表現在電腦不知道該用哪一個了,所以這就是 namespace 好用的地方,能夠明確點出我是誰。

所以當今天類別撞名,就算你使用了 using 你還是得寫 NDHU::Student / NTU::Stduent 來明確讓編譯器知道誰是誰。

而透過上面的例子,你大概能夠在心裡構建出這樣一張圖。

兩個命名空間互不相干,非常簡潔明瞭,但你如果想要在 A 命名空間使用 B 命名空間的內容,其實也可以在 A 裡面透過 B:: 去取用你要的東西。

Nested Namespace 巢狀命名空間

今天如果需求更上升,需要劃分更明確的需求的時候,namespace 包著其他的 namespace 也是有可能的。

nested namespace syntax

namespace COMPANY{ namespace Finance{...} namespace Engineering{...} namespace SocialMarketing{...} }

一個公司內部可能有多個部門,每個部門各司其職,份內事彼此互不相干,但如果有共同的東西想要被大家一起共用,你可以寫在大家都能夠存取到的地方。

巢狀並沒有限制幾層,想怎麼寫就怎麼寫,但要寫得有效率。
Orange

namespace TSMC{ const string boss = "張忠謀"; // 台積電的老闆是張忠謀 const int age = 35; // 台積電今年 35 歲 void drinkCoffee(){...} // 台積電每個部門的人都可以喝咖啡 void takeDayOff(){...} // 台積電每個人都可以請假 namespace Finance{...} namespace Engineering{...} namespace SocialMarketing{...} }

像上面這樣,Boss、age 跟兩個 function 是不論部門大家都可以用的,但如果今天 Engineering 需要用到 Finance 內的一些工具,就要在 Engineering 內寫 Finance::,簡單明瞭。

總結

這篇筆記主要是讓大家理解 namespace 的運作方式

namespace 讓我們能夠把程式碼區分得更明確,讓彼此不相干的功能不會互相影響,然而只單純使用 namespace 有時候不會完全的讓程式碼簡潔,必要的時候是要結合繼承、運算子多載、函式多載等我們過去學過的功能來讓它的效益最大化。

當然更複雜的還有範本(template)、多型(polymorphism),與此結合程式碼能夠更加完善。

Reference