跟我一起學習C++ 之 初識名稱空間

lhb_immortal發表於2014-06-29

宣告
            本人自學C++, 沒有計算機基礎,在學習的過程難免會出現理解錯誤,出現風馬牛不相及的現象,甚至有可能會貽笑大方。 如果有幸C++大牛能夠掃到本人的部落格,誠心希望大牛能給予批評與指正!不勝感激!
            學習的過程分為初識、入門、進階三個階段。
            因為對C++沒有什麼瞭解,這樣的學習設定可能也有失準確性。望兄弟們多指點。謝謝!


目錄:
科普:識別符號、作用域
1. 命令空間出現的由來及什麼是名稱空間及全域性名稱空間
2. 命令空間的呼叫
3. 定義、新增名稱空間及成員
4. 巢狀名稱空間
5. 未命名的名稱空間
回顧


科普:
在學習名稱空間時啊,有這麼一個概念一定要先弄明白。不然你會學得和尚摸不著頭腦的。這個就是識別符號。
識別符號:
 識別符號是用於表示以下內容之一的字元序列:
    
    物件或變數名稱
    類、結構或聯合名稱
    列舉型別名稱
    類、結構、聯合或列舉的成員
    函式或類成員函式
    typedef 名稱
    標籤名稱
    巨集名稱
    巨集引數
    
  說簡單一點,識別符號就是指的物件、變數、類、結構等的名字

識別符號的組成規則:第一個字元必須是字母或者是下劃線, 除了第一個字元外,由字元、數字、下劃線組成

作用域:

    通常來說,一段程式程式碼中某個識別符號的使用都是有範圍限制的,而這個識別符號在哪個程式碼範圍內可以被使用就是這個識別符號的作用域。    
    對於物件而言(其他也是一樣的),比如在main函式中,物件的作用域為它所在的最近的一對花括號內。全域性的物件的作用域為宣告之後的整個檔案(單個檔案內,不涉及多檔案的工程)。

    我們拿個變數來舉個例子:
halberd:~ # cat scope1.cpp

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;
  4. int a=2;

  5. class test {
  6. public:
  7.     test() {
  8.     cout << \"Testing varialbe \\\"a\\\" is used out of main()!\" << endl;
  9.     cout << \"Testing variable \\\"a\\\" is :\" << a << endl;
  10.     test1();
  11.     }
  12.     static void test1() {
  13.         cout << \"Testing varialbe \\\"b\\\" cout not be used out of main()!! \" << endl;
  14.         //cout << \"Testing variable \\\"b\\\" is \" << b << endl; //關鍵是這一行!請注意被註釋的情況下跟沒被註釋的情況
  15.     }
  16. };
  17. static test test;
  18.     
  19. int main()
  20. {
  21.   int b=3;
  22.   cout << \"variable \\\"a\\\" be used in main is :\" << a << endl;
  23.   cout << \"variable \\\"b\\\" be used in main is :\" << b << endl;
  24.   return 0;
  25. }

halberd:~ # g++ scope1.cpp -o scope1
halberd:~ # ./scope1
Testing varialbe "a" is used out of main()!
Testing variable "a" is :2
Testing varialbe "b" cout not be used out of main()!!
variable "a" be used in main is :2
variable "b" be used in main is :3

a定義在{}外,b定義在{}內。
我們看到上面程式程式碼編譯正常,執行正常,a 可以在不同{}內呼叫,b也可以在{}內呼叫,但是是否可以在不同{} 內呼叫呢?
再看下取消註釋行後的情況 :

halberd:~ # cat scope1.cpp

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;
  4. int a=2;

  5. class test {
  6. public:
  7.     test() {
  8.     cout << \"Testing varialbe \\\"a\\\" is used out of main()!\" << endl;
  9.     cout << \"Testing variable \\\"a\\\" is :\" << a << endl;
  10.     test1();
  11.     }
  12.     static void test1() {
  13.         cout << \"Testing varialbe \\\"b\\\" cout not be used out of main()!! \" << endl;
  14.         cout << \"Testing variable \\\"b\\\" is \" << b << endl; //關鍵是這一行!請注意被註釋的情況下跟沒被註釋的情況
  15.     }
  16. };
  17. static test test;
  18.     
  19. int main()
  20. {
  21.   int b=3;
  22.   cout << \"variable \\\"a\\\" be used in main is :\" << a << endl;
  23.   cout << \"variable \\\"b\\\" be used in main is :\" << b << endl;
  24.   return 0;
  25. }


