名稱可見性

作用域

在C++中,諸如變數、函式和複合型別等命名實體在使用前需要先宣告。宣告在程式中出現的位置會影響其可見性。

在任何塊之外宣告的實體具有全域性作用域,意味著其名稱在程式碼的任何地方都有效。而在塊(如函式或選擇語句)內宣告的實體具有塊作用域,它只在宣告所在的特定塊內可見,在塊外不可見。

具有塊作用域的變數被稱為區域性變數

例如,在函式體中宣告的變數是一個區域性變數,其作用域延伸至函式末尾(即,直到關閉函式定義的右花括號 }),但在函式之外無效。

1
2
3
4
5
6
7
8
9
10
11
12
13
int foo;        // global variable

int some_function ()
{
  int bar;      // local variable
  bar = 0;
}

int other_function ()
{
  foo = 1;  // ok: foo is a global variable
  bar = 2;  // wrong: bar is not visible from this function
}

在每個作用域中,一個名稱只能代表一個實體。例如,在同一作用域中不能有兩個同名的變數。

1
2
3
4
5
6
7
int some_function ()
{
  int x;
  x = 0;
  double x;   // wrong: name already used in this scope
  x = 0.0;
}

具有塊作用域的實體的可見性會延伸到該塊的末尾,包括內部塊。然而,內部塊由於是不同的塊,可以重用外部作用域中已存在的名稱來指代不同的實體;在這種情況下,該名稱將僅在內部塊中指代不同的實體,從而隱藏了外部同名的實體。而在內部塊之外,它仍然指代原始的實體。例如:

// inner block scopes
#include <iostream>
using namespace std;

int main () {
  int x = 10;
  int y = 20;
  {
    int x;   // ok, inner scope.
    x = 50;  // sets value to inner x
    y = 50;  // sets value to (outer) y
    cout << "inner block:\n";
    cout << "x: " << x << '\n';
    cout << "y: " << y << '\n';
  }
  cout << "outer block:\n";
  cout << "x: " << x << '\n';
  cout << "y: " << y << '\n';
  return 0;
}
inner block:
x: 50
y: 50
outer block:
x: 10
y: 50

請注意,y 在內部塊中沒有被隱藏,因此訪問 y 仍然是訪問外部變數。

在引入塊的宣告中宣告的變數,例如函式引數和在迴圈和條件語句(如 for 或 if 中宣告的變數)中宣告的變數,其作用域僅限於它們所引入的塊。

名稱空間

在特定的作用域中,一個名稱只能對應一個實體。這對於區域性名稱來說很少成為問題,因為塊通常相對較短,並且名稱在其中有特定的用途,例如命名一個計數器變數、一個引數等……

但是非區域性名稱帶來了更多名稱衝突的可能性,特別是考慮到庫可能會宣告許多函式、型別和變數,它們本質上都不是區域性的,而且其中一些非常通用。

名稱空間允許我們將本應具有全域性作用域的命名實體分組到更窄的作用域中,賦予它們名稱空間作用域。這使得可以將程式的元素組織到由名稱引用的不同邏輯作用域中。

宣告名稱空間的語法是:


namespace identifier
{
  named_entities
}


其中 identifier 是任何有效的識別符號,而 named_entities 是包含在名稱空間內的一組變數、型別和函式。例如:

1
2
3
4
namespace myNamespace
{
  int a, b;
}

在這種情況下,變數 ab 是在名為 myNamespace 的名稱空間內宣告的普通變數。

這些變數可以在其名稱空間內部正常地透過其識別符號(ab)訪問,但如果從 myNamespace 名稱空間外部訪問,則必須使用作用域解析運算子 :: 進行適當的限定。例如,要從 myNamespace 外部訪問前面的變數,它們應被限定為:

1
2
myNamespace::a
myNamespace::b

名稱空間對於避免名稱衝突特別有用。例如:

// namespaces
#include <iostream>
using namespace std;

namespace foo
{
  int value() { return 5; }
}

namespace bar
{
  const double pi = 3.1416;
  double value() { return 2*pi; }
}

int main () {
  cout << foo::value() << '\n';
  cout << bar::value() << '\n';
  cout << bar::pi << '\n';
  return 0;
}
5
6.2832
3.1416

