跳轉到內容

C++ 程式設計

來自華夏公益教科書,開放的書籍,開放的世界

在任何語言中,範圍(上下文;背景是什麼)對給定動作或語句的有效性有很大影響。程式語言也是如此。

在一個程式中,我們可能會有各種各樣的結構,可能是物件、變數或其他任何結構。它們從你宣告它們的地方開始存在(在宣告它們之前,它們是未知的),然後,在某個時刻,它們被銷燬(正如我們將看到的,有許多原因會這樣),並且當你的程式終止時,所有這些都會被銷燬。

我們將看到 變數在你的程式執行時具有有限的生命週期,一個物件或變數的範圍只是程式中該變數名存在或對編譯器可見的那一部分。

全域性範圍

[編輯 | 編輯原始碼]

預設範圍定義為全域性範圍,這通常用於定義和使用全域性變數或其他全域性結構(類、結構、函式等...),這使得它們在任何時候對編譯器都有效且可見。

注意

如果可能,作為減少複雜性和命名衝突的一種方式,最好使用 名稱空間 範圍來隱藏否則為全域性的元素,而不刪除它們的有效性。

區域性範圍

[編輯 | 編輯原始碼]

區域性範圍與在複合語句內部建立的範圍相關。

注意
唯一例外的情況是 for 關鍵字。在這種情況下,在 for 初始化部分宣告的變數將是區域性範圍的一部分。

名稱空間關鍵字允許你建立一個新的範圍。名稱是可選的,可以省略以建立一個無名名稱空間。建立名稱空間後,你必須明確引用它或使用using關鍵字。名稱空間使用名稱空間塊定義。

語法
    namespace name {
    declaration-list;
    }

在許多 程式語言中,名稱空間識別符號的上下文。C++ 可以處理語言中的多個名稱空間。透過使用名稱空間(或using namespace關鍵字),人們可以使用一種簡潔的方式將程式碼聚合到一個共享的標籤下,以防止命名衝突,或者只是為了便於回想起和使用非常具體的範圍。除了“名稱空間”之外,還有其他“名稱空間”;這可能會讓人困惑。

名稱空間(注意其中的空格),正如我們將看到的,它們超越了範圍的概念,提供了一種簡單的方法來區分正在呼叫/使用的東西。正如我們將看到的,類也是名稱空間,但它們不是名稱空間。

注意
只在需要時或為了方便使用名稱空間,例如聚合相關程式碼,不要以一種使程式碼對你和其他人生成過於複雜的方式使用它。

示例
namespace foo {
  int bar;
}

在這個塊中,識別符號可以按宣告的方式使用。在這個塊之外,必須加上名稱空間說明符(也就是說,它必須被限定)。例如,在名稱空間 foo之外,bar必須寫成foo::bar

C++ 包含另一個結構,它使這種冗長不再必要。透過在程式碼中新增一行using namespace foo;,就不再需要字首foo::

無名名稱空間
[編輯 | 編輯原始碼]

沒有名稱的名稱空間稱為無名名稱空間。對於這樣的名稱空間,將為每個翻譯單元生成一個唯一的名稱。無法將using關鍵字應用於無名名稱空間,因此無名名稱空間的工作方式就好像using關鍵字已應用於它一樣。

語法
    namespace {
    declaration-list;
    }
命名空間別名
[編輯 | 編輯原始碼]

你可以為名稱空間(包括巢狀名稱空間)建立新的名稱(別名)。

語法
   namespace identifier = namespace-specifier;


使用名稱空間
[編輯 | 編輯原始碼]
使用
using namespace std;

使用-指令指示任何在程式中使用但未宣告的名稱都應該在‘標準 (std)' 名稱空間中查詢。

注意
在標頭檔案中使用使用指令永遠是一個壞主意,因為它會影響對該標頭檔案的每次使用,並且會使其在其他派生專案中的使用變得困難;沒有辦法“撤銷”或限制該指令的使用。另外,不要在#include指令之前使用它。

要使來自 名稱空間 的單個名稱可用,以下使用-宣告存在