halberd:~ # g++ scope1.cpp -o scope1
scope1.cpp: In static member function ‘static void test::test1()’:
scope1.cpp:15:49: error: ‘b’ was not declared in this scope
         cout << "Testing variable \"b\" is " << b << endl;  

這裡我們看到,編譯時出現錯誤 。提示在b在作用域中沒有被宣告。從這裡我們證明了,b 只能在被宣告的那個{}內使用,出了這部分程式碼區域,程式就無法識別了。

上面這個例子中 識別符號 a 的作用域是整個檔案,無論是在main外還是main函式內都可以使用,識別符號 a 的使用在此段程式碼檔案中是沒有限制的,也就是說 識別符號 a的作用域是整個檔案。
而b的可用範圍是在main 函式的{}內部,也就是說b的作用域在main 函式的{}內。當在main函式外部 呼叫時,我們發現編譯時無法通過,提示:error: ‘b’ was not declared in this scope('b'在該作用域中沒有被宣告)。那是在哪個作用域中沒有宣告呢?就是test1() 的花括號{}內了。
所以經過我們的測試,是否可以這樣說呢:

單個檔案內
作用域最大範圍是整個程式碼檔案,最小範圍是識別符號所在的花括號內的程式碼範圍,

為什麼特別說明是單個檔案內呢?因為隨著我們學習知識越來越多,到時候會遇到作用域延伸的情況,比如某個識別符號通過extern 被引用到另外一個檔案中,這樣這個識別符號的作用域就得到了延伸。等我們入門以後再來學吧。這裡只需要意識到還有其他情況 就可以了。

1. 名稱空間的由來、什麼是名稱空間及全域性名稱空間

   在認識什麼是名稱空間前,我們先來了解下,為什麼會出現名稱空間這個東西。
   在瞭解前,我們先想想,我們上學的時候,是不是會經常遇到有同名同性的同學啊?我相信有不少人會有這種經歷的。那麼我們先假設下,在沒有分專業,沒有分班 級前,開了一次全校大會,大會上校長激昂陳詞,要叫一個學生A上臺,至於為啥要叫上臺,叫上臺幹嘛我們不考慮,只想象下這個場景,而這個學校裡有10個同 學叫A,校長這一,臺下呼啦站起10個同學,校長是不是會蒙掉啊?哈哈
   但是,校長了解到他想叫的這個同學是哪個院校哪個專業哪個班級的,校長找這個人的時候根據這些資訊是不是就不會出現上面的情況了?校長把同學A的院校、專業、班級資訊一起說出來,就避免了人名衝突的問題。

   在C++開發過程中,也會遇到類似的情況,相同的識別符號在同一個作用域中宣告瞭多次,當對程式進行編譯時就會出現衝突,編譯器不知道應該 使用哪個識別符號,這種衝突,在C++叫被叫作 Namespace Pollution。那為了避免或者說為了解決 這個問題,我們需要給這些識別符號加上一些附加資訊:“院校”、“專業”、“班級”,這些附加資訊對應我們的程式碼檔案中,就是名稱空間了。
   
   現在我們知道名稱空間是怎麼來的了,那我們怎麼理解這個名稱空間呢?我覺得應該從兩個角度來理解,一個是組成,一個是作用。
   
        同樣的比喻,“班級”是一個名稱空間,那班級裡有什麼呢?班級裡是不是都是人啊?有教授,有助教,有男同學,有女同學等。那麼我們就可以這樣理解:從命名 空間的組成角度來講,名稱空間就是一個集合體,什麼東西的集合體呢?
