<article class="mx-1 my-1">
<h3>目錄</h3>
<ul>
<li><a href="#%E7%9B%AE%E7%9A%84">目的</a></li>
<li><a href="#%E5%95%8F%E9%A1%8C%E8%88%87%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88">問題與解決方案</a>
<ul>
<li><a href="#namespace-%E5%90%8D%E7%A8%B1%E6%B1%A1%E6%9F%93%E8%88%87%E8%A1%9D%E7%AA%81">namespace-解決名稱污染與衝突</a></li>
<li><a href="#using-declaration/using-directive-%E6%B8%9B%E5%B0%91%E4%B8%8D%E6%96%B7%E5%87%BA%E7%8F%BE%E7%9A%84%E8%B3%87%E6%A0%BC%E4%BF%AE%E9%A3%BE%E8%A9%9E">using declaration / using directive-減少不斷出現的資格修飾詞</a></li>
<li><a href="#using-declaration/using-directive-%E5%B8%B6%E4%BE%86%E7%9A%84%E5%8D%B1%E9%9A%AA%E6%80%A7">using declaration / using directive-帶來的危險性</a></li>
<li><a href="#using-declaration/using-directive-%E5%B7%AE%E5%88%A5%E4%B8%8D%E5%83%85%E5%83%85%E5%9C%A8%E6%96%BC%E5%AE%A3%E5%91%8A%E7%AF%84%E5%9C%8D">using declaration / using directive-差別不僅僅在於宣告範圍</a></li>
</ul>
</li>
</ul>
<h3 id="目的">目的</h3>
使用Namespace可以提供、解決以下問題:
<ul>
<li>名稱污染、衝突</li>
<li>程式碼模組化</li>
<li>有邏輯的組織程式碼</li>
</ul>
此外以下為本文使用的名稱(參考[1])所對應之意義
<ul>
<li>資格修飾詞:std::、::(全域)</li>
<li>using declaration:using std::string;</li>
<li>using direction:using namespace std;</li>
</ul>
<!--more-->
<h3 id="問題與解決方案">問題與解決方案</h3>
<h4 id="namespace-名稱污染與衝突">namespace-解決名稱污染與衝突</h4>
當我們定義函式庫時沒有使用Namespace,這個函式庫被使用者#include後,將會污染使用者#include時的程式碼區塊。 下面的函式,只要#include,就可以使用:
<pre>----// my_lib.cpp
bool isEqual(int a, int b) {
return a == b;
}
----// main.cpp
#include
#include "my_lib.cpp"
int main() {
std::cout << (isEqual(0, 1) ? "true" : "false") << std::endl;
return 0;
}
----output:
false
</pre>
但當我們引入多個函式庫,並且他們擁有同樣命名名稱的函式時,將會引起編譯錯誤,因為不知道要使用哪一個:
<pre>----// my_lib.cpp
bool isEqual(int a, int b) {
return a == b;
}
+---// another_lib.cpp
+ bool isEqual(int a, int b) {
+ return a != b; // 一個荒謬的函式
+ }
----// main.cpp
#include
#include "my_lib.cpp"
+ #include "another_lib.cpp"
int main() {
+ std::cout << (isEqual(0, 1) ? "true" : "false") << std::endl;
return 0;
}
----編譯錯誤,重複定義
</pre>
此時我們可以利用namespace加上資格修飾詞,明確知道要使用哪個程式碼
<pre>----// my_lib.cpp
+ namespace my_lib {
bool isEqual(int a, int b) {
return a == b;
}
+ }
----// another_lib.cpp
+ namespace another {
bool isEqual(int a, int b) {
return a != b; // 一個荒謬的函式
}
+ }
----// main.cpp
#include
#include "my_lib.cpp"
#include "another_lib.cpp"
int main() {
+ std::cout << (my_lib::isEqual(0, 1) ? "true" : "false") << std::endl;
+ std::cout << (another::isEqual(0, 1) ? "true" : "false") << std::endl;
return 0;
}
----output:
false
true
</pre>
其實這種方式我們早已習慣,因為全域、結構(struct, class, enum/enum class[2]等)也算是一種Namespace:
<pre>----// main.cpp
#include
char c = 'g';
struct s {
static const char c = 's';
// 為何要使用static修飾詞,這使得c屬於s,而不屬於由s定義的物件,方便舉例
// 為何要使用const修飾詞,請參考[3]
};
int main() {
char c = 'l';
std::cout << "local c is char: " << c << std::endl;
std::cout << "global c is char: " << ::c << std::endl;
std::cout << "c in struct s is char: " << ::s::c << std::endl;
return 0;
}
----output:
local c is char: l
global c is char: g
c in struct s is char: s
</pre>
需注意:名稱覆蓋不限定在同一種宣告,例如:
<pre>----// main.cpp
#include
void max();
int main() {
int max = 2147483647;
std::cout << "local max is integer: " << max << std::endl;
std::cout << "want to invoke global max function: " << max() << std::endl;
return 0;
}
----編譯錯誤:max(),max是一個整數,不是一個函式
</pre>
<h4 id="using-declaration/using-directive-減少不斷出現的資格修飾詞">using declaration / using directive-減少不斷出現的資格修飾詞</h4>
在上述的程式碼中,我們可以看見使用iostream的cout等,都須要使用資格修飾詞std::,我們可以使用using declaration來減少這種限制:
<pre>----// my_lib.cpp
#include
namespace my_lib {
+ using std::cout;
int print() {
+ cout << 123 << std::endl;
}
}
----// main.cpp
#include "my_lib.cpp"
int main() {
my_lib::print();
return 0;
}
----output:
123
</pre>
仔細觀察上一個例子,我們可以發現std::endl,還是得使用資格修飾詞std::,若我們想運用std所有的成員,我們可以運用using directive:
<pre>----// my_lib.cpp
#include
namespace my_lib {
+ using namespace std;
int print() {
+ cout << 123 << endl;
}
}
----// main.cpp
#include "my_lib.cpp"
int main() {
my_lib::print();
return 0;
}
----output:
123
</pre>
<h4 id="using-declaration/using-directive-帶來的危險性">using declaration / using directive-帶來的危險性</h4>
我們必須特別小心地運用using declaration / using directive,以下我們以using directive(using declaration的影響是一樣的,只是看你宣告了哪個成員)一一舉例說明:
<ol>
<li>將namespace std宣告於namespace my_lib底下,這意味著我只要存取my_lib即可存取std成員:
<pre>----my_lib.cpp(or my_lib.hpp)
#include
namespace my_lib {
+ using namespace std;
void print() {
cout << "123" << endl;
}
}
----main.cpp
#include "my_lib.cpp"
int main() {
+ my_lib::cout << "456" << endl;
return 0;
}
----output
123
456
</pre>
</li>
<li>將namespace std宣告在namespace global,這種狀況更糟糕,限制更小,只要#include該檔案,即可使用std成員:
<pre>----my_lib.cpp(or my_lib.hpp)
#include
+ using namespace std;
namespace my_lib {
void print() {
cout << "123" << endl;
}
}
----main.cpp
#include "my_lib.cpp"
int main() {
+ cout << "456" << endl;
return 0;
}
----output
123
456
</pre>
</li>
<li>這下子看起來好像不管在哪裡使用using directive都會有危險性?不,我們再重新看一下上面第二點的描述,你發現了嗎?「只要#include該檔案,就可以使用std成員」,那我們不要#include就好了呀!,咦!不#include,那我們要怎麼取得該檔案?利用標頭/實作檔,我們將using directive儘量放在實作檔,就可以大致解決問題了:
<pre>+---my_lib.hpp
+ namespace my_lib {
+ void print();
+ }
----my_lib.cpp
+ #include "my_lib.hpp"
#include
using namespace std;
void my_lib::print() {
cout << "123" << endl;
}
----main.cpp
+ #include "my_lib.hpp"
int main() {
- // cout << "456" << endl; // 錯誤,iostream作用域沒有延伸到my_lib.hpp
- // std::cout << "456" << std::endl; // 同上
return 0;
}
----編譯錯誤
</pre>
可是!我們可能並不能完全解決名稱污染的問題,因為我們有些情況下必須將檔案#include在標頭檔(下面明確提供函式getMatrix介面給使用者),這時候就算我們沒有使用using directive,#include該檔案的使用者,仍然可以自行宣告std從而存取到vector成員(如下),良好的模組分離終究是需要相當技術的:
<pre>+---my_lib.hpp
+ #include
namespace my_lib {
+ std::vector getMatrix();
void print();
}
----main.cpp
#include "my_lib.hpp"
int main() {
+ std::vector vec{1};
return 0;
}
----output
(編譯成功)
</pre>
</li>
</ol>
<h4 id="using-declaration/using-directive-差別不僅僅在於宣告範圍">using declaration / using directive-差別不僅僅在於逐個、全部宣告</h4>
using declaration會實際的宣告(真是人如其名)一個名稱在它所處的scope,另外,如同宣告區域變數一樣,也會覆蓋外圍的namespace(全域):
<pre>----main.cpp
#inlcude
using namespace std;
namespace eg {
int i = 0;
}
int i = 2;
int main() {
using eg::i;
// int i = 1; // 錯誤,已被using declaration宣告
cout << i;
cout << ::i;
cout << eg::i;
return 0;
}
----output:
0
1
0
</pre>
using directive則僅是表示你在所處的scope可以使用某namespace的成員,而這種情況,如果同名將會導致模稜兩可:
<pre>----main.cpp
#include
using namespace std;
namespace eg {
char c = 'e';
}
char c = 'g';
int main () {
char c = 'l'; // 若沒有宣告local c,將會造成模稜兩可
using namespace eg; // using directive 與 宣告local c 的順序不重要
cout << c << endl;
cout << ::c << endl;
cout << eg::c << endl;
return 0;
}
----output:
l
g
e
</pre>
<h3>參考資料</h3>
<ol>
<li>TCPL(The C++ Programming Language)國際中文版 - Bjarne Stroustrup</li>
<li><a href="https://stackoverflow.com/questions/18335861/why-is-enum-class-preferred-over-plain-enum/18335954">Why is enum class preferred over plain enum?</a></li>
<li><a href="https://stackoverflow.com/questions/9656941/why-cant-i-initialize-non-const-static-member-or-static-array-in-class">Why I can't initialize static data members in class?</a></li>
</ol>
</article>