本章内容包括:
- 单独编译
- 存储持续性,作用域和链接性
- 定位(placement) new运算符
- 名称空间
C++名称空间是另一种控制访问权的方式.
9.1 单独编译
- 请不要将函数定义或变量声明放到头文件中.
- 一般头文件中常包含的内容
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
- 例如:使用”coordin.h”,而不是
<coordin.h>
.如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器).如果没有在那里找到头文件,则将在标准位置查找.因此在包含自己的头文件时,应使用引号而不是尖括号. - 警告:在IDE中,不要将头文件加入到项目列表中,也不要在源代码文件中使用#include来包含其他源代码文件.
- 程序清单9.1 coordin.h
- 头文件管理:在同一个文件中只能将同一个头文件包含一次.记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次.有一种标准的C/C++技术可以避免多次包含同一个头文件.它是基于预处理器编译指令#ifndef(即if not defined)的.注意,这种方法并不能防止编译器将文件包含两次,而知识让它忽略除第一次包含之外的所有内容.大多数标准C和C++头文件豆试用这种防护方案.否则,可能在一个文件中定义同一个结构两次,这将导致编译错误.
- 程序清单9.2 file1.cpp
- 程序清单9.3 file2.cpp
- 多个库的链接:C++标准允许每个编译器设计人员以他认为何时的方式实现名称修饰,因此由不同编译器创建的二进制模块(对象代码文件)很可能无法正确地链接.也就是说,两个编译器将为同一个函数生成不同的修饰名称.名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配.在链接编译模块时,请确保所有对象文件或库都是由同一个 编译器生成的.如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误.
9.2 存储持续性,作用域和链接性
- C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间.
- 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的.他们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,他们使用的内存被释放.C++有两种存储持续性为自动的变量.
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性斗味静态.他们在陈谷整个运行过程中都存在.C++有3种存储持续性为静态的变量
- 线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时初始多个执行任务.折让程序能够将计算放在可并行处理的不同线程中.如果变量是使用关键字thread_local声明的,则其声明周期与所属的线程一样常.
- 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止.这种内存的存储持续性为动态,有时被称为自由存储或堆.
9.2.1 作用域和链接
- 作用域描述了名称在文件(翻译单元)的多大范围内可见.
- 链接性描述了名称如何在不同单元间共享.链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享.自动变量的名称没有链接性,因为他们不能共享.
9.2.2 自动存储持续性
- 在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性.
- 程序清单9.4 autoscp.cpp
- 使用C++11中的auto:在C++11中,关键字auto用于自动类型推断.但在C语言和以前的C++版本种,auto的含义截然不同,它用于显式的指出变量为自动存储.由于只能将关键字auto用于默认为自动的变量,因此程序员几乎不使用它.它的主要用途是指出当前变量为局部自动变量.在C++11中,这种用法不再合法.制定标准的人不愿引入新关键字,因为这样做可能导致将该关键字用于其他目的的代码非法.考虑到auto的老用法很少使用,因此赋予其新含义比引入新关键字是更好的选择.
- 自动变量的初始化
- 可以使用任何在声明时其值为已知的表达式来初始化自动变量.
- 自动变量和栈
- 栈是LIFO(后进先出)的,即最后加入到栈中的变量首先被弹出.这种设计简化了参数传递.函数调用将其参数的值放在栈顶,然后重新设置栈顶指针.被调用的函数根据其形参描述来确定每个参数的地址.
- 寄存器变量
- 关键字register最初是由C语言引入的,它建议编译器使用CPU寄存器来存储自动变量.这旨在提高访问变量的速度.
- 鉴于关键字register只能用于原本就是自动的变量,使用它的唯一原因是,指出程序员想使用一个自动变量,这个变量的名称可能与外部变量相同.这与auto以前的用途完全相同.然而,保留关键字register的重要原因是,避免使用了该关键字的现有代码非法.
- 自动变量的初始化
9.2.3 静态持续变量
- 如果没有显式的初始化静态变量,编译器将把它设置为0.在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0.
- 注意:传统的K&R C不允许初始化自动数组和结构,但允许初始化静态数组和结构.ANSI C和C++允许对这两种数组和结构进行初始化,但有些旧的C++翻译器使用与ANSI C不完全兼容的C编译器.如果使用的是这样的实现,则可能需要使用这3种静态存储类型之一,以初始化数组和结构.
9.2.4 静态持续性,外部链接性
- 单定义规则
- 该规则指出,变量只能有一次定义.
- C++提供了两种变量声明:
- 一种是定义声明或简称为定义.它给变量分配存储空间
- 另一种是引用声明或简称为声明,它不给变量分配存储空间,因为它引用已有的变量.
- 引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间.
- 请注意,单定义规则并非意味着不能有多个变量的名称相同.
- 程序清单9.5 external.cpp
- 程序清单 support.cpp
- 程序说明
- C++比C语言更进了一步—它提供了作用域解析运算符(::).放在变量名前面时,该运算符表示使用变量的全局版本.
- 全局变量和局部变量:计算经验表明,程序越能避免对数据进行不必要的访问,就越能保持数据的完整性.
9.2.5 静态持续性,内部链接性
- 注意:在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量.使用该变量的其他文件必须使用关键字extern声明它.
- 程序清单9.7 twofile1.cpp
- 程序清单9.8 twofile2.cpp
9.2.6 静态存储持续性,无链接性
- 在代码块中使用static时,将导致局部变量的存储持续性为静态的.这意味着虽然该变量旨在该代码块中可用,但它在该代码块不处于活动状态时仍然存在.因此在两次函数调用之间,静态局部变量的值将保持不变.
- 程序清单9.9 static.cpp
9.2.7 说明符和限定符
- 存储说明符
- auto(在C++11中不再是说明符)
- register
- static
- extern
- thread_local(C++11新增的)
- mutable
- 在同一个声明中不能使用多个说明符,但thread_local除外,它可与static或extern结合使用.
- 1.cv-限定符
- const
- volatile
- cv表示const和volatile.最常用的cv-限定符是const.关键字volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化.
- 2.mutable
- 可以用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改.
- 3.再谈const
- 在函数或代码块中声明const时,其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才是可用的.这意味着在函数或代码块中创建常量时,不必担心其名称与其他地方定义的常量发生冲突.
9.2.8 函数和链接性
- 和变量一样,函数也有链接性,虽然可选择的范围比变量小.
- 内联函数的定义放在头文件中.这样,包含了头文件的每个文件都有内联函数的定义.然而,C++要求同一个函数的所有内联定义都必须相同.
- C++在哪里查找函数:假设在程序的某个文件中调用一个函数,C++将到哪里去寻找该函数的定义呢?如果该文件中的函数原型指出该函数是静态的,则编译器将旨在该文件中查找函数定义;否则,编译器(包括链接程序)将在所有的程序文件中查找.如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义.如果在程序文件中没有找到,编译器将在库中搜索.这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数(然而,C++保留了标准库函数的名称,即程序员不应使用它们).有些编译器-链接程序要求显式地指出要搜索哪些库.
9.2.9 语言链接性
- 链接程序要求每个不同的函数都有不同的符号名.
- 链接程序寻找与C++函数调用匹配的函数时,使用的方法与C语言不同.
- C和C++链接性是C++标准制定的说明符,但实现可提供其他语言链接性说明符.
9.2.10 存储方案和动态分配
- 编译器使用三块独立的内存:一块用于静态变量(可能再细分),一块用于自动变量,另外一块用于动态存储.
- 虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量.
- 注意:在程序结束时,由new分配的内存通常都将被释放,不过情况也并不总是这样.例如,在不那么健壮的操作系统中,在某些情况下,请求大型内存块将导致该代码块在程序结束不会被自动释放.最佳的做法是,使用delete来释放new分配的内存.
- 1.使用new运算符初始化
- 2.new失败时
- new可能找不到请求的内存量.在最初的10年中,C++在这种情况下让new返回空指针,但现在将引发异常std::bad_alloc.
- 3.new:运算符,函数和替换函数
- 如果您有足够的知识和意愿,可为new和delete提供替换函数,并根据需要对其进行定制.
- 4.定位new运算符
- 程序清单9.10 newplace.cpp
- 5.程序说明
- 6.定位new的其他形式
- 定位new函数不可替换,但可重载.
9.3 名称空间
9.3.1 传统的C++名称空间
- 声明区域declaration region是可以在其中进行声明的区域.
- 潜在作用域potential scope.变量的潜在作用域从声明点开始,到其声明区域的结尾.因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用.
- 然而,变量并非在其潜在作用域内的任何位置都是可见的.
9.3.2 新的名称空间特性
- 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中.因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量).
- 全局名称空间global namespace.
- 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突.名称空间中的声明和定义规则同全局声明和定义规则相同.
- 1.using声明和using编译指令
- using声明由被限定的名称和它前面的关键字using组成;如:using Jill::fetch;
- 在函数的外面使用using声明时,将把名称添加到全局名称空间中;
- using声明使一个名称可用,而using编译指令使所用的名称都可用.
- 2.using编译指令和using声明之比较
- 注意:假设名称空间和声明区域定义了相同的名称.如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错.如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本.
- 3.名称空间的其他特性
- 可以将名称空间声明进行嵌套
- 可以在名称空间中使用using编译指令和using声明
- 4.未命名的名称空间
- 该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾.具体的说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称.这提供了链接性为内部的静态变量的替代品.
9.3.3 名称空间示例
- 程序清单9.11 namesp.h
- 程序清单9.12 namesp.cpp
- 程序清单9.13 usenmsp.cpp
9.3.4 名称空间及其前途
- 指导原则
- 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量.
- 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量.
- 如果开发了一个函数库或类库,将其放在一个名称空间中.
- 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计.
- 不要在头文件中使用using编译指令.
- 导入名称时,首选使用作用域解析运算符或using声明的方法.
- 对于using声明,首选将其作用域设置为局部而不是全局.
- 使用名称空间的主旨是简化大型编程项目的管理工作.
9.4 总结
9.5 复习题
9.6 编程练习