目錄
命名空間
為什么要使用命名空間
什么是命名空間
命名空間的使用方式
關鍵點總結
命名空間的嵌套使用
匿名命名空間
跨模塊調用問題
命名空間可以多次定義
總結
首先從C++的hello,world程序入手,來認識一下C++語言
#include <iostream>
using namespace std;int main(int argc, char * argv[]){cout << "hello,world" << endl;return 0;
}>
(1)iostream是C++的頭文件,為什么沒有后綴?—— 模板階段再作講解
(2)using namespace std是什么含義?—— 命名空間的使用
(3) cout << "hello,world" << endl; 實現了輸出hello,world的功能,如何理解這行代碼?—— cout的使用
命名空間
為什么要使用命名空間
一個大型的工程往往是由若干個人獨立完成的,不同的人分別完成不同的部分,最后再組合成一個完整的程序。由于各個頭文件是由不同的人設計的,有可能在不同的頭文件中用了相同的名字來命名所定義的類或函數,這樣在程序中就會出現名字沖突。不僅如此,有可能我們自己定義的名字會與C++庫中的名字發生沖突。
名字沖突就是在同一個作用域中有兩個或多個同名的實體,為了解決命名沖突 ,C++中引入了命名空間,所謂命名空間就是一個可以由用戶自己定義的作用域,在不同的作用域中可以定義相同名字的變量,互不干擾,系統能夠區分它們。
C語言中避免名字沖突,只能進行起名約定
int hw_cpp_tom_num = 100;
int wd_cpp_bob_num = 200;
什么是命名空間
命名空間又稱為名字空間,是程序員命名的內存區域,程序員根據需要指定一些有名字的空間域,把一些全局實體分別存放到各個命名空間中,從而與其他全局實體分隔開。通俗的說,每個名字空間都是一個名字空間域,存放在名字空間域中的全局實體只在本空間域內有效。名字空間對全局實體加以域的限制,從而合理的解決命名沖突。
C++中定義命名空間的基本格式如下:
namespace wd
{
int val1 = 0;
char val2;
}// end of namespace wd
在聲明一個命名空間時,大括號內不僅可以存放變量,還可以存放以下類型:
變量、常量、函數、結構體、引用、類、對象、模板、命名空間等,它們都稱為實體
(1)請嘗試定義命名空間,并在命名空間中定義實體。
(2)命名空間中的實體如何使用呢?
命名空間的使用方式
命名空間一共有三種使用方式,分別是using編譯指令、作用域限定符、using聲明機制。
-
作用域限定符
每次要使用某個命名空間中的實體時,都直接加上作用域限定符::,例如:
namespace wd { int number = 10; void display() {//cout,endl都是std空間中的實體,所以都加上'std::'命名空間std::cout << "wd::display()" << std::endl; } }//end of namespace wdint main(void) {std::cout << "wd::number = " << wd::number << endl;wd::display();return 0; }
好處:準確,只要命名空間中確實有這個實體,就能夠準確調用(訪問)
壞處:繁瑣
-
using編譯指令
我們接觸的第一個C++程序基本上都是這樣的,其中std代表的是標準命名空間。
#include <iostream> using namespace std; //using編譯指令int main(int argc, char * argv[]){cout << "hello,world" << endl;return 0; }
其中第二行就使用了using編譯指令。如果一個名稱空間中有多個實體,使用using編譯指令,就會把該空間中的所有實體一次性引入到程序之中;對于初學者來說,如果對一個命名空間中的實體并不熟悉時,直接使用這種方式,有可能還是會造成名字沖突的問題,而且出現錯誤之后,還不好查找錯誤的原因,比如下面的程序就會報錯,當然該錯誤是人為造成的。
#include <iostream> using namespace std; double cout() { return 1.1; } int main(void) { cout(); return 0; }
-
using聲明機制
using聲明機制的作用域是從using語句開始,到using所在的作用域結束。要注意,在同一作用域內用using聲明的不同的命名空間的成員不能有同名的成員,否則會發生重定義。
作用域示例
namespace A {void foo() {}int x = 10;
}void func1() {using A::foo; // 作用域開始foo(); // 正確:調用 A::foo()
} // 作用域結束(到 func1 末尾)void func2() {// foo(); // 錯誤:此處無法使用 A::foo
}
重定義示例
namespace B {void foo() {} // 與 A::foo 同名但實現不同int x = 20; // 與 A::x 同名
}// 全局作用域中的 using 聲明
using A::x; // 引入 A::x
using B::x; // 錯誤:重定義!x 已存在(來自 A)int main() {// x = 30; // 若單獨使用一個 using 聲明,此處可訪問return 0;
}
合法作用域示例
void safe_func1() {using A::foo;foo(); // 調用 A::foo
}void safe_func2() {using B::foo;foo(); // 調用 B::foo(不同作用域,無沖突)
}
關鍵點總結
-
作用域限定:
using
聲明的作用域從聲明處開始,到當前代碼塊({}
)結束 -
沖突規則:同一作用域內不允許通過
using
引入同名標識符 -
隔離方案:通過函數/代碼塊劃分作用域可避免沖突
-
與
using namespace
區別:using
聲明精確引入單個標識符,using namespace
會污染當前作用域的所有標識符
實際開發中建議:
-
優先在函數內局部使用
using
聲明 -
避免在頭文件的全局作用域使用
using
-
對頻繁使用的標識符,使用
namespace alias
(如namespace fs = std::filesystem;
)
#include <iostream>
using std::cout;
using std::endl;
namespace wd
{
int number = 10;
void display()
{
cout << "wd::display()" << endl;
}
}//end of namespace wd
using wd::number;
using wd::display;
int main(void)
{cout << "wd::number = " << number << endl;wd::display();return 0;
}
在這三種方式之中,我們推薦使用的就是第三種,需要哪個實體的時候就引入到程序中,不需要的實體就不引入,盡可能減小犯錯誤的概率。
命名空間的嵌套使用
類似于文件夾下還可以建立文件夾,命名空間中還可以定義命名空間。那么內層命名空間中的實體如何訪問呢?嘗試一下
匿名命名空間
命名空間還可以不定義名字,不定義名字的命名空間稱為匿名命名空間(簡稱匿名空間),其定義方式如下:
namespace {
int val1 = 10;
void func();
}//end of anonymous namespace
使用匿名空間中實體時,可以直接使用,也可以加上作用域限定符(沒有空間名),但是如果匿名空間中定義了和全局位置中同名的實體,會有沖突,即使使用::作用域限定符也無法訪問到匿名空間中重名的實體,只能訪問到全局的實體。
在C++代碼中可以直接使用一些C語言的函數,就是通過匿名空間實現(體現了C++對C的兼容性),在本文件使用匿名命名空間的實體時不必用命名空間限定。
printf本身可以直接用,和C語言中的效果一致。但是經過匿名空間改寫后,效果不一樣了 —— 不要隨意改寫
匿名空間注意事項:
(1)匿名空間不要定義與全局空間中同名的實體;
(2)匿名空間中支持改寫兼容C語言的函數,但是最好不要改寫;
(3)匿名空間中的實體不能跨模塊調用。
補充:匿名空間和有名空間(具名空間)統稱為命名空間(名稱空間)。
跨模塊調用問題
一個.c/.cc/*.cpp的文件可以稱為一個模塊。
(1)全局變量和函數是可以跨模塊調用的
externA.cc
externB.cc
對externA.cc和externB.cc聯合編譯,實現跨模塊調用
(2)有名命名空間中的實體可以跨模塊調用
命名空間中的實體跨模塊調用時,要在新的源文件中再次定義同名的命名空間,進行聯合編譯時,這兩次定義被認為是同一個命名空間。
使用規則:如果要同時從全局位置和命名空間中外部引入實體,要么讓它們不要重名,要么在使用時采取作用域限定的方式。
using wd2 :: num 放在test2外面就發生沖突,使用using namespace wd2 也會發生沖突,看似不在一個作用域也會沖突,所以就用上面的解決辦法。
(3)靜態變量和函數只能在本模塊內部使用
(4)匿名空間的實體只能在本模塊內部使用
匿名空間中的實體只能在本文件的作用域內有效,它的作用域是從匿名命名空間聲明開始到本文件結束。
補充:extern外部引入的方式適合管理較小的代碼組織,用什么就引入什么,但是如果跨模塊調用的關系不清晰,很容易出錯;
include頭文件的方式在代碼組織上更清晰,但是會一次引入全部內容,相較而言效率比較低。
命名空間可以多次定義
函數可以聲明多次,但是只能定義一次;命名空間可以多次定義。
在同一個源文件中可以多次定義同名的命名空間,被認為是同一個命名空間,所以不能進行重復定義。
在命名空間中可以聲明實體、定義實體,但是不能使用實體。使用命名空間中的實體一定在命名空間之外,可以理解為命名空間只是用來存放實體。
總結
命名空間的作用:
-
避免命名沖突:命名空間提供了一種將全局作用域劃分成更小的作用域的機制,用于避免不同的代碼中可能發生的命名沖突問題;
-
組織代碼:將相關的實體放到同一個命名空間;
-
版本控制:不同版本的代碼放到不同的命名空間中;
總之,需要用到代碼分隔的情況就可以考慮使用命名空間。
還有一個隱藏的好處:聲明主權。
下面引用當前流行的命名空間使用指導原則:
-
提倡在已命名的名稱空間中定義變量,而不是直接定義外部全局變量或者靜態全局變量。
-
如果開發了一個函數庫或者類庫,提倡將其放在一個命名空間中。
-
對于using 聲明,首先將其作用域設置為局部而不是全局。
-
不要在頭文件中使用using編譯指令,這樣,使得可用名稱變得模糊,容易出現二義性。
-
包含頭文件的順序可能會影響程序的行為,如果非要使用using編譯指令,建議放在所有#include預編譯指令后。