C++PrimerPlus第九章学习笔记——内存模型和名称空间

前言

此文为本人学习所做一些记录,仅做个人学习之用,加入了我的理解,如发现错误欢迎指正,邮箱:lujialun99 A T gmail.com

读到这一章时书中蹩脚的翻译和一些低级错误让我有些恼火,建议读英文版。

单独编译(Separate Compilation)

一个C++程序包含的组件函数通常会放在独立的文件中,编译器可以单独编译这些文件然后链接成可执行程序。单独编译的好处就是:如果要修改了一个文件,可以只重新编译该文件,然后将该文件与其他文件的编译版本链接,使得大程序的管理更便捷。

C++提供下面这一种组织程序的策略:

  • 头文件(.h):包含结构声明和使用这些结构的函数的原型
  • 源代码文件(.cpp):包含与结构有关的函数的代码
  • 源代码文件(.cpp):包含与调用结构相关的函数的代码

头文件中最好不要包含函数定义和变量声明,如果在同一个程序的两个文件中包含该头文件将会造成函数重复定义。下面列出的是头文件中常包含的内容:

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

包含头文件时,使用< >代表编译器将在存储标准头文件的文件位置查找;使用” “代表编译器首先查找当前的工作目录或源代码目录,没有找到则同上。在IDE中不要将头文件加入项目列表,也不要在源代码文件中使用#include来包含其他源代码文件。

头文件管理

在同一个文件中同一个头文件只能包含一次,C/C++为了避免多次包含同一头文件使用了基于预处理器编译指令#ifndef技术。请看下面实例:

#ifndef COORDIN_H_
#define COORDIN_H_

//在这编写头文件代码

#endif

COORDIN_H_是根据文件名coordin.h取的名称,使用#define COORDIN_H_定义该名称,然后在后面包含头文件的内容。在编译器首次遇到该文件时,如果发现COORDIN_H_没有被定义,将定义COORDIN_H_并包含该头文件的内容。如果在同一个文件中遇到其他包含coordin.h的代码,编译器知道COORDIN_H_已经被定义,就会跳到#endif后面一行。这种方式不能防止编译器将文件包含两次,但是会忽略除第一次包含之外的所有内容。

Markdown

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

C++存储方式是通过存储连续性、作用域和链接性来描述的。

C++11含四种存储数据的方案:
1. 自动存储连续性:函数定义中声明的变量(包括函数参数),代码块执行完内存释放,包括两种变量
2. 静态存储连续性:函数外定义的变量和使用static定义的变量,他们在整个程序运行过程中都存在,包括三种变量。
3. 线程存储连续性(C++11才有):并行编程中用到,使用thread_local声明的变量生命周期与所属线程一样长。
4. 动态存储连续性:new分配,delete或程序结束释放。

扫描二维码关注公众号,回复: 9192220 查看本文章

作用域和链接

作用域描述了名称在文件的多大范围内可见。

全局变量 静态变量 自动变量
作用域 定义位置到文件结尾 取决于如何被定义 局部

链接性(linkage)描述了名称如何在不同单元间共享

  • 链接性如果为外部,则名称可在文件间共享
  • 链接性如果为内部,则名称只能由一个文件中的函数共享
  • 自动变量的名称没有链接性,因为他们不能共享

自动存储连续性

在默认情况下,在函数声明的函数参数和变量的存储连续性为自动,作用域为局部,没有链接性。当程序执行这些变量所属的代码块时,才会为其分配内存当函数结束时,这些变量将消失。

自动变量和栈

管理自动变量的方法是,留出一段内存,并将其视为栈,以管理变量的增减。之所以被称为栈,是由于新数据象征性的被放在原有数据的上面(相邻内存单元)。栈的默认长度取决于实现。

Markdown

寄存器变量

关键字register由C语言引入,它建议编译器使用CPU寄存器来存储自动变量,旨在提高访问变量的速度。

register int a;

现在使用register与以前的auto相同,保留这个关键字的原因是避免使用了该关键字的现有代码非法。

静态存储连续性

C++为静态存储连续性提供了三种链接性:

  1. 外部链接性(代码块外声明)
  2. 内部链接性(代码块外声明,并且使用static限定)
  3. 无链接性(代码块内声明,并且使用static限定)

