9.1 单独编译
头文件常包含的内容:
函数原型
使用
#define
或const
定义的符号常量结构声明
类声明
模板声明
内联函数
如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;
如果文件名包含在双引号中,则编译器将首先查找当前的当前的工作目录或源代码目录(或其他目录,这取决于编译器)
在UNIX系统中编译由多个文件组成的C++程序:
头文件管理:
在同一个文件中只能将同一个头文件包含一次
可以使用基于预处理编译指令
#ifndef
来避免多次包含同一个头文件1 2 3 4
#ifndef COORDIN_H_ #define COORDIN_H_ /////...... #endif
编译器首次遇到该文件时,名称COORDIN_H_没有定义(根据include文件名来选择名称,并加上下划线,为了创建一个在其他地方不太可能被定义的名称),此时编译器查看
#ifndef
和#endif
之间的内容;如果COORDIN_H_已经被定义了,从而跳到#endif
后面的一行
9.2 存储持续性、作用域和链接性
存储持续性:
自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量
线程存储持续性(C++11):当前,多核处理器很常见,一这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。本书不探讨并行编程
动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,.有时被称为自由存储(freestore)或堆(heap)
作用域(scope)描述了名称在文件(翻译单元)的多大范围内可见
链接性(linkage)描述了名称如何在不同的单元间共享:链接性为外部的名称可以在文件间共享,链接性在内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享
当代码块内外有同名变量时,程序执行代码块内部语句时,解释为局部代码块变量,新的定义暂时隐藏了以前的定义
自动变量和栈:
程序再运行时对自动变量进行管理,留出一段内存,视其为栈,管理变量的增减
之所以叫栈,是因为新数据象征性地被放在原有数据的上面
栈默认长度取决于实现,一般可以选择改变
程序用两个指针跟踪栈,栈底和栈顶
函数调用时,自动变量入栈,函数结束时,栈顶指针重置
寄存器变量:关键字
register
建议编译器使用CPU寄存器存储自动变量,旨在提高访问变量的速度,使用原因:指出程序员想使用的一个自动变量。然而保留这个关键字的重要原因时避免使用过该关键字的代码非法静态持续变量:
外部链接性(可在其他文件中访问):必须在代码块外面声明
内部链接性(仅当前文件访问):必须在代码块外面声明并使用static限定符
无链接性(仅当前函数或代码块使用):必须在代码块内部声明,并使用static限定符
三种链接性都在整个程序执行期间存在
编译器分配固定的内存块存储所有静态变量
如果没有显式初始化,那么默认设置为0
5种变量存储方式:
- 零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化
外部变量:是在函数外部定义的,也称全局变量
单定义规则:变量只能有一次定义
- 定义声明(defining declaration),简称定义,它给变量分配存储空间
- 引用声明(referencing declaration),简称声明:不给变量分配存储空间,因为它引用已有的变量
引用声明使用关键字
extern
,且不进行初始化,否则声明为定义,导致分配存储空间1 2
extern int blem;//声明 extern char gr = 'z';//定义
想要使用其他文件定义的外部变量,那么就要在本文件中使用
extern
声明定义与全局变量同名的的局部变量后,局部变量将隐藏全局变量
C++提供了作用域解析运算符(::),放在变量名前面时,该运算符表示使用变量的全局版本
静态外部变量(也就是使用
static
修饰的外部变量),如果与另一个文件中声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量无链接性的局部静态变量:
在代码块中使用
static
,将导致局部变量的存储持续性变为静态的变量在该代码块不处于活动状态时仍然存在
两次函数调用之间,静态局部变量的值保持不变
如果初始化了静态局部变量,则程序只在启动时进行一次初始化
说明符:
auto(C++11不再是说明符)
register
static
extern
thread_local(C++11新增):thread_local变量之于线程,犹如常规静态变量之于整个程序
mutable:即使结构或类变量为const,其某个成员也可以被修改,比如声明了一个const结构对象,按理来说其成员不可被修改,但倘若其成员被mutable修饰,那么还是可以被修改的
cv-限定符:
const
volatile:即使程序代码没有对内存单元进行修改,其值也可能发生变化(比如硬件修改数据,如果不加这个限定符,编译器可能会执行优化导致一个变量存到寄存器(默认变量不会变化)而不能被硬件或其他情况修改,详情见书p317)
const限定符修饰的全局变量将会从外部链接性转变为内部链接性,也就是说,在C++看来,全局const定义就像使用了static说明符一样
函数和链接性:
所有函数的存储持续性都自动为静态的
在默认情况下,函数的链接性为外部的,即可以在文件间共享,当然,想在其他文件中使用,需要提前使用extern来声明函数(不用再次定义否则报错噢)
和变量类似,函数也可以使用
extern
和static
语言链接性(详情见p319,感觉这个点没啥用):
在C++中,同一个名称可能对应多个函数,必须将这些函数生成不同的符号名称,例如soiff(int)转换为_spoff_i,这种方法被称为C++语言链接
存储方案和动态分配:
通常,编译器使用三块独立的内存:一块用于静态变量(可能再细分),一块用于自动变量,一块用于动态存储
new失败时,在最初的10年C++会让new返回空指针,但现在将引发异常
std::bad_alloc
定位new运算符(感觉不太常用,详情见p321)
- 通常new负责在堆中找内存块,而new存在变体,定位new运算符可以让程序员指定需要使用的位置
9.3 名称空间
传统C++名称空间:
声明区域(declaration region):可以在其中进行声明的区域
潜在作用域(potential scope):从声明点开始,到声明区域结尾,因此比声明区域小
新的名称空间特性:
通过定义一种新的声明区域来创建命名的名称空间,目的之一是提供一个声明名称的区域
一个名称空间中的名称不会与另一个名称空间的相同名称发生冲突
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中,所以在默认情况下,在名称空间中声明的名称的链接性为外部的(除非引用了常量)
全局名称空间(global namespace):它对应于文件级声明区域
访问:使用域解析运算符
::
如Jack::pail = 12.34;
未被装饰的名称如
pail
称为未限定的名称(unqualified name);包含名称空间的名称如Jack::pail
称为限定的名称(qualified name)C++提供了两种机制即using声明和using编译指令来简化堆名称空间中名称的使用
using声明使特定的标识符可用,using编译指令使整个名称空间可用
using声明:
using Jack::fetch;
,之后便可以用fetch
来代替Jack::fetch
using编译指令:
using namespace Jack;
它们增加了名称冲突的可能性
还有一些比较复杂的情况,比如局部名称隐藏名称空间名等等,感觉并不会很常用,详情见p328
名称空间其他特性:
可以嵌套
未命名的名称空间(怪,这真的会用到么):这提供了链接性为内部的静态变量的替代品(好像还真有点用,比如少写一些static)
名称空间及其前途:
使用在已命名的名称空间中声明的变量,而不是使用外部的全局变量
使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
如果开发了一个函数库或类库,将其放在一个名称空间中
仅将编译指令作为一种将旧代码转换为使用名称空间的权宜之计
不要在头文件中使用using编译指令,如果非用,应放在预处理编译指令
#include
之后导入名称时,首选使用作用域解析运算符或using声明的方法
对于using声明,首选将其作用域设置为局部而不是全局