首页 C++ Primer Plus-第9章 内存模型和名称空间
文章
取消

C++ Primer Plus-第9章 内存模型和名称空间

9.1 单独编译

  • 头文件常包含的内容:

    • 函数原型

    • 使用#defineconst定义的符号常量

    • 结构声明

    • 类声明

    • 模板声明

    • 内联函数

  • 如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;

  • 如果文件名包含在双引号中,则编译器将首先查找当前的当前的工作目录或源代码目录(或其他目录,这取决于编译器)

  • 在UNIX系统中编译由多个文件组成的C++程序:

    xhmjxJ.jpg

  • 头文件管理:

    • 在同一个文件中只能将同一个头文件包含一次

    • 可以使用基于预处理编译指令#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种变量存储方式:

    xTm26O.jpg

  • 零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化
  • 外部变量:是在函数外部定义的,也称全局变量

  • 单定义规则:变量只能有一次定义

    • 定义声明(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来声明函数(不用再次定义否则报错噢)

  • 和变量类似,函数也可以使用externstatic

    xHqqbD.jpg

  • 语言链接性(详情见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):从声明点开始,到声明区域结尾,因此比声明区域小

    xHjYGj.jpg xHjJiQ.jpg

  • 新的名称空间特性:

  • 通过定义一种新的声明区域来创建命名的名称空间,目的之一是提供一个声明名称的区域

  • 一个名称空间中的名称不会与另一个名称空间的相同名称发生冲突

  • 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中,所以在默认情况下,在名称空间中声明的名称的链接性为外部的(除非引用了常量)

  • 全局名称空间(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声明,首选将其作用域设置为局部而不是全局

本文由作者按照 CC BY 4.0 进行授权

C++ Primer Plus-第8章 函数探幽

C++ Primer Plus-第10章 对象和类