using foo::bar;

在進行此聲明後,名稱bar可以在當前的 名稱空間 中使用,而不是更冗長的版本foo::bar. 請注意,程式設計師通常互換使用宣告和指令這兩個術語,儘管它們的含義在技術上有所不同。

最好使用窄的第二種形式(使用宣告),因為寬泛的第一種形式(使用指令)可能會使比預期更多的名稱可用。示例

namespace foo {
  int bar;
  double pi;
}
 
using namespace foo;
 
int* pi;
pi = &bar;  // ambiguity: pi or foo::pi?

在這種情況下,宣告using foo::bar;只會使foo::bar可用,避免了pifoo::pi的衝突。這個問題(同名變數或函式的衝突)被稱為“名稱空間汙染”,作為規則應該儘可能避免。

使用-宣告可以出現在許多不同的位置。其中包括

  • 名稱空間(包括預設名稱空間)
  • 函式

一個使用-宣告使名稱(或 名稱空間)在宣告的範圍內可用。示例

namespace foo {
  namespace bar {
   double pi;
  }

  using bar::pi;
  // bar::pi can be abbreviated as pi
}
 
// here, pi is no longer an abbreviation. Instead, foo::bar::pi must be used.

名稱空間是分層的。在假設的 名稱空間food::fruit中,識別符號orange指的是food::fruit::orange(如果它存在),或者如果不存在,則指的是food::orange(如果它存在)。如果兩者都不存在,則orange指的是預設名稱空間中的識別符號。

未在名稱空間中顯式宣告的程式碼被認為是在預設名稱空間中。

名稱空間的另一個屬性是它們是開放的。一旦聲明瞭名稱空間,它就可以被重新宣告(重新開啟),並且可以新增名稱空間成員。例如

namespace foo {
  int bar;
}
 
// ...
 
namespace foo {
  double pi;
}

名稱空間最常用於避免命名衝突。雖然名稱空間在最近的 C++ 程式碼中被廣泛使用,但大多數舊程式碼沒有使用此功能。例如,整個標準庫是在名稱空間std中定義的,而在該語言的早期標準中,是在預設名稱空間中定義的。

對於一個很長的名稱空間名稱,可以定義一個更短的別名(一個名稱空間別名宣告)。例如

namespace ultra_cool_library_for_image_processing_version_1_0 {
  int foo;
}
 
namespace improc1 = ultra_cool_library_for_image_processing_version_1_0;
// from here, the above foo can be accessed as improc1::foo

存在一個特殊的名稱空間無名名稱空間。這個名稱空間用於特定原始檔或其他名稱空間私有的名稱。

namespace {
  int some_private_variable;
}
// can use some_private_variable here

在周圍的作用域中,無名名稱空間的成員可以在沒有限定的情況下訪問,即不使用名稱空間名稱和::作為字首(因為名稱空間沒有名稱)。如果周圍的作用域是名稱空間,則成員可以被視為其成員並被訪問。但是,如果周圍的作用域是檔案,則成員無法從任何其他原始檔訪問,因為沒有辦法將檔案命名為作用域。一個無名名稱空間宣告在語義上等效於以下結構

namespace $$$ {
  // ...
}
using namespace $$$;

其中$$$是編譯器製造的唯一識別符號。

正如你可以在一個普通的名稱空間中巢狀一個無名名稱空間,反之亦然,你也可以巢狀兩個無名名稱空間。

namespace {

  namespace {
    // ok
  }

}

注意
如果你在程式碼中啟用名稱空間的使用,所有程式碼都會使用它(你不能定義將使用和排除其他部分的程式碼段),但是你可以使用巢狀名稱空間宣告來限制它的範圍。

由於空間的考慮,我們實際上無法展示名稱空間命令的正確使用方法:它將需要一個非常大的程式才能展示它有用地工作。但是,我們可以很容易地說明這個概念本身。

// Namespaces Program, an example to illustrate the use of namespaces
#include <iostream>

namespace first {
  int first1;
  int x;
}

namespace second {
  int second1;
  int x;
}

