命名实体(如变量、函数和复合类型)在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;
}
|
inner block:
x: 50
y: 50
outer block:
x: 10
y: 50
|
注意,y并没有声明在内部块中,因此访问y仍然是访问外部变量。
在引入语句块的声明中声明的变量(如函数参数)和在循环语句以及条件语句中声明的变量
(如在for或if上声明的变量)是它们引入的块的局部变量。
在特定范围内,只能存在一个具有特定名称的实体。
对于局部的名称来说,这很少是一个问题,因为语句块往往相对较短,
并且名称有特定的用途,例如命名计数器变量、参数等
但是非局部名称带来了更多名称冲突的可能性,特别是考虑到库可能声明许多函数、
类型和变量,它们本质上都不是局部的,而且其中一些使用非常广泛。
命名空间允许我们将具有全局作用域的已命名实体分组为更窄的作用域,从而赋予它们新的名称空间作用域。
这会将程序的元素组织到由名称划分的不同逻辑范围中
声明命名空间的语法是:
namespace identifier
{
named_entities
}
|
其中identifier是任何有效的标识符,named_entities是命名空间中包含的变量、
类型和函数的集合。例如:
namespace myNamespace
{
int a, b;
}
|
在本例中,变量a和b是在名为myNamespace的命名空间中声明的普通变量。
这些变量通常可以通过其标识符(a或b)从名称空间内部访问,
但如果从myNamespace名称空间外部访问,则必须使用作用域操作符::
对它们进行适当限定。例如,要从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;
}
|
5
6.2832
3.1416
|
在本例中,有两个名称相同的函数:value。一个定义在命名空间foo中,另一个定义在bar中。
由于命名空间,不会发生重定义错误。请注意如何在名称空间bar中以非限定的方式访问pi(就像pi一样),
而在main中访问它,需要限定为bar::pi。
命名空间可以分割代码:代码的两个部分可以在同一个命名空间中声明:
namespace foo { int a; }
namespace bar { int b; }
namespace foo { int c; }
|
这里声明了三个变量:a和c在命名空间foo中,而b在命名空间bar中。
命名空间甚至可以跨不同的编译单元(例如,跨不同的源代码文件)进行扩展。
关键字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(没有任何名称限定符)指向第一个::x,和y指向第二个::y。
第一个变量::y和第二个变量::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
|
在本例中,通过声明 using namespace first,
所有没有名称限定符的x和y都直接使用 first。
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;
|
5
3.1416
|
标准c++库的所有实体(变量、类型、常量和函数)都在std命名空间中声明。
实际上,这些教程中的大多数示例都包括以下一行:
这将把std命名空间的所有名称直接引入到代码中。在这些教程中这样做是为了便于理解和缩短示例的长度,
但是许多程序员更喜欢限定他们程序中使用的标准库的每个元素。例如,不使用std命名空间:
通常我们会看到他们写成:
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实际上可以包含任何值(包括0)。