【cplusplus教程翻译】名字可见性(Name visibility)

作用域(Scopes)

命名实体,如变量、函数和复合类型,在C++中使用之前需要声明。程序中发生此声明的点会影响其可见性:
在任何块外部声明的实体都具有全局作用域,这意味着其名称在代码中的任何位置都是有效的。而在块内声明的实体,如函数或选择性语句,具有块作用域,并且只能在声明它的特定块内可见,而不能在块外可见。
具有块作用域的变量称为局部变量。
例如,在函数体中声明的变量是一个局部变量,它一直延伸到函数的末尾(即,直到关闭函数定义的大括号),但不在函数的外部:

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
}

在每个作用域中,一个名称只能表示一个实体。例如,在同一作用域内不能有两个具有相同名称的变量:

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;
}

请注意,y在内部块中没有被隐藏,因此访问y仍然访问外部变量。
在引入块的声明中声明的变量,例如在循环和条件中声明的函数参数和变量(例如在for或if上声明的变量),对于它们引入的块是本地的。

命名空间(namespace)

在特定的作用域中,只能存在一个具有特定名称的实体。对于局部名称来说,这很少是一个问题,因为块往往相对较短,并且名称在其中有特定的用途,例如命名计数器变量、参数等。
但非本地名称带来了更多名称冲突的可能性,特别是考虑到库可能声明许多函数、类型和变量,它们本质上都不是本地的,而且其中一些非常通用。
命名空间允许我们将原本具有全局作用域的命名实体分组到更窄的作用域中,从而赋予它们命名空间作用域。这允许将程序的元素组织到由名称引用的不同逻辑范围中。
声明命名空间的语法是

namespace identifier
{
  named_entities
}

其中,identifier是任何有效的标识符,named_entities是命名空间中包含的一组变量、类型和函数。例如:

namespace identifier
{
  int a, b;
}

在这种情况下,变量a和b是在名为myNamespace的命名空间中声明的普通变量。
这些变量通常可以使用其标识符(a或b)从其命名空间内访问,但如果从myNamespace命名空间外访问,则必须使用scope运算符::对其进行适当限定。例如,要从myNamespace外部访问以前的变量,语法如下:

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;
}

在这种情况下,有两个函数具有相同的名称:value。一个在名称空间foo中定义,另一个在bar中定义。由于使用了名称空间,因此不会发生重新定义错误。还要注意,在main中再次访问pi时,如何以非限定的方式从名称空间bar中访问pi(就像pi一样),但这里需要将其限定为bar::pi
命名空间可以拆分:代码的两段可以在同一个命名空间中声明:

namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }

这声明了三个变量:a和c在名称空间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;
}

请注意,变量x(没有任何名称限定符)主要指第一个::x,而y指第二个::y,正如using声明所指定的那样。变量first::y和second::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;
}

在这种情况下,通过声明命名空间first,可以直接使用first命名空间中的变量x和y。
using和using 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;
}

命名空间别名

现有名称空间可以使用新名称进行别名,语法如下:

namespace new_name = current_name;

std命名空间

标准C++库的所有实体(变量、类型、常量和函数)都在std命名空间中声明。事实上,这些教程中的大多数示例都包括以下行:

using namespace std;

这引入了std命名空间的所有名称在代码中的直接可见性。在这些教程中这样做是为了便于理解并缩短示例的长度,但许多程序员更喜欢对程序中使用的标准库的每个元素进行限定。例如,而不是:

cout << "Hello world!";

通常是这样使用的

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;
}

实际输出可能会有所不同,但只有x的值才能保证为零。y实际上可以包含几乎任何值(包括零)。

总结

可见性本质上是用来解决同名变量的问题,优先使用当前代码块的符号,找不到就往上一层找,直到全局层次,namespace在逻辑上把全局层次进行了划分,还可以和类联动

posted @ 2023-05-18 21:44  xiaoweing  阅读(47)  评论(0)    收藏  举报