ch9 内存模型和名称空间
9.1 单独编译
运行make时,可以跟踪程序依赖的文件以及这些文件的最后修改时间。# include
处理多个文件声明函数的问题,将程序分成三个部分:
- 头文件:包含结构声明和使用这些结构的函数的原型
- 源代码文件:包含与结构有关的函数的代码;(function)
- 源代码文件:包含调用与结构有关的函数的代码;(main函数)
头文件中常包含的内容:
- 函数原型
- 使用
define
或const
定义的符号常量; - 结构声明
- 类声明
- 模板声明
- 内联函数
注意: 请不要将函数定义或变量声明放到头文件中,通常容易引起混乱。
头文件有两种方式<>和“ ”:
- <corrd.h>:如果文件名包含在尖括号中,c++编译器将在存储标准头文件的主机系统中查找;
- “corrd.h”:如果文件名包含在双引号中,c++编译器首先查找当前的工作目录或源代码目录,如果没有找到,则将在标准位置查找;
9.2 存储持续性,作用域和链接性;
存储类别影响信息在文件夹中的共享,存储方案有以下几种,不同的存储方案影响数据保留在内存中的时间:
- 自动存储持续性:在函数定义中声明的变量的存储持续性为自动的,在程序开始执行其所属的函数或代码块时被创建,在执行完函数代码块时内存被释放
- 静态存储持续性:在函数定义外定义的变量和使用关键字 static定义的变量的存储持续性都为静态。在整个允许过程中都存在
- 线程存储持续性:使用关键字thread_local声明的,其生命周期和所属的线程一样长;
- 动态存储持续性:用new分配的内存将一直存在,直到使用delete运算符将其释放或者程序结束为止。有时也被称为自由存储(free store) 或堆(heap)
9.2.1 作用域和链接
-
作用域(scope)描述了名称在文件的多大范围内可见
-
链接性(linkage)描述了名称如何在不同单元间共享。链接性在外部的名称可以在文件夹共享,链接性为内部的名称只能由一个文件中的函数共享。注:自动变量的名称没有链接性,不能共享
9.2.2 自动存储持续性
默认情况下,函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。新的代码块中的定义隐藏了以前的定义,新定义可见,旧定义不可见,在程序离开该代码块时,原来的定义由重新可见。
-
自动变量的初始化
Int w;
cin >> w;
int z = 3 * w;
-
自动变量和栈
系统用栈管理自动变量,新的变量被放到上面,当程序使用完成后从栈中删除。程序使用两个指针跟踪栈,一个指向栈底—找到开头的位置,另外指向堆顶—下一个可用的内存单元。
-
寄存器变量
关键字register,建议编译器使用cpu寄存器来存储自动变量, 旨在提升访问变量的速度:
register int count_fast;
9.2.3 静态持续变量
静态存储持续性变量提供了3钟链接性:
- 外部链接性: 可以在其他文件中访问, 必须在代码块的外面声明它;
- 内部链接性:只能在当前文件中访问,必须在代码块的外面声明它,并使用static限定符;
- 无链接性:只能在当前函数或代码块中访问,必须在代码块的里面声明它,并使用static限定符;
由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(栈)来管理他们。编译器将分配固定的内存块来存储所以的静态变量,这些变量在整个程序执行期间一直存在。
int global = 1000; // 外部链接, 可以在程序的其他文件中使用它
static int one_file = 50; // 内部链接性
void funct1(int n)
{
static int count = 0; // 无链接性
}
未被初始化的静态变量所有位都设置为0,这种变量被称为零初始化。
9.2.4 静态持续性,外部链接性
链接性为外部的变量称为外部变量,存储持续性为静态,作用域为整个文件。
-
单定义规则
变量只能有一次定义,为此c++提供零两种变量声明:
- 定义:给变量分配空间
- 引用声明:不给变量分配空间,引用已有的变量。使用关键字extern,且不进行初始化;
double up; //定义 extern int blem; // 声明 extern char gr = 'z'; // 定义 因为赋值了
如果在多个文件中使用外部变量,只需要在一个文件中包含该变量的定义,但在使用该变量的其它所有文件中都必须使用关键字extern声明它。
定义全局变量和局部变量后,局部变量会隐藏全局变量。程序应该避免对数据进行不必要的访问,就越能保持数据的完整性。通常情况下,应该使用局部变量。外部存储数据尤其适用于常量数据,可以使用关键字const来防止数据被修改。
9.2.5 静态持续性,内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性为内部的,链接性为内部的变量只能在其所属的文件中使用,但常规外部变量都具有外部链接性。
static int count = 10;
9.2.6 静态存储持续性,无链接性
将static限定符应用于在代码块中定义的变量,局部变量的持续性为静态的,无链接性。该变量只能在代码块中使用,但它在代码块不处于活动状态时候仍然存在。适用于再生。程序只在启动的时候进行第一次初始化,之后再调用函数时,将不会再次进行初始化,再之前的基础上进行处理。
9.2.7 说明符合限定符
有些被称为存储说明符或cv-限定符
- auto
- register
- static
- extern
- Thread_local
- mutable
cv-限定符
- const:内存被初始化后,程序代码不得对内存单元进行修改
- volatile:不稳定的,程序代码没有对内存单元进行修改,其值也可能发生变化。该关键字的作用是为了改善编译器的优化能力。将变量声明为volatile,相当于告诉编译器不要进行
mutable
即使结果变量为const,其某个成员也可以被修改。
struct data
{
char name[30];
mutable int access;
};
const data veep = {"Calybornue Coldde", 0};
strcpy(veep.name, "jojn") // not allowed
veep.access++; //allowed
9.2.8 函数和链接性
在默认情况下,函数的链接性为外部的,即可以在文件间共享,实际上可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的。还可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用。单定义规则也适用于非内联函数,因此对于每个非内联函数,程序只能包含一个定义。
c++在那里查找函数
- 如果该文件中的函数原型指出该函数是静态的,则编译器将只在该文件中查找定义,否则将在所有程序文件中查找
- 如果找到两个定义,则报错
- 如果在程序文件中没有找到,编译器将在库中搜索
9.2.9 语言链接性
编译器执行名称矫正或者名称修饰,为重载函数生成不同的符号名称。可能将spiff(int) 转换成_spiff_i,将spiff(int, int) 转化成_ _spiff_i_i
9.2.10 存储方案和动态分配
使用c++运算符new分配内存,这种内存被称为动态内存。动态内存由new和delete控制。与自动内存不同,动态内存不是LIFO,其分配和释放顺序要取决于new和delete在何时以何种方式被使用。
-
使用new运算符初始化
如果腰围内置的标量类型分配存储空间并初始化,可在类型名称后面加上初始值
int * pi = new int (6); // set *pi = 6 double * pd = new double (99.9); // set *pd to 99.9 struct where { double x; double y; double z; }; where * one = new where {2.5, 5.3, 7.3};
-
new失败
失败时,早期会返回空指针,但现在会报异常 std::bad_alloc
-
new: 运算符 函数和转换函数
运算符new和new [] 分别调用以下函数:
void * operator new(std:size_t); // used by new void * operator new[](std:size_t); // used by new[] void operator delete(void *); void operator delete[](void *);
上述函数被称为分配函数(alloction function),位于全局名称空间中。同样有delete和delete[]调用的释放函数。
-
定位new运算符
通常, new负责在堆(heap)中找到一个足以满足要求的内存块。new运算符有一种变体称为定位new运算符(placement) new运算符,能够指定使用的位置。要使用定位运算符,头文件要包含new。
#include <new> // common new int *p1; p1 = new int (6); // placement new int *p2; p2 = new (buffer1) int (10); // 从buffer1中分配空间给p2
9.3 名称空间
在c++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也增加。c++标准提供了名称空间工具,以便更好地控制名称的作用域。
9.3.1 传统的c++名称空间
- 声明区域:声明区域可以在其中进行声明的区域,例如可以在函数外面声明全局变量等。
- 潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。
- 作用域:变量对程序而言可见的范围
9.3.2 新的名称空间特性
通过定义一种新的声明区域来创建命名的名称空间,目的是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突。同时允许程序的其它部分使用该名称空间中声明的东西。
Namespace Jack{
double pail;
void fetch();
int pal;
struct Well{
…;
}
}
默认情况下,在名称空间中声明的名称的链接性为外部的。
通过作用域解析运算符::
使用名称空间来限定该名称:
Jack::pail = 12.34;
- using 声明和 using编译
不希望每次使用名称时候都对它进行限定,c++提供了两种方式进行简化使用:
- using声明:
using Jack::pail
, 一个变量可用 - using编译:
using namespace Jack
, 该名称空间中所有名称都可用 - 一般来说,使用using声明比using编译指令更加安全
总结
- 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
- 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
- 如果开发了一个函数库或者类库,应该将其放在一个名称空间中
- 不要在头文件中使用using编译指令
- 导入名称时,首选使用作用域解析运算符或using声明的方法
- 对于using声明, 首选将其作用域设置为局部而不是全局
- 使用头文件来定义用户类型,为操作用户类型的函数提供函数原型
- 将函数定义放在一个独立的源代码文件中
- 头文件和源代码文件一起定义和实现用户定义的类型及其使用方式
- 将main和其它使用这些函数的函数放在第三个文件中