名稱空間是識別符號及其宣告部分的集合。
       那麼從名稱空間的作用角度又該怎麼理解呢?
       我們知道了,名稱空間是用來避免識別符號宣告衝突的情況。那名稱空間本身是以什麼角色出現的呢?其實我們可以這樣理解,名稱空間就是一個作用域。C++程式 程式碼,由不同的作用域組成,在單個作用域中有不同的識別符號,不同的作用域中有存在相同識別符號的情況,當我們希望指定某個識別符號時,其中一種方式就是使用命 名空間。也就是說,名稱空間本身就是一個作用域。

   綜上,我們知道了,名稱空間本身是一個作用域,由識別符號及其宣告部分組成。
   
   那我們怎麼理解全域性名稱空間呢?
   《C++ Primer》中這樣寫道:
   Names defined at global scopenames declared outside any class, function, or namespaceare defined inside the global namespace. The global namespace is implicitly declared and exists in every program. Each file that defines entities at global scope adds those names to the global namespace
   按照我的理解,全域性名稱空間,是隱式定義的名稱空間,沒有明確的名字,整個程式裡可以共用的(函式、類、名稱空間以外)宣告程式碼,都是宣告在全域性名稱空間的。
   使用全域性名稱空間中的識別符號,需要使用作用域識別符號“::”
   

2. 命令空間的呼叫

   哎呀,我們搞明白了什麼是名稱空間,現在來學學怎麼用吧。我們要學以至用,學了不用不成了紙上談兵了麼……嘿嘿
   名稱空間有三種用法(以std為例):
呼叫方式
優點
缺點
推薦
using namespace std;  
使用方便,無需重複指定,下次使用時不需要指定名稱空間
整個名稱空間全部對檔案開放,容易引起識別符號的衝突問題

using std:cout;
定位精準,無需重複指定,下次使用時不需要指定名稱空間,不容易出現識別符號衝突問題。
未知

std::cout; 
定位精準,
下次使用需要重新指定名稱空間但是書寫繁瑣

  
關於呼叫名稱空間關鍵詞、符號的說明:
   using: 指示符,當using 作為指示符使用時,後面必須跟namespace 關鍵詞,如果後面跟的不是已宣告的名稱空間,會報錯。當然,using 還有其他功能,如重新宣告基類成員,這個後面再研究吧。一下子吃不透那麼多東西。
   ::   : 作用域限定符,它的作用是說明“::”後面所跟的識別符號所在的作用域,它是同namespace一同出現的,用於避免識別符號衝突的問題。 如:std::cout ,說明識別符號cout 是作用域std中的cout,程式發現了"::"就會到該限定符前的作用域中去查詢該識別符號的宣告程式碼,以確定識別符號的功能。
          如果該限定符前面什麼都沒有呢?那程式就會從全域性作用域中查詢該識別符號的定義、宣告。(什麼時候會用到呢?蛋疼啊~知道的太少,實在想不出場景~望大神能給個例子,救人一命勝造七級浮圖啊~教人一卷勝過吃飯千碗,功德無量!! 感謝老戴提示:類靜態成員和函式時會用到。等 以後學到類和函式時再研究)

3. 定義、新增名稱空間及成員


   折騰好幾天,終於算是對名稱空間是個什麼東西了,下面我們來學習定義名稱空間吧~激動人心的時刻來臨啦~
   
   3.1 定義名稱空間的語法結構
   
       namespace namespace_name {
       ………… //宣告部分
       }
      在具體編寫前,我們要先了解定義名稱空間有哪些限制:
      a) 由於名稱空間只是作用域的一種表現形式,因此也需要遵守作用域中識別符號的命名規則:作用域中的名字在該作用域中只能是唯一的,不然會出現衝突 。名稱空間也一樣,在一個名稱空間中,名字也必須 是唯一的。
      b) 名稱空間可以在全域性作用域或者其他作用域中定義,但是不能在函式、類結構內部定義
      c) 名稱空間作用域不能以分號結束
      
      那名稱空間允許我們定義哪些內容呢? 嘿嘿,只要是可以出現在全域性作用域中的任意宣告都可以在名稱空間中定義,如類、變數及初始化、函式及定義、模板及其他名稱空間(注意沒有?名稱空間中還可 以有名稱空間呢,也就是說名稱空間其實是可以巢狀的,後面會對這一點進一步瞭解。)
      
      來來來,我們來寫一個灰常簡單的名稱空間,先享受一次會寫名稱空間的樂趣,嘿嘿。
      程式碼如下 :

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. using std::cout;
  3. using std::cin;
  4. using std::endl;

  5. namespace halberd_ns {
  6.         int a = 1;
  7. }

  8. int a = 2;

  9. int main()
  10. {
  11. cout << \"This variable is from namespace halberd_ns: a=\"<<halberd_ns::a<<endl;
  12. cout <<\"This variable is from global namesapce :: a=\"<<a<<endl;
  13. return 0;
  14. }