namespace first {
  int first2;
}

int main(){
  //first1 = 1;
  first::first1 = 1;
  using namespace first;
  first1 = 1;
  x = 1;
  second::x = 1;
  using namespace second;

  //x = 1;
  first::x = 1;
  second::x = 1;
  first2 = 1;

  //cout << 'X';
  std::cout << 'X';
  using namespace std;
  cout << 'X';
  return 0;
}

我們將從程式的開頭到結尾檢查程式碼,依次檢查程式碼片段。

#include <iostream>

這只是包含iostream庫,以便我們可以使用std::cout將內容列印到螢幕上。

namespace first {
  int first1;
  int x;
}

namespace second {
  int second1;
  int x;
}

namespace first {
  int first2;
}

我們建立一個名為first的名稱空間,並在其中新增兩個變數first1x。然後我們關閉它。然後,我們建立一個名為second的新名稱空間,並在其中放入兩個變數:second1x。然後我們重新開啟名稱空間first,並在其中新增另一個名為first2的變數。一個名稱空間可以以這種方式被重新開啟,只要需要就可以新增額外的名稱。

  main(){
1  //first1 = 1;
2  first::first1 = 1;

主程式的第一行被註釋掉了,因為它會導致錯誤。為了獲取第一個名稱空間中的名稱,我們必須在變數的名稱之前加上其名稱空間的名稱和兩個冒號;因此主程式的第二行不是語法錯誤。變數的名稱在作用域內:它只需要以這種特殊的方式被引用,才能在這一點被使用。因此,這將全域性名稱列表分割成組,每組都有自己的字首名稱。

3  using namespace first;
4  first1 = 1;
5  x = 1;
6  second::x = 1;

主程式的第三行引入了using namespace命令。該命令將第一個名稱空間中的所有名稱拉入作用域。然後它們可以從那裡開始以通常的方式使用。因此,程式的第四行和第五行編譯沒有錯誤。特別是,變數x現在可用:為了訪問第二個名稱空間中的另一個變數x,我們會像第六行所示那樣呼叫它second::x。因此,這兩個名為x的變數可以被分別引用,因為它們在第五行和第六行。

7  using namespace second;
8  //x = 1;
9  first::x = 1;
10 second::x = 1;

然後我們再次使用using namespace命令,將名稱空間名為second的宣告拉入。以下行被註釋掉,因為它現在是一個錯誤(而之前是正確的)。由於兩個名稱空間都被帶入了全域性名稱列表,所以變數x現在是模稜兩可的,需要以第九行和第十行所示的限定方式進行討論。

11 first2 = 1;

主程式的第十一行顯示,即使first2是在名為first名稱空間的單獨部分中宣告的,但它與名稱空間first中的其他變數具有相同的身份。一個名稱空間可以被重新開啟,只要你願意。當然,作用域的通常規則適用:在同一個名稱空間中嘗試兩次宣告同一個名稱是不合法的。

12 //cout << 'X';
13 std::cout << 'X';
14 using namespace std;
15 cout << 'X';
}

在計算機中有一組特殊的檔案中定義了一個名稱空間。它的名稱是std,所有系統提供的名稱,如cout,都在許多不同的檔案中在這個名稱空間中宣告:它是一個非常大的名稱空間。請注意,程式頂部的#include語句並沒有完全將名稱空間拉入:這些名稱存在,但仍然必須以限定形式引用。第十二行必須被註釋掉,因為目前系統提供的名稱,如cout,是不可用的,除非以限定形式std::cout,如第十三行所示。因此,我們需要像第十四行這樣的行:寫完這一行之後,所有系統提供的名稱都可用,如程式的最後一行所示。此時,我們有三個名稱空間的名稱被合併到程式中。

如示例程式所示,所需宣告被按需引入,不需要的宣告被排除,可以使用帶有雙冒號的限定形式以受控的方式引入。這為大型程式提供了對名稱的更大控制。在上面的示例中,我們只使用了變數的名稱。但是,名稱空間也按需要同樣控制過程和類的名稱。

華夏公益教科書