《C++ Primer Plus》:内存模型和名称空间

本章内容概览

  • 单独编译
  • 存储持续性、作用域和连接性
  • 定位new运算符
  • 名称空间

单独编译

C++鼓励程序员将组件函数放到独立的文件中,可以单独编译这些文件,然后将它们链接成可执行的程序。

我们可以将许多东西放在头文件中,然后再源代码文件中包含这些头文件,头文件常包含以下内容:

  • 函数原型
  • 使用#define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数

同一个文件只能包含一个头文件一次,不过大部分时候无法避免,所以我们要在头文件中声明一些预处理命令,有两种方式:

#pargma once
--------------------
#ifndef ***_H_
#define ***_H_  // *** 是头文件名大写
...
#endif

存储持续性、作用域和链接性

C++11标准,使用4种不同的方案来存储数据:

  • 自动存储持续性:在函数定义中声明的变量的存储持续性是自动的,它们在程序开始执行其代码块时创建,结束时释放内存。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量,在程序整个运行过程中存在。
  • 线程存储持续性:如果变量使用关键字thread_local声明,则其生命周期和所属线程一致。
  • 动态存储持续性:使用new分配的内存一直存在,直到使用delete运算符。

作用域和链接

作用域为局部的变量只在定义它的代码块中使用,作用域为全局的变量在定义位置到文件结尾之间都可以使用。自动变量的作用域为局部,静态变量的作用域为全局还是局部取决于它是如何被定义的。在函数原型作用域中使用的名称只在包含参数列表的括号内可用。在类中声明的成员的作用域为整个类,在名称空间中声明的变量的作用域为整个名称空间。

C++函数的作用域可以是整个类或整个名称空间,但不能是局部的(不能在代码块中定义函数)。

自动持续变量

自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。新数据象征性地放在原有数据上面,程序使用完后,将其从栈删除。程序使用两个指针来跟踪栈,一个指针指向栈底——栈地开始位置,另一个指向堆顶——下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元,函数结束后,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。

静态持续变量

C++为静态存储持续性提供了3种链接性:外部链接性(可在其它文件中访问)、内部链接性(只能在当前文件访问)和无链接性(只能在当前函数或代码块种访问)。

要想创建链接性为外部的静态持续变量,必须在代码块外声明;要创建链接性为内部的静态持续变量,必须在代码块的外面声明,并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内声明,并使用static限定符(与自动持续变量的区别是,它会一直存在)。

静态持续变量声明后默认是0.

静态持续性、外部链接性

在每个使用外部变量的文件中,都必须声明它,同时C++有单定义规则,即变量只能定义一次。为满足这种需求,C++提供了两种变量声明。一种是定义声明,它给变量分配存储空间;另一种是引用声明,它不给变量分配存储空间,因为它引用已有的变量。

引用声明使用extern关键字,且不进行初始化,否则,声明为定义,导致分配存储空间:

double up;  //定义
extern int blem;  //引用声明,blem在其它某处定义
extern char gr = 'z';  //定义

如果要在多个文件中使用外部变量,只需在一个文件中包含该便令的定义,在使用该变量的其它所有文件中,都必须使用关键字extern。

说明符和限定符

存储说明符:

  • auto (C++11中不再是)
  • register
  • static
  • extern
  • thread_local(C++11新增)
  • mutable
  1. cv-限定符
  • const
  • volatile

volatile:程序代码没有对内存单元进行修改,其值也可能发生变化。说白了其实是让编译器不使用某种优化。

  1. mutable
    用来指出即使结构或类变量为const,其某个成员也可以被修改,如:
struct data
{
    char name[30];
    mutable int accesses;
    ...
};
  1. const
    在C++中,默认情况下全局变量链接性为外部,但const全局变量是链接性为内部的。

如果想要让某个常量的的链接性为外部,则可以使用extern关键字来覆盖默认的内部链接性:

extern const int states = 50;

函数和链接性

默认情况下。函数的链接性为外部的,可以在文件间共享,可以使用static关键字来表示其链接性为内部,使其只能在一个文件中使用。静态函数会覆盖外部定义。

存储方案和动态分配

  1. 使用new运算符初始化
int* pi = new int (6);
double* pd = new double (99.99);

上述方法是C++98的方式,C++11可以这么做:

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};
int* pin = new int {6};
  1. new失败时
    new可能找不到请求的内存量,这是会引发异常std::bad_alloc。

  2. new:运算符、函数和替换函数
    new和new[]分别调用:

void* operator new(std::size_t);
void* operator new[](std::size_t);

这些函数被称为分配函数,位于全局名称空间中,同时也有由delete和delete[]调用的释放函数:

void* operator delete(std::size_t);
void* operator delete[](std::size_t);
  1. 定位new运算符
    new运算符还有一种变体,定位运算符,它能够指定要使用的的位置。要使用的话,首先要先包含new头文件,然后将new运算符用于提供了所需地址的参数,比如:
cahr buffer1[50];
int* pa;
p1 = new(buffer1) int[20];

名称空间

C++名称空间通过namespace关键字定义:

namespace Jack
{
    double pail;
    void fetch();
    int pal;
    struct Well{...};
}

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

访问名称空间中的名称使用::作用域解析运算符:

Jack::pail = 12.34;

using声明可以让某一特定标识符可用:

using Jack::fetch;

using 编译指令让名称空间中所有名称都可用:

using namespace Jack;

同时C++不允许同名名称使用using声明。

名称空间可以嵌套:

namespace elements
{
    namespace fire
    {
        int flame;
    }
    float water;
}

flame即elements::fire::flame,还可以:

using namespace elements::fire;

名称空间中可以使用using编译指令和using声明:

namespace myth
{
    using Jill::fetch;
    using namespace elements;
    using std::cout;
    using std::cin;
}

如果要访问Jill::fetch,可以使用myth::fetch,同时也可以使用Jill::fetch,无冲突名称还可以直接访问。

名称空间是可以嵌套的,比如

using namespace myth;
<=>
using namespace myth;
using namespace elements;

还可以给名称空间创建别名:

namespace mvft = myth;

你可能感兴趣的:(《C++ Primer Plus》:内存模型和名称空间)