編譯:
halberd:/home/C++/codes # g++ ./namespace_.cpp -o namespace_
執行:
halberd:/home/C++/codes # ./namespace_
This variable is from namespace halberd_ns: a=1    //從這裡我們看到名稱空間中變數的定義及初始化成功了,並在名稱空間外成功呼叫名稱空間中的變數
This variable is from global namesapce :: a=2

哈哈,成功了。

3.2   合併名稱空間(2014-06-30 補充)
其實C++名稱空間,可以分開寫的。可以在同一個檔案裡分別宣告不同功能的部分,也可以分到幾個檔案裡宣告。下面我們做個實驗:
   在上一小節,我定義了一個名稱空間:halberd_ns . 下面,我們以這具為基礎,進行實驗。首先在檔案內部再宣告同名名稱空間:
halberd:/home/C++/codes # cp namespace_.cpp ./namespace_1.cpp
halberd:/home/C++/codes # vi namespace_.cpp
halberd:/home/C++/codes # cat namespace_1.cpp

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. using std::cout;
  3. using std::cin;
  4. using std::endl;

  5. namespace halberd_ns {
  6.         int a = 1;
  7. }

  8. int a = 2;

  9. namespace halberd_ns { //新宣告名稱空間 --看看是什麼作用
  10.         int b = 3;
  11. }

  12. int main()
  13. {
  14. cout << \"This variable is from namespace halberd_ns: a=\"<<halberd_ns::a<<endl;
  15. cout <<\"This variable is from global namesapce : a=\"<<a<<endl;
  16. cout <<\"This variable is used to test namespace consolidation: b=\" <<halberd_ns::b<<endl; //驗證我們的猜想
  17. return 0;
  18. }



看上面的修改的部分,一個是新宣告瞭一個名稱空間halberd_ns,然後新增一行,用以驗證兩人個名稱空間是否同時生效。
看下結果:
halberd:/home/C++/codes # g++ ./namespace_1.cpp -o namespace_1
halberd:/home/C++/codes # ./namespace_1
This variable is from namespace halberd_ns: a=1
This variable is from global namesapce : a=2
This variable is used to test namespace consolidation: b=3

我們看到:
第一,原名稱空間沒有被覆蓋,原來的宣告還有效。
第二,宣告已存在 的名稱空間,會將兩個名稱空間的內容合併起來,同時生效,這就相當於名稱空間的合併。

下面我們再做個實驗,我們在不同的檔案中宣告同一個名稱空間,看會是什麼效果呢:
alberd:/home/C++/codes # vi attach_namespace.cpp
halberd:/home/C++/codes # cat attach_namespace.cpp

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. namespace halberd_ns{
  3.         int c=4;
  4. }


halberd:/home/C++/codes # vi namespace_1.cpp
halberd:/home/C++/codes # cat namespace_1.cpp

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. #include \"attach_namespace.cpp\"
  3. using std::cout;
  4. using std::cin;
  5. using std::endl;

  6. namespace halberd_ns {
  7.         int a = 1;
  8. }

  9. int a = 2;

  10. namespace halberd_ns {
  11.         int b = 3;
  12. }

  13. int main()
  14. {
  15. cout << \"This variable is from namespace halberd_ns: a=\"<<halberd_ns::a<<endl;
  16. cout <<\"This variable is from global namesapce : a=\"<<a<<endl;
  17. cout <<\"This variable is used to test namespace consolidation: b=\" <<halberd_ns::b<<endl;
  18. cout <<\"This variable is used to test outer file\'s namespace consolidattion: c=\" <<halberd_ns::c<<endl;
  19. return 0;
  20. }
halberd:/home/C++/codes # g++ ./namespace_1.cpp -o namespace_1
halberd:/home/C++/codes # ./namespace_1
This variable is from namespace halberd_ns: a=1
This variable is from global namesapce : a=2
This variable is used to test namespace consolidation: b=3
This variable is used to test outer file's namespace consolidattion: c=4