在這種情況下,有兩個同名函式:value。一個在名稱空間 foo 中定義,另一個在 bar 中定義。由於名稱空間的存在,沒有發生重定義錯誤。另請注意,pi 在名稱空間 bar 內部以非限定方式訪問(直接用 pi),而在 main 函式中再次訪問時,則需要將其限定為 bar::pi

名稱空間可以被分割:一段程式碼的兩個部分可以被宣告在同一個名稱空間中。

1
2
3
namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }

這裡聲明瞭三個變數:ac 在名稱空間 foo 中,而 b 在名稱空間 bar 中。名稱空間甚至可以跨越不同的翻譯單元(即,跨越不同的原始碼檔案)。

using

關鍵字 using 將一個名稱引入到當前的宣告區域(例如一個塊),從而避免了對該名稱進行限定的需要。例如:

// using
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
  int y = 10;
}

namespace second
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using first::x;
  using second::y;
  cout << x << '\n';
  cout << y << '\n';
  cout << first::y << '\n';
  cout << second::x << '\n';
  return 0;
}
5
2.7183
10
3.1416

注意在 main 函式中,變數 x(沒有任何名稱限定符)指向 first::x,而 y 指向 second::y,這正是 using 宣告所指定的。變數 first::ysecond::x 仍然可以被訪問,但需要使用完全限定的名稱。

關鍵字 using 也可以用作指令來引入整個名稱空間:
// using
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
  int y = 10;
}

namespace second
{
  double x = 3.1416;
  double y = 2.7183;
}

int main () {
  using namespace first;
  cout << x << '\n';
  cout << y << '\n';
  cout << second::x << '\n';
  cout << second::y << '\n';
  return 0;
}
5
10
3.1416
2.7183

在這種情況下,透過宣告我們正在使用名稱空間 first,所有不帶名稱限定符直接使用的 xy 也會在名稱空間 first 中查詢。

usingusing namespace 僅在其宣告所在的塊中有效,或者如果它們直接在全域性作用域中使用,則在整個原始檔中有效。例如,可以透過將程式碼分割到不同的塊中,先使用一個名稱空間的物件,然後再使用另一個名稱空間的物件:

// using namespace example
#include <iostream>
using namespace std;

namespace first
{
  int x = 5;
}

namespace second
{
  double x = 3.1416;
}

int main () {
  {
    using namespace first;
    cout << x << '\n';
  }
  {
    using namespace second;
    cout << x << '\n';
  }
  return 0;
}
5
3.1416

命名空間別名

現有的名稱空間可以用新名稱設定別名,語法如下:

namespace new_name = current_name;

std 名稱空間

C++標準庫的所有實體(變數、型別、常量和函式)都在 std 名稱空間中宣告。實際上,這些教程中的大多數示例都包含了以下這行程式碼:

1
using namespace std;

這使得 std 名稱空間中的所有名稱在程式碼中可以直接訪問。在這些教程中這樣做是為了方便理解並縮短示例的長度,但許多程式設計師更喜歡在他們的程式中為每個使用的標準庫元素進行限定。例如, вместо

1
cout << "Hello world!";

更常見的寫法是:

1
std::cout << "Hello world!";

無論 std 名稱空間中的元素是透過 using 宣告引入,還是在每次使用時都進行完全限定,都不會以任何方式改變最終程式的行為或效率。這主要是一個風格偏好問題,儘管對於混合使用多個庫的專案,顯式限定通常是首選。

儲存類

具有全域性名稱空間作用域的變數,其儲存空間在整個程式執行期間都被分配。這被稱為靜態儲存,與區域性變數(在塊內宣告的變數)的儲存方式形成對比。區域性變數使用所謂的自動儲存。區域性變數的儲存空間僅在其宣告的塊內可用;之後,該儲存空間可能會被用於其他函式的區域性變數,或作他用。

但具有靜態儲存的變數和具有自動儲存的變數之間還有另一個實質性的區別:
- 具有靜態儲存的變數(如全域性變數),如果沒有被顯式初始化,會自動初始化為零。
- 具有自動儲存的變數(如區域性變數),如果沒有被顯式初始化,則保持未初始化狀態,因此其值是不確定的。

例如:
// static vs automatic storage
#include <iostream>
using namespace std;

int x;

int main ()
{
  int y;
  cout << x << '\n';
  cout << y << '\n';
  return 0;
}
0
4285838

實際輸出可能會有所不同,但只有 x 的值保證為零。y 實際上可以包含任何值(包括零)。
Index
目錄