《C++ Primer Plus》第9章 内存模型和名称空间
第9章 内存模型和名称空间
单独编译
将组件函数放在独立的文件中,可以单独编译这些文件,然后将它们链接成可执行的程序。如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接,这使得大程序的管理更便捷。
一种组织程序的策略是:
- 头文件:包含结构声明和使用这些结构的函数的原型
- 源代码文件:包含与结构有关的函数的代码
- 源代码文件:包含调用与结构相关的函数的代码
头文件中常包含的内容:
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
在包含头文件时,文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录,若未找到,则在标准位置查找。因此在包含自己的头文件时,应使用引号而不是尖括号。
不要使用#include来包含源代码文件,这样做将导致多重声明。
在一个文件中只能将同一个头文件包含一次,否则可能定义同一个结构两次,这将导致编译错误。基于预处理器编译指令#ifndef(即if not defined)可避免多次包含同一个头文件,下面的代码片段意味着仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句,否则将忽略#ifndef和#endif之间的语句:
#ifndef COORDIN_H_ #define COORDIN_H_ //根据include文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称 ... #endif
在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的,防止不同编译器为同一函数生成不同修饰名,导致链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。
存储持续性、作用域和链接性
存储持续性:
- 自动存储持续性
- 静态存储持续性
- 线程存储持续性
- 动态存储持续性
不同持续性的存储方案区别在于数据保留在内存中的时间。
作用域描述了名称在文件(翻译单元)的多大范围可见,链接性描述了名称如何在不同单元间共享(作用域描述的是名称在文件内的可见情况,链接性描述的是名称在文件间的可见情况)。
在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。
如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。两个同名的变量,一个位于外部代码块中,另一个位于内部代码块中,程序执行内部代码块中的语句时,内部代码块中新的定义隐藏了以前的定义。在程序离开该代码块时,原来的定义又重新可见。
可以使用任何在声明时其值为已知的表达式来初始化自动变量。
自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。
静态存储持续性变量有三种链接性:外部链接性、内部链接性和无链接性。这三种链接性都在整个程序执行期间存在。
静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理他们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。
如果没有显式的初始化静态变量,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0,这种变量被成为零初始化的。
创建不同链接性的静态持续变量的方法:
- 链接性为外部的静态持续变量:在代码块的外面声明它
- 链接性为内部的静态持续变量:在代码块的外面声明它,并使用static限定符
- 没有链接性的静态持续变量:在代码块内声明它,并使用static限定符
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动 | 自动 | 代码块 | 无 | 在代码块中 |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数中 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数中,使用关键字static |
除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件时初始化变量。动态初始化意味着变量将在编译后初始化。首先,所有静态变量都被零初始化,接下来,如果使用常量表达式初始化了变量,且编译器仅根据文件内容(包括被包含的头文件)就可以计算表达式,编译器将执行常量表达式初始化,必要时,编译器将执行简单计算,如果没有足够的信息,变量将被动态初始化。
链接性为外部的变量通常称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,可以在main()前面或头文件中定义它们,可以在文件中位于外部变量定义后面的任何函数中使用它,因此外部变量也成为全局变量。
一方面,在每个使用外部变量的文件中,都必须声明它,另一方面,C++有“单定义规则”,该规则指出,变量只能有一次定义。为满足这种需求,C++提供了两种变量声明。一种是定义声明,或简称为定义,它给变量分配存储空间;另一种是引用声明,或简称声明,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化,否则,声明为定义,导致分配存储空间。
如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。
定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。作用域解析运算符(::)放在变量前面时,表示使用变量的全局版本。
将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。在多文件程序中,链接性为内部的变量只能在其所属的文件中使用;但常规外部变量都具有外部链接性,即可以在其他文件中使用。
可使用外部变量在多文件程序的不同部分之间共享数据;可以使用链接性为内部的静态变量在同一个文件的多个函数之间共享数据。如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突。
在代码块中使用static时,将导致局部变量的存储持续性为静态的,这意味着虽然该变量只在该代码块中可用,但他在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。
在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。但可以使用extern关键字来覆盖默认的内部链接性:
extern const int states = 50;
在这种情况下,必须在所有使用该常量的文件中使用extern关键字来声明它。
在函数或代码块中声明const时,其作用域为代码块,仅当程序执行该代码块中的代码时,该常量才是可用的。这意味着在函数或代码块中创建常量时,不必担心其名称与其他地方定义的常量发生冲突。
所有函数的存储持续性都自动为静态的,即在程序执行期间都一直存在。在默认情况下,函数的链接性为外部的,即可以在文件间共享。可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的,不过这是可选的。还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用,但必须同时在原型和函数定义中使用该static关键字。这意味着该函数只能在这个文件中可见,还意味着可以在其他文件中定义同名的函数。在定义静态函数的文件中,静态函数将覆盖外部定义。
对于每个非内联函数,程序只能包含一个定义。对于链接性为外部的函数来说,这意味着在多个文件程序中,只能由一个文件包含该函数的定义,但使用该函数的每个文件中都应包含其函数原型。
内联函数不受单定义规则的约束,故允许将内联函数的定义放在头文件中,这样,包含了头文件的每个文件都有内联函数的定义。然而,C++要求同一个函数的所有内联定义都必须相同。
使用C++运算符new分配的内存被称为动态内存。动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。
虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。
初始化动态分配的变量,可在类型名后面加上初始值 ,并将其用括号括起:
double * pd = new double (99.99);
这种括号语法也可用于有合适构造函数的类。
要初始化常规结构或数组,需要使用大括号的列表初始化:
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};
也可将列表初始化用于单值变量。
定位new运算符能够指定要使用的位置,要使用定位new特性,需要包含头文件new。
名称空间
使用关键字namespace创建名称空间。
通过作用域解析运算符来访问名称空间中的名称。
存储描述 | 时间 | 空间 | 如何声明 | 如何使用 | 备注 | |||
持续性 | 作用域 | 链接性 | ||||||
变量 | 自动 | 自动 | 代码块 | 无 | 在代码块中 | |||
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register | ||||
静态 | 静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static | |||
静态,外部链接性 | 静态 | 文件 | 外部 |
不在任何函数内 |
在外部文件中使用时,需使用extern关键字重新声明 |
根据单定义规则,只需在一个文件中包含该变量的定义(进行初始化的为定义,否则是声明),但在使用变量的其他所有文件中,都必须使用关键字extern声明它 需要注意的是,若在头文件中定义了静态外部链接性变量,则相当于在任一包含此头文件的外部文件中均定义了此变量,将引发重定义错误 |
||
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static | ||||
动态 | 由new和delete控制 | |||||||
类数据成员 | 类作用域 | 在类中 | ||||||
常量 | const | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字const | 不使用extern关键字定义的const静态变量虽然链接性为内部,但若在头文件中定义,则包含此头文件的外部文件可以不加声明的使用该静态变量 | ||
静态 | 文件 | 外部 | 不在任何函数内,使用关键字extern及const | 在外部文件中使用时,需使用extern关键字重新声明 | ||||
类数据成员 | 静态 | 类作用域 | 在类中,使用关键字static(还可使用枚举) | 声明中使用关键字static,定义如果是独立的(非内联)则不用关键字static | ||||
函数 | 静态,外部链接性 | 静态 | 文件 | 外部 | ||||
静态,内部链接性 | 静态 | 文件 | 内部 | 使用关键字static | ||||
类成员函数 | 类作用域 | 在类中,使用关键字static | 声明中使用关键字static,定义如果是独立的(非内联)则不用static;不能通过对象调用静态成员函数,静态成员函数甚至不能使用this指针,如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它;静态成员函数不与特定的对象相关联,因此只能使用静态数据成员 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)