八. 内存模型和名称空间
8.1 单独编译
头文件中常包含的内容:
· 函数原型;
· 使用#difine或const定义的符号常量;
· 结构声明;
· 类声明;
· 模板声明;
· 内联函数。
将结构声明放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为 const 的数据和内联函数有特殊的链接属性(稍后将介绍),因此可以将其放在头文件中,而不会引起问题。
在包含头文件时,我们使用“****.h”,而不是<****.h>。如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找:但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器)。如果没有在那里找到头文件,则将在标准位置查找。因此在包含自己的头文件时,应使用引号而不是尖括号。
头文件管理
在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的C/C++技术可以避免多次包含同一个头文件。它是基于预处理器编译指今#ifndef(即 if not defined)的。下面的代码片段意味着仅当以前没有使用预处理器编译指令#define 定义名称COORDIN_H_ 时,才处理#ifndef和endif之间的语句:
#ifndef COORDIN_H_
...
#endif
通常,使用#define语句来创建符号常量,如下所示:
#define MAXIMUM 4096
但只要将#define用于名称,就足以完成该名称的定义:
#define COORDIN_H_
编译器首次遇到该文件时,名称COORDIN_H_ 没有定义(根据 include 文件名来选择名称,并加上下划线,以创建一个在其他地方不太可能被定义的名称)。在这种情况下,编译器将查看#ifndef和#endif之间的内容,并读取定义 COORDIN_H_的一行。如果在同一个文件中遇到其他包含coordin.h的代码,编译器将知道COORDIN_H_已经被定义了,从而跳到#endif后面的一行上。注意,这种方法并不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。大多数标准C和C++头文件都使用这种防护(guarding)方案。否则,可能在一个文件中定义同一个结构两次,这将导致编译错误。
coordin.h
#ifndef COORDIN_H_
#define COORDIN_H_
struct polar {
double distance;
double angle;
};
struct rect {
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
file1.cpp
#include <iostream>
#include "coordin.h"
using namespace std;
int main() {
rect rplace;
polar pplace;
cout << "Enter the x and y values:";
while (cin >> rplace.x >> rplace.y) {
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Bye!\n";
return 0;
}
file2.cpp
#include <iostream>
#include <cmath>
#include "coordin.h"
polar rect_to_polar(rect xypos) {
using namespace std;
polar answer;
answer.distance =
sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y, xypos.x);
return answer;
}
void show_polar(polar dapos) {
using namespace std;
const double Rad_to_deg = 57.29577951;
cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degrees\n";
}
8.2 存储持续性、作用域和链接性
C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的不同之处就在于数据保留在内存中的时间。
· 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
· 静态存储持续性:在函数定义外定义的变量和使用关键字 static 定义的变量的存储持续性都为静
态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
· 线程存储持续性(C++11):当前,多核处理器很常见,这些 CPU 可同时处理多个执行任务。这
让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字 thread_local 声明的,则
其生命周期与所属的线程一样长。本书不探讨并行编程。
· 动态存储持续性:用new 运算符分配的内存将一直存在,直到使用 delete 运算符将其释放或程序
结束为止。这种内存的存储持续性为动态,有时被称为自由存储 (free store)或堆(heap)。
8.2.1 作用域和链接
作用域 (scope)描述了名称在文件(翻译单元)的多大范围内可见。例如,函数中定义的变量可在该函数中使用,但不能在其他函数中使用:而在文件中的函数定义之前定义的变量则可在所有函数中使用。链接性(linkag)描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。
C++变量的作用域有多种。作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起的一系列语句。例如函数体就是代码块,但可以在函数体中嵌入其他代码块。作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾之间都可用。自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域 (function prototype scope)中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因)。在类中声明的成员的作用域为整个类。在名称空间中声明的变量的作用域为整个名称空间(由于名称空间已经引入到C++语言中,因此全局作用域是名称空间作用域的特例)。
C++函数的作用域可以是整个类或整个名称空间(包括全局的),但不能是局部的(因为不能在代码块内定义函数,如果函数的作用域为局部,则只对它白己是可见的,因此不能被其他函数调用。这样的函数将无法运行)。不同的 C++存储方式是通过存储持续性、作用域和链接性来描述的。
8.2.2 自动存储持续性
在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。也就是说,如果在 main()中声明了一个名为 texas 的变量,并在函数oil()中也声明了一个名为texas 变量,则创建了两个独立的变量——只有在定义它们的函数中才能使用它们。对 oil( )中的texas 执行的任何操作都不会影响 main()中的texas,反之亦然。另外,当程序开始执行这些变量所属的代码块时,将为其分配存:当函数结束时,这些变量都将消失(注意,执行到代码块时,将为变量分配内存,但其作用域的起点为其声明位置)。
如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。例如,假设在main()的开头定义了一个名为teledeli 的变量,然后在 main( )中开始一个新的代码块,并在其中定义了一个新的变量 websight,则teledeli 在内部代码块和外部代码块中都是可见的,而websight 就只在内部代码块中可见,它的作用域是从定义它的位置到该代码块的结尾。
然而,如果将内部代码块中的变量命名为teledeli而不是 websight,使得有两个同名的变量(一个位于外部代码块中,另一个位于内部代码块中),情况将如何呢?在这种情况下,程序执行内部代码块中的语句时,将 teledeli 解释为局部代码块变量。我们说,新的定义隐藏了 (hide)以前的定义,新定义可见,旧定义暂时不可见。在程序离开代码块时,原来的定义重新可见。
1. 自动变量的初始化
可以使用任何在声明时其值为已知的表达式来初始化自动变量。
2. 自动变量和栈
由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。之所以被称为栈,是由于新数据被象征性地放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一个内存单元中),当程序使用完后,将其从栈中删除。栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。程序使用两个指针来跟踪栈,一个指针指向栈底一一的开始位置,另一个指针指向堆顶一一下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后
面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。
栈是 LIFO(后进先出)的,即最后加入到栈中的变量首先被弹出。这种设计简化了参数传递。函数调用将其参数的值放在栈顶,然后重新设置栈顶指针。被调用的函数根据其形参描述来确定每个参数的地址。例如,下图表明,函数fib()被调用时,传递一个2字节的int和一个4字节的long。这些值被加入到栈中。当 fib()开始执行时,它将名称 real和 tell 同这两个值关联起来。当fib()结束时,栈顶指针重新指向以前的位置。新值没有被删除,但不再被标记,它们所占据的空间将被下一个将值加入到栈中的函数调用所使用(图9.3做了简化,因为函数调用可能传递其他信息,如返回地址)。
8.2.3 静态持续变量
和C语言一样,C++也为静态存储持续性变量提供了三种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这三种链接性都在整个程序执行期间存在,与自动变量相比,他们的寿命更长。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊装置(如栈)来管理他们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将会把它设置为0。造默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0。
要创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static限定符。
int global = 1000; //外部链接性
static int one_file = 50; //内部链接性
int main()
{
}
void funct1(int n)
{
static int count = 0; //无链接性
int llama = 0;
}
void funct2(int q)
{
}
正如前面指出的,所有静态持续变量(上述示例中的global、one_file和count)在整个程序执行期间都存在,在funct1()中声明的变量count 的作用域为局部,没有链接性这意味着只能在funct1()函数中使用它就像自动变量 llama一样。然而与llama不同的是,即使在funct1()函数没有被执行时count也留在内存中。global和one_file 的作用域都为整个文件即在从声明位置到文件结尾的范围内都可以被使用。具体地说,可以在 main()、funct1()和 funct2()中使用它们。由于one_file 的链接性为内部,因此只能在包含上述代码的文件中使用它;由于 global 的链接性为外部,因此可以在程序的其他文件中使用它。
所有的静态持续变量都有下述初始化特征:未被初始化的静态变量的所有位都被设置为0.。这种变量被称为零初始化的。
静态变量的初始化
除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化。
8.2.4 静态持续性、外部链接性
链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。例如,可以在main()前面或头文件中定义它们。可以在文件中位于外部变量定义后面的任何函数中使用它,因此外部变量也称全局变量(相对于局部的自动变量)。
1. 单定义规则
一方面,在每个使用外部变量的文件中,都必须声明它;另一方面,C++有“单定义规则”(ODR),该规则指出,变量只能有一次定义。为满足这种需求,C++提供了两种变量声明。一种是定义声明或简称为定义,它给变量分配存储空间;另一种是引用声明或简称为声明,它不给变量分配存储空间,因为它引用已有的变量。
引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间。如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它:
单定义规则并非意味着不能有多个变量的名称相同。例如,在不同函数中声明的同名自动变量是彼此独立的,它们都有自己的地址。另外,局部变量可能隐藏同名的全局变量。虽然程序中包含多个同名的变量,但每个变量都只有一个定义。
如果在函数中声明了一个与外部变量同名的变量,这种声明将被视为一个自动变量的定义,当程序执行自动变量所属的函数时,该变量将位于作用域内。程序清单 9.5 和程序清单9.6 在两个文件中使用了一个外部变量,还演示了自动变量将隐藏同名的全局变量。它还演示了如何使用关键字 extern来重新声明以前定义过的外部变量,以及如何使用 C++的作用域解析运算符来访问被隐藏的外部变量。
external.cpp
#include <iostream>
using namespace std;
double warming = 0.3;
void update(double dt);
void local();
int main() {
cout << "Global warming is " << warming << " degrees.\n";
update(0.1);
cout << "Global warming is " << warming << " degrees.\n";
local();
cout << "Global warming is " << warming << " degrees.\n";
return 0;
}
support.cpp
#include <iostream>
extern double warming;
void update(double dt);
void local();
using std::cout;
void update(double dt) {
extern double warming;
warming += dt;
cout << "Updating global warming to " << warming;
cout << " degrees.\n";
}
void local() {
double warming = 0.8;
cout << "Local warming = " << warming << " degrees.\n";
cout << "But global warming = " << ::warming;
cout << " degrees.\n";
}
local()函数表明,定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。例如,local()函数显示warming的值时,将使用warming的局部定义。
C++比C语言更进了一步——它提供了作用域解析运算符(::),放在变量名前时,该运算符表示使用变量的全局版本。
全局变量和局部变量
既然可以选择使用全局变量或局部变量,那么到底应使用哪种呢?首先,全局变量很有吸引力,因为所有的函数能访问全局变量,因此不用传递参数。但易于访问的代价很大——程序不可靠。计算经验表明,程序越能避免对数据进行不必要的访问,就越能保持数据的完整性。通常情况下,应使用局部变量,应在需要知晓时才传递教据,而不应不加区分地使用全局变量来使数据可用。
OOP 在数据隔离方面又向前迈进了一步,然而,全局变量也有它们的用处。例如,可以让多个函数可以使用同一个数据块(如月份名数组或原子量数组)。外部存储尤其适于表示常量数据,因为这样可以使用关键宇 const 来防止数据被修改。
const char* const months[12] = {"January", ... , "December"};
在上述示例中,第一个const防止字符串被修改,第二个const确保数组中每个指针始终指向它最初指向的字符串。
8.2.5 静态持续性、内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。在多文件程序中,内部链接性和外部链接性之间的差别很有意义。链接性为内部的变量只能在其所属的文件中使用;但常规外部变量都具有外部链接性,即可以在其他文件中使用。
在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件必须使用关键字extern声明它。
可使用外部变量在多文件程序的不同部分之间共享数据;可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另外一种共享数据的方法)。另外,如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突。
twofile1.cpp
#include <iostream>
int tom = 3; //外部变量声明
int dick = 30; //外部变量声明
static int harry = 300; //静态,内部链接性
//函数原型
void remote_access();
int main() {
using namespace std;
cout << "main() reports the following address:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, ";
cout << &harry << " = &harry\n";
remote_access();
return 0;
}
twofile2.cpp
#include <iostream>
extern int tom;
static int dick = 10;
int harry = 200;
void remote_access() {
using namespace std;
cout << "remote_access() reports the following address:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, ";
cout << &harry << " = &harry\n";
}
这两个文件使用了同一个tom变量,但使用了不同的dick和harry变量。
8.2.6 静态持续性、无链接性
至此,介绍了链接性分别为内部和外部、作用域为整个文件的变量。接下来介绍静态持续家族中的第三个成员——无链接性的局部变量。这种变量是这样创建的:将 static 限定符用于在代码块中定义的变量。在代码块中使用static 时,将导致局部变量的存储持续性为静态的。这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。静态变量适用于再生(可以用它们将瑞士银行的秘密账号传递到下一个要去的地方)。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化以后再调用函数时,将不会像自动变量那样再次被初始化。
static.cpp
#include <iostream>
const int Arsize = 10;
void strcout(const char* str);
int main() {
using namespace std;
char input[Arsize];
char next;
cout << "Enter a line:\n";
cin.get(input, Arsize);
while (cin) {
cin.get(next);
while (next != '\n')
cin.get(next);
strcout(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, Arsize);
}
cout << "Bye\n";
return 0;
}
void strcout(const char* str) {
using namespace std;
static int total = 0;
int count = 0;
cout << "\"" << str << "\"contains ";
while (*str++)
count++;
total += count;
cout << count << " characters\n";
cout << total << " characters total\n";
}
每次函数被调用时,自动变量count都被重置为0,然而静态变量total只在程序运行时被设置为零,以后在两次函数调用之间,其值将保持不变,因此能够记录读取的字符总数。
8.2.7 说明符和限定符
有些被称为存储说明符或cv-限定符的C++关键字提供了其他有关存储的信息。下面时存储说明符:
· auto(在C++11中不在是说明符)
· register
· static
· extern
· thread_local
· mutable
在同一个声明中不能使用多个说明符,但 thread_local 除外,它可与 static 或extern 结合使用。前面讲过,在C++11之前,可以在声明中使用关键字auto 指出变量为自动变量;但在C++11
中,auto用于自动类型推断。关键字 register 用于在声明中指示寄存器存储,而在 C++11中,它是显式地指出变量是自动的。关键字 static 被用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的。关键字 extern表明是引用声明,即声明引用在其他地方定义的变量。关键字thread_local 指出变量的持续性与其所属线程的持续性相同,thread_local 变量之于线程犹如常规静态变量之于整个程序。关键字mutable 的含义将根据const 来解释,因此先来介绍cv-限定符,然后再解释它。
1. cv-限定符
· const
· volatile
const表明,变量被初始化后,程序不能再对它进行修改。
关键字 volatile 表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息。在这种情况下,硬件(而不是程序)可能修改其中的内容。或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为 voatile,则编译器将进行这种优化:将变量声明为volatile,相当于告诉编译器,不要进行这种优化。
2. mutable
mutable可以用来指出,即使结构(或类)变量为const,其某个成员也可以被修改。
3. const
在C++中,const限定符对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部,但const全局变量的链接性为内部。也就是说,在C++看来,全局const定义就像使用了static说明符一样。
C++修改了常量类型的规则,例如,假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件,那么,预处理器头文件的内容包含到每个源文件中后,所有的源文件都将包含类似下面这样的定义:
extern const int fingers = 10;
extern const char* warning = “Wak!";
如果全局const声明的链接性像常规变量那样是外部的,则根据单定义规则,这将出错。也就是说,只能有一个文件可以包含前面的声明,而其他文件必须使用extern关键字来提供引用声明。另外,只有未使用extern关键字的声明才能初始化。
因此,需要为某个文件使用一组定义,而其他文件使用另一组声明。然而,由于外部定义的 const 数据的链接性为内部的,因此可以在所有文件中使用相同的声明。
内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因。这样,只要在两个源代码文件中包括同个头文件,则它们将获得同一组常量。
如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用 extern 关键字来覆盖默认的内部链接性.
8.2.8 函数和链接性
和变量一样,函数也有链接性,虽然可选择的范围比变量小。和 C 语言一样,C++不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间都一直存在。在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上,可以在函数原型中使用关键字 extern 来指出函数是在另一个文件中定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)。还可以使用关键字 static 将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在原型和函数定义中使用该关键字。
这意味着该函数只在这个文件中可见,还意味着可以在其他文件中定义同名的的函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数。
单定义规则也适用于非内联函数,因此对于每个非内联函数,程序只能包含一个定义。对于链接性为外部的函数来说,这意味着在多文件程序中,只能有一个文件(该文件可能是库文件,而不是您提供的)包含该函数的定义,但使用该函数的每个文件都应包含其函数原型。
内联函数不受这项规则的约束,这允许程序员能够将内联函数的定义放在头文件中。这样,包含了头文件的每个文件都有内联函数的定义。然而,C++要求同一个函数的所有内联定义都必须相同。
8.2.9 语言链接性
另一种形式的链接性——称为语言链接性 (language linking)也对函数有影响。首先介绍一些背景知识。链接程序要求每个不同的函数都有不同的符号名。在C 语言中,一个名称只对应一个函数,因此这很容易实现。为满足内部需要,C 语言编译器可能将 spiff 这样的函数名翻译为_spiff。这种方法被称为C语言链接性(C language linkage)。但在 C++中同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此,C++编译器执行名称矫正或名称修饰为重载函数生成不同的符号名称,例如,可能将spiff (int)转换为_spiff_i而将spiff (doubledouble)转换为_spiff_d_d这种方法被称为C++语言链接(C++language linkage)。
8.2.10 存储方案和动态分配
与自动内存不同的是,动态内存不是LIFO,其分配和释放顺序要取决于new和delete在何时以何种方式被使用。通常,编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,另外一块用于动态存储。
8.3 名称空间
在 C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库时,可能导致名称冲突。例如,两个库可能都定义了名为 List、Tree 和 Node 的类,但定义的方式不兼容。用户可能希望使用一个库的 List 类,而使用另一个库的Tree类。这种冲突被称为名称空间问题。C++标准提供了名称空间工具,以便更好地控制名称的作用域。
8.3.1 传统的C++名称空间
8.3.2 新的名称空间特性
1. using声明和using编译指令
C++提供了两种机制(using声明和using编译指令)来简化对名称空间的使用。using声明使特定的标识符可用,using编译指令使整个名称空间可用。
using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch;
using声明将特定的名称添加到它所属的声明区域中。
using声明使一个名称可用,而using编译指令使所有的名称都可用,而不需要使用作用域解析运算符:
using namespace std;
在全局声明区域中使用using编译指令,将使该名称空间的名称全局可用:
#include <iostream>
using namespace std;
在函数中使用using编译指令,将使其中的名称在该函数中可用。
using编译指令和using声明增加了名称冲突的可能性,导致二义性。所以最好使用作用域解析运算符:
std::cout << "Bye!";
假设名称空间和声明区域定义了相同的名称。如果试图使用 using 声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。如果使用 using 编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
8.3.3 使用名称空间的一些指导原则
· 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
· 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
· 如果开发了一个函数库或类库,将其放在一个名称空间中。事实上,C++当前提倡将标准函数库
放在名称空间std中这种做法扩展到了来自C语言中的函数。例如,头文件 math.h是与C语言兼容的,没有使用名称空间,但C++头文件 cmath 应将各种数学库函数放在名称空间std 中。实际上,并非所有的编译器都完成了这种过渡。
· 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计。
· 不要在头文件中使用using 编译指令。首先,这样做掩盖了要让哪些名称可用;另外,包含头文
件的顺序可能影响程序的行为。如果非要使用编译指令 using,应将其放在所有预处理器编译指令
#include之后。
· 导入名称时,首选使用作用域解析运算符::或using声明的方法。
· 对于using声明,首选将其作用域设置为局部而不是全局。
8.4 总结
C++鼓励程序员在开发程序时使用多个文件。一种有效的组织策略是,使用头文件来定义用户类型,为操纵用户类型的函数提供函数原型;并将函数定义放在一个独立的源代码文件中。头文件和源代码文件起定义和实现了用户定义的类型及其使用方式。最后,将main()和其他使用这些函数的函数放在第三个
文件中。
C++的存储方案决定了变量保留在内存中的时间(储存持续性)以及程序的哪一部分可以访问它(作用域和链接性)。自动变量是在代码块(如函数体或函数体中的代码块)中定义的变量,仅当程序执行到包含定义的代码块时,它们才存在,并且可见。自动变量可以通过使用存储类型说明符register 或根本不使用说明符来声明,没有使用说明符时,变量将默认为自动的。register 说明符提示编译器,该变量的使用频率很高,但C++11 摒弃了这种用法。
静态变量在整个程序执行期间都存在。对于在函数外面定义的变量,其所属文件中位于该变量的定义后面的所有函数都可以使用它(文件作用域),并可在程序的其他文件中使用(外部链接性)。另一个文件要使用这种变量,必须使用extern关键字来声明它。对于文件间共享的变量,应在一个文件中包含其定义声明(无需使用extern但如果同时进行初始化也可使用它)并在其他文件中包含引用声明(使用extern且不初始化)。在函数的外面使用关键字 static 定义的变量的作用域为整个文件但是不能用于其他文件(内部链接性)。在代码块中使用关键字 static 定义的变量被限制在该代码块内(局部作用域、无链接性)但在整个程序执行期间,它都一直存在并且保持原值。
在默认情况下,C++函数的链接性为外部,因此可在文件间共享,但使用关键字 static 限定的函数的链接性为内部的,被限制在定义它的文件中。
动态内存分配和释放是使用new 和 delete 进行的,它使用自由存储区或堆来存储数据。调用new 占用内存,而调用 delete 释放内存。程序使用指针来跟踪这些内存单元。
名称空间允许定义一个可在其中声明标识符的命名区域。这样做的目的是减少名称冲突,尤其当程序非常大,并使用多个厂商的代码时。可以通过使用作用域解析运算符、using 声明或using 编译指令,来使名称空间中的标识符可用。