第9章 内存模型和名称空间
(一)单独编译
1.原程序分为三部分:
(1)头文件:包含结构声明和使用这些结构的函数的原型
(2)源代码文件:包含与结构有关的函数的代码
(3)源代码文件:包含调用与结构相关的函数的代码
2.头文件中包含的内容:函数原型;使用#define或const定义的符号常量;结构声明;类声明;模板声明;内联函数
3.注意点:(1)在包含头文件时,应将文件名包含在双引号中;(2)在集成开发环境中,不要将头文件加入到项目列表中,也不要在源代码文件中使用#include来包含其他的源代码文件
4.头文件管理:在同一个文件中只能将同一个头文件包含一次
#ifndef COORDIN_H_
#define COORDIN_H_ //将#define用于名称,能完成该名称的定义
//place include file contents here
#endif
上述代码是基于预处理器编译指令#ifndef(即 if not defined)的。仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句。
(二)存储持续性、作用域和链接性
1.C++使用4种不同的方案来存储数据(数据保留在内存中的时间)
(1)自动存储持续性:在函数定义中声明的变量(包含函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
(2)静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
(3)线程存储持续性(C++11):多核处理器使得CPU同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
(4)动态存储持续性:使用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。
2.作用域:描述了名称在文件(翻译单元)的多大范围内可见(C++函数的作用域可以是整个类或整个名称空间(包括全局的))
链接性:描述了名称如何在不同单元间共享(链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函 数共享,自动变量不能共享,因此没有链接性)
3.自动存储持续性:
注意:auto:自动类型推断,显式地指出变量为自动存储(以前的定义)
(1)自动变量的初始化
int w;
int x=5;
int big=INT_MAX-1;
INT Y=2*x;
cin>>w;
int z=3*w;
(2)自动变量和栈
栈是LIFO(后进先出)的,即最后加入到栈中的变量首先被弹出。被调用的函数根据其形参描述来确定每个参数的地址
(3)寄存器变量
register int count_fast; //提高访问变量的速度
关键字register只是显示地指出变量是自动的
4.静态持续变量
C++为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。故要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要想创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static限定符
静态初始化:零初始化和常量表达式初始化
动态初始化:意味着变量在编译后初始化
5.静态持续性、外部链接性
(1)单定义规则:变量只能有一次定义
C++提供了两种变量声明:定义声明(它给变量分配存储空间)、引用声明(引用已有的变量)
(2)引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间
double up; //定义,up是0
extern int blem; //引用声明
extern char gr='z'; //定义
注意:如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它
(3)作用域解析运算符(::):放在变量名前面,该运算符表示使用变量的全局版本
6.静态持续性、内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性为内部的。链接性为内部的变量只能在其所属的文件中使用,但常规外部变量都具有外部链接性,即可以在其他文件中使用。
可使用外部变量在多文件程序的不同部分之间共享数据;可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另外一种共享数据的方法)。另外,如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突。
7.静态存储持续性、无链接性
将static限定符用于在代码块中定义的变量,将导致局部变量的存储持续性为静态的,这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化,以后再调用函数时,将不会像自动变量那样再次被初始化。
8.存储说明符
(1)auto(在C++11中不再是说明符):在C++11之前,指出变量是自动变量,现为自动类型推断
(2)register:指示寄存器存储,现为显式地指出变量是自动的
(3)static:被用在作用域为整个文件的声明中时,表示内部链接性;被用在局部声明中,表示局部变量的存储持续性为静态的
(4)extern:表示引用声明,即声明引用在其他地方定义的变量
(5)thread_local(C++11新增的):指出变量的持续性与其所属线程的持续性相同
(6)mutable:即使结构或类的变量为const,其某个成员也可以被修改
9.cv-限定符
(1)volatile:即使程序代码没有对内存单元进行修改,其值也可能发生变化,该关键字的主要作用是为了改善编译器的优化能力
(2)const:在默认情况下的全局变量的链接性是外部的,但const全局变量的链接性是内部的
const int fingers=10;
const char *warning="Wak!";
extern const int states=50;
如果希望某个常量的链接性是外部的,则使用extern关键字来覆盖默认的内部链接性
10.函数和链接性
默认情况下,函数的链接性为外部的,即可在文件间共享;可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的;在函数原型和函数定义中使用static,将函数的链接性设为内部的;单定义规则适用于非内联函数(程序只能包含一个定义);内联函数不受约束,C++要求同一个函数的所有内联定义都必须相同
11.语言链接性
C++语言链接:在C++中,同一名称可能对应多个函数,必须将这些函数翻译为不同的符号名称,因此C++编译器执行名称修饰,为重载函数生成不同的符号名称。如,spiff(int)转换为_spiff_i,将spiff(double,double)转换为_spiff_d_d。
12.使用new运算符初始化
(1)为标量类型分配存储空间并初始化
int *pi=new int(6);
double *pd=new double(99.99);
(2)初始化常规结构或数组,需要使用大括号的列表初始化
struct where {double x; double y; double z;};
where *one=new where{2.5, 5.3, 7.2};
int *ar=new int[4]{2,4,6,7};
(3)列表初始化用于单值变量
int *pin=new int {6};
double *pdo=new double {99.99};
13.new:运算符、函数和替换函数
(1)new失败时,一种解决方法是使用new返回空指针,另一种解决方法是引发异常std::bad_alloc。
(2)运算符new和new[]分别调用如下函数:
void *operator new(std::size_t);
void *operator new[](std::size_t);
这些函数被称为分配函数,它们位于全局名称空间中
delete和delete[]调用的释放函数:
void operator delete(void *);
void operator delete[](void *);
14.定位new运算符
要使用定位new特性,首先需要包含头文件new,它提供了这种版本的new运算符的原型;然后将new运算符用于提供了所需地址的参数。使用定位new运算符,变量后面可以有方括号,也可以没有
#include<new>
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
char *p1,*p2;
int *p3,*p4;
p1=new chaff;
p3=new int[20];
p2=new(buffer1) chaff;
p4=new(buffer2) int[20];
...
}
默认定位new函数,定位new运算符的工作原理:返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型
标准定位new调用一个接受两个参数的new()函数:
int * pi=new int; //调用new(sizeof(int))
int * p2=new(buffer) int; //调用new(sizeof(int),buffer)
int * p3=new(buffer) int[40]; //调用new(40*sizeof(int),buffer)
定位new函数不可替换,但可重载。它至少需要接收两个参数,其中第一个总是std::size_t,指定了请求的字节数。这样的重载函数都被称为定义new,即使额外的参数没有指定位置
(三)名称空间
1.传统的C++名称空间
声明区域:可以在其中进行声明的区域
潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾
2.新的名称空间特性
通过定义一种新的声明区域来创建命名的名称空间,目的是提供一个声明名称的区域。名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。在默认情况下,在名称空间中声明的名称的链接性是外部的(除非引用常量)
使用作用域解析运算符::,使用名称空间来限定该名称
Jack::pail=12.34;
Jill::Hill mole;
Jack::fetch();
3.using声明和using编译指令(增加了名称冲突的可能性)
using声明使特定的标识符可用,using编译指令使整个名称空间可用
using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; //一个using声明
using编译指令由名称空间名和它前面的关键字using namespace组成,它使名称空间中的所有名称可用,而不需要使用作用域解析运算符: using namespace Jack;
4.using编译指令和using声明之比较
using声明导入指定的名称,如果该名称与局部名称发生冲突,编译器将发生指示;using编译指令导入所有的名称,包括可能并不需要的名称,如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告