從上面的實驗,我們看到,名稱空間halberd_ns, 分別在attach_namespace.cpp 中宣告一次,並在namespace_1.cpp中宣告兩次。檔案attach_namespace.cpp 被引用到namespace_1.cpp中,名稱空間的宣告也同時被引用過來。而且名稱空間中的宣告,也可以正常使用。
那麼,我們可以這樣說:

1) 名稱空間的宣告、定義可以是不連續的,一個名稱空間可以分散到多個檔案中,或者在同一個檔案中的不同部分進行分別宣告或者定義。
2) 同名名稱空間的(非重複)聲名、定義,可以累積、合併,在外界看來,就如同是隻經過一次宣告一樣。

3.3 新增名稱空間成員(待補充)
       (感覺跟3.2 內容有些重複,但是確實是一塊新東西。跟類相關,與老戴討論半天,也沒弄明白。先放一放吧。等 學完類再來補充這一塊)

4. 巢狀名稱空間(2014-07-01 補充)

     所謂的名稱空間巢狀,其實就是在名稱空間作用域內再定義一個名稱空間。格式類似如下:
     namespace halberd_ns {
                             namespace halberd_ns1{
                            //definitions
                            ……
}
}
   來來來,我們做個實驗:

點選(此處)摺疊或開啟

  1. #include <iostream>
  2. using std::cout;
  3. using std::cin;
  4. using std::endl;

  5. namespace halberd_ns {
  6.         int a = 1;
  7.         namespace halberd_ns_nested{
  8.                 int b=2;
  9.                                 }
  10. }


  11. int main()
  12. {
  13. cout << \"This variable is from namespace halberd_ns: a=\"<<halberd_ns::a<<endl;
  14. cout <<\"This variable is from nested namesapce halberd_ns_nested:: b=\"<<halberd_ns::halberd_ns_nested::b<<endl;
  15. return 0;
  16. }
編譯:
linux-emf1:/home/C++/codes # g++ ./namespace_nested.cpp -o namespace_nested
執行進行驗證:
linux-emf1:/home/C++/codes # ./namespace_nested
This variable is from namespace halberd_ns: a=1
This variable is from nested namesapce halberd_ns_nested:: b=2

嘿嘿,果然又成功了。怎麼樣,我們學起來,不是很難吧。雖然遇到有不會的,無法 理解的。但是,我們一直在進步呢。對吧。只要在前進,就會離成功越來越近。


5. 未命名的名稱空間(2014-07-01 補充)

     未命名的名稱空間,啥意思,不用我多說了吧,就是沒有名字嘛。
     定義格式如下:
     namespace {
    // definitions
    ……
     }

     前面我們瞭解過全域性名稱空間,還有印象不,沒有的話,到本頁最上面看。這全域性名稱空間有相似之處。你知道是啥不?對啦,就是沒有名字。那他們之間有什麼區別呢?弄清楚這個區別,我們這個沒有名字的名稱空間的特點也就瞭解了:

對比
全域性名稱空間
未命名的名稱空間
是否允許巢狀

是,允許巢狀在其他名稱空間內
使用範圍
全域性,可在工程內任意檔案中引用成員
區域性,只能在一個檔案內的區域性作用域中使用,不能跨檔案使用
引用成員方式
使用作用域限定符"::"
直接使用成員名,不可以使用作用域限定符"::"
定義方式
隱匿定義(自動定義,每個工程都有自己的預設的一個全域性名稱空間)
顯式定義
未命名的名稱空間其他特點:
1) 未命名的名稱空間中定義的變數在程式開始時建立,在程式結束時釋放一直存在
2) 如果在最外層定義未命名的名稱空間,成員名不能與全域性作用域中的名字不一樣,為啥?會跟全域性名稱空間的成員衝突唄

回顧:
我們對於名稱空間都學習了哪些東西呢?
1. 什麼是識別符號和作用域
2. 名稱空間的由來
3. 什麼是名稱空間(從作用及組成兩方面來考慮)
4. 名稱空間的呼叫
5. 名稱空間的定義
6. 名稱空間合併
7. 新增名稱空間成員(待補充)
8. 未命名的名稱空間
9. 巢狀名稱空間


特別鳴謝: 瑤玲啊瑤玲、老戴等

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25313300/viewspace-1198190/,如需轉載,請註明出處,否則將追究法律責任。

相關文章