跳转到内容

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,如第十三行所示。因此,我们需要像第十四行这样的行:写完这一行之后,所有系统提供的名称都可用,如程序的最后一行所示。此时,我们有三个命名空间的名称被合并到程序中。

如示例程序所示,所需声明被按需引入,不需要的声明被排除,可以使用带有双冒号的限定形式以受控的方式引入。这为大型程序提供了对名称的更大控制。在上面的示例中,我们只使用了变量的名称。但是,命名空间也按需要同样控制过程和类的名称。

华夏公益教科书