CPP内存模型与名称空间
主要内容
- 单独编译
- 存储持续性、作用域、链接性
- 定位(placement)new运算符
- 名称空间
单独编译
程序文件结构
- 头文件:包含结构声明和使用这些结构的函数的原型
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
- 源代码文件:包含与结构有关的函数的代码
- 源代码文件:包含调用与结构相关的函数的代码
❗❗❗注意:
代表在标准头文件的目录中查找;包含自定义头文件,使用"myhead.h",代表在当前工作目录和源代码目录查找,如果没找到则到标准位置查找 使用预处理指令避免多次包含同一头文件
#ifndef COORDIN_H #define COORDIN_H //place include file contents here #endif
存储持续性、作用域、链接性
CPP11四种存储方案
- 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。
- 线程存储持续性(CPP11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
- 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
❗❗❗注意:静态变量默认初始化为0
说明符和限定符
说明符
auto:自动类型推断
register:在声明中指示寄存器存储
static:用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的
extern:引用声明,即声明引用在其他地方定义的变量
thread_local(CPP11新增):变量的持续性与其所属线程的持续性相同
mutable:结构(或类)变量为const,其mutable成员也可以被修改
struct data { char name[30]; mutable int accesses; } const data veep = {"claybourne Clodde", 0, ...}; strcpy(veep.name, "Joye Joux"); //not allowed veep.accesses++; //allowed
限定符
- const:内存被初始化后,程序便不能再对它进行修改;const全局变量的链接性为内部的,每个文件都有自己的一组常量,而不是所有文件共享一组常量,每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因
- volatile:不将值缓存到寄存器中,从内存中获取变量的值
动态分配
使用new运算符初始化
int *pi = new int (6); //*pi set to 6 double *pd = new double (99.99); //*pd set to 99.99 struct where {double x; double y; double z;}; where *one = new where {2.5, 5.3, 7.2}; //CPP11 int *ar = new int[4] {2, 4, 6, 7}; //CPP11
new失败:抛出异常std::bad_alloc
new运算符重载
void *operator new(std::size_t); //used by new void *operator new[](std::size_t); //used by new[]
定位(placement)new运算符
new负责在堆(heap)中找到一个足以能够满足要求的内存块。new运算符还有另一种变体,被称为定位(placement)new运算符,能够指定要使用的位置。
int *p1 = new int; //invokes new(sizeof(int))
int *p2 = new(buffer) int; //invokes new(sizeof(int), buffer)
int *p3 = new(buffer) int[40]; //invokes new(40*sizeof(int), buffer)
名称空间
using声明和using编译指令
using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; //a using declaration
using声明使一个名称可用,而using编译指令使所有的名称都可用
using namespace Jack; //make all the names in Jack available
可以给名称空间创建别名
namespace my_very_favorite_things {...}; namespace mvft = my_very_favorite_things; namespace MEF = myth::elements::fire; using MEF::flame;
匿名名称空间:提供了链接性为内部的静态变量的替代品
static int counts; //static storage, internal 1inkage int other(); int main() { ... } int other() { ... } //采用名称空间的方法如下 namespace { int counts; //static storage, internal linkage } int other(); int main() { ... } int other() { ... }
命名空间使用建议
- 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
- 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
- 如果开发了一个函数库或类库,将其放在一个名称空间中。事实上,CPP当前提倡将标准函数库放在名称空间std中,这种做法扩展到了来自C语言中的函数。例如,头文件math.h是与C语言兼容的,没有使用名称空间,但CPP头文件cmath应将各种数学库函数放在名称空间std中
- 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计
- 不要在头文件中使用using编译指令。首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为。如果非要使用编译指令using,应将其放在所有预处理器编译指令#include之后
- 导入名称时,首选使用作用域解析运算符或using声明的方法
- 对于using声明,首选将其作用域设置为局部而不是全局
参考文献
《CPP Primer Plus》