这三种链接性在整个程序执行期间都存在,由于静态变量的数目在程序运行期间是不变的,所以程序不需要使用特殊的装置来管理他们,编译器分配固定的内存块来存储静态变量。如果没有初始化静态变量(数组、结构),编译器会默认置为0(零初始化)。

Markdown

静态变量的初始化分为静态初始化和动态初始化。

外部链接性

链接性为外部的变量简称为外部变量,也称为全局变量。由于在每个使用外部变量的文件中,都必须声明它,但是C++只允许变量有一次定义,所以C++提供两种变量声明:
1. 定义声明(定义),给变量分配存储空间
2. 引用声明(声明),不给变量分配存储空间,引用已有变量extern

//file1.cpp
int a=1;
-----------
//file2.cpp
extern int a;

Markdown

内部链接性

将static用于作用于为整个文件的变量时,该变量的链接性为内部的,只能用于所属的文件而不能用于其他文件,因此可以有以下用法:

//file1.cpp
int a=1;
--------------
//file2.cpp 静态变量将隐藏常规外部变量
static int a;

无链接性

将static用于代码块中定义的变量,将导致变量的存储连续性为静态的,这意味着该变量在代码块不处于活动状态时任然存在。

说明符或限定符

有些被称为存储说明符或CV-限定符的C++关键字提供了其他有关存储的信息。

  • auto(C++11不再是说明符,现在用于自动类型推断)
  • static
  • register
  • extern
  • thread_local(可与static或extern结合使用)
  • mutable(指出即使结构变量为const其某个成员也可以被修改)
  • const
  • volatile(建议不对该语句进行优化,一定执行)

有关const

const全局变量的链接性为内部的,如果希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性:

extern const int a = 1;

在这种情况下所有使用该常量的文件都需使用extern来声明。

函数的链接性

所有函数的存储连续性都是静态的,整个程序执行期间一直都存在;默认情况下链接性为外部的,可以在文件间共享。还可以使用static将函数的链接性设置为内部的,必须同时在函数原型和定义中使用该关键字。

语言链接性

C++编译器执行名称矫正(或修饰)为重载函数生成不用的符号名称。
如:spiff(double,double)——>_spiff_d_d,这种方法被称为C++语言链接。

存储方案和动态分配

前面介绍的内存分配方案不适用与使用new和malloc分配的内存,这种内存被称为动态内存。程序使用的内存可以粗略的分为三块:

  • 静态变量
  • 自动变量
  • 动态存储

有关new这部分翻译有些混乱,我省略一些内容。

定位new运算符

定位运算符允许使用指定的内存块,使用时要包含头文件new。
可以这样使用定位运算符new:

char buffer[100];
int *p;
p=new (buffer) int [20]

另外delete只能释放常规new运算符分配的堆内存,数组buffer不在delete管理范围之内。

名称空间

为解决名称冲突的问题使用名称空间工具、

新的C++名称空间特性

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

示例:

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

namespace Jerry{
    char other;
    void   fetch();
    int pal;
    struct Hill {..};
}

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。(默认链接性为外部,除非使用了常量),名称空间中的名称不会与其他名称空间中的名称冲突,已存在的名称空间可以加入新的名称。如下:

namespace Tom{
    char *name;
}

访问某一名称空间中的名称要使用作用域解析符::

Tom::other='a';

using 声明和using编译指令

  • using 声明使特定的标识符可用 :using Tom::pal=1;
  • usng编译指令使整个名称空间所有的名称都可用

使用using 声明和using编译指令可能会增加名称冲突的可能性,使用作用域解析运算符就不会存在二义性。

using 声明和using编译指令的比较

如果某个名称在函数中已经声明了,就不能用using声明导入相同的名称。

使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身的最小申明区域中声明了名称一样。(这句话没看懂)

如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。

详细介绍还是看书吧P328-P329。

名称空间其他特性

1.名称空间可以嵌套

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

    namespace Jerry{
    char other;
    void   fetch();
        int pal;
        struct Hill {..};
    }
}

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

3.可以给名称空间创建别名

namespace TJ=Tom::Jerry;

未命名的名称空间

namespace 
{
    int ice;
}

不能在其他文件使用,相当于链接性为内部的静态变量。

名称空间及其前途

Markdown

发布了20 篇原创文章 · 获赞 25 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/Timekeeperl/article/details/70478148