第9章 内存模型和名称空间

本章内容:

 单独编译

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

 定位(placement)new运算符

 名称空间

9.1 单独编译

同C,C++鼓励将组件函数放在独立的文件中。
程序可分为三部分:
头文件:包含结构声明和使用这些结构的函数的原型
源代码文件:包含与结构有关的函数的代码(函数的功能实现源代码)
源代码文件:包含调用与结构相关的函数的代码(main调用功能函数)
头文件常包含内容:
 函数原型
 #define或const定义的符号常量
 结构声明
 类声明
 模板声明
 内联函数
不能将函数定义或变量声明放到头文件中,除非函数是内联的如果在头文件中包含一个函数定义,然后再其他两个文件(属于同一个程序)中包含该头文件,则同一个程序中将包含同一个函数的两个定义,将导致出错
被声明为const的数据和内联函数有特殊的链接属性,可以放在头文件中。
头文件管理
语法格式(如头文件coordin.h):

#ifndef COORDIN_H
#define COORDIN_H
// place include file contents here
#endif

上述代码表示仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H时,才处理#ifndef#endif之间的语句,防止函数多次声明。
==当有多个.cpp文件调用/加载/include头文件coordin.h时,只有第一个include该头文件coordin.h时才会创建加载coordin.h里边的内容,其余的则会直接跳到#endif==
多个库的链接
不同编译器创建的二进制模块(对象代码文件)很可能无法正确链接。两个编译器将为同一个函数生成不同的修饰名称,名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。
总结在链接编译模块时,确保所有对象文件或库都是由同一个编译器生成的(可通过重新编译源码来消除链接错误)

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

C++使用三种(C++11更新为四种)不同的方案存储数据,其区别在于数据保留在内存中的时间:
自动存储连续性:在函数定义中声明的变量(包括函数参数),其存储持续性是自动的。它们在程序开始执行(函数或代码块)时被创建,在程序(函数或代码块)执行完毕后,其使用的内存被释放。
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性为静态。它们在程序整个运行过程中都存在。
 线程存储持续性(C++11):并行编程相关。
动态存储持续性:用new运算符分配的内存一直存在,直到使用delete运算符将其释放或程序结束位置。其存储持续性为动态,也称自由存储或堆。

9.2.4 静态持续性、外部链接性

链接性为外部的变量称为外部变量(全局变量),定义在函数外部,其存储连续性为静态,作用域为整个文件,。
引用声明使用关键字extern,且不进行初始化(否则会导致分配存储空间)。
如果要再多个文件中使用外部变量,只需在一个文件包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。
在这里插入图片描述
上述代码中,所有文件都使用在file01.cpp中定义的变量catsdogs,但file02.cpp没有声明变量fleas所以无法访问;file01.cpp中的关键字extern非必不可少,可省略。
全局变量局部变量
全局变量不用传递参数,但其代价是程序不可靠;程序越能避免对数据进行不必要的访问,越能保持数据的完整性。

9.2.5 静态持续性、内部链接性

static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。链接性为内部的变量只能在其所属的文件中使用;但常规外部变量都具有外部链接性,可以在其他文件中使用。
在这里插入图片描述
上述代码会报错,程序包含两个errors的定义。可使用关键字static定义其中一个errors:
在这里插入图片描述
static指出标识符errors的链接性为内部,并非提供外部定义,因此没有违反单定义原则。
注意在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量,使用该变量的其他文件必须使用关键字extren声明它
可使用外部变量在多文件程序的不同部分之间共享数据
可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据名称空间还提供了另外一种共享数据的方法)。
程序清单9.7-twofile1.cpp—twofile2.cpp

//twofile1.cpp

#include <iostream>
using namespace std;
int tom = 3;
int dick = 30;
static int harry = 300;

//twofile2.cpp

#include <iostream>
using namespace std;
extern int tom;
static int dick = 10;
int harry = 300;

上述代码中,两个文件使用了同一个tom变量(地址相同),但使用了不同的dickharry变量(地址不同)。

9.2.6 静态存储持续性、无链接性

无链接性的局部变量:在代码块中定义的static修饰变量。该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在(类似全局变量只是只有代码块中可用)。

9.2.7 说明符合限定符

以下为存储说明符:
auto(C++11中不再是说明符):用于自动类型推断。
register:用于在声明中指示寄存器存储(C++11显示指出变量试自动的)。
static:用于作用域为整个文件的声明中时,表示内部链接性;用在局部声明中,表示局部变量的存储连续性为静态的。
extern:用于引用在其他地方定义的变量。
thread_local:指出变量的持续性和其所属线程的持续性相同。
mutable:可用它指出即使结构(或类)变量为const,其某个成员也可以被修改。
同一生命中不能使用多个说明符,thread_local除外(它可与static或extern结合使用)。
const:默认情况下全局变量的链接性为外部的,但const修饰的全局变量的链接性为内部的。即全局const定义就像使用了static说明符一样。

const int fingers = 10;     //same as static 
int main(void){

假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件,预处理将头文件的额内容包含到每个源文件中后,所有的源文件都将包含类似下面的定义:

const int fingers; 
const char *warning = “Wak”;

此时如果全局const声明的链接性像常规变量试外部的,则违反了单定义原则将报错。也就是说只能有一个文件可以包含前面的声明,而其他文件必须使用extern关键字来提供引用声明(另外,只有未使用extern关键字的声明才能进行初始化):

extern const int fingers;   //cannot be initialized

内部链接性意味着每个文件都有一套自己的一组常量,而非所有文件共享一套常量。每个定义都是其所属文件私有的(能够将常量定义放在头文件中的原因),这样在两个源代码文件中包括同一个头文件时将获得同一组常量。
如果希望某个常量的链接性为外部,可以使用extern关键字来覆盖默认的内部链接性:

extern const int states = 50;

区别于常规外部变量(定义时可不加extern只需在引用该变量时使用extern),上述代码表示在所有使用该常量的文件中都需使用extern关键字。
volatile
关键字volatile表明即使程序代码没有对内存单元进行修改,其值也可能发生变化。比如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间或信息,此时硬件(而非程序)可能修改其中的内容,或者两个程序可能互相影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找该值两次,而是将该值缓存到寄存器中。这种优化假设变量在这两次使用之间不会发生变化。如果不将变量声明为volatile,则编译器进行这种优化;反之,声明为volatile,则是告诉编译器不进行这种优化。

9.2.10 存出方案和动态分配

动态内存:使用C++运算符new(C函数malloc())分配的内存。
1. 使用new运算符初始化
 内置的变量类型(如intdouble)分配存储空间并初始化:在类型名后面加上初始值并将其用括号括起:

int *pi = new int(6);
double *pd = new double(99.99);

此种括号语法也适用于有合适构造函数的类。
 常规结构或数组初始化:需使用大括号的列表初始化(要求编译器支持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, 5, 6};

C++11还支持将列表初始化用于单值变量:

int *pi = new int {6};
double *pd = new double{99.99};

2. new失败时
之前(最初10年)返回空指针,现在是引发异常std::bad_alloc。
3. new:运算符、函数和替换函数
newnew[]调用函数:

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

上述两个函数被称为分配函数,位于全局名称空间中;对应的,也有deletedelete[]释放函数:

void * operator delete(void *);
void * operator delete[](void *);

4. 定位new运算符
new除了负责在堆中找到满足要求的内存块还有另一种变体称为定位new运算符——能够指定要使用的位置。可通过这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。
使用定位new特性需要包含头文件new,以下代码演示了new运算符的四种用法。
在这里插入图片描述

9.3 名称空间

当两个库可能都定义了同一名称的类,比如均包含List、Tree和Node类,用户可能希望使用一个库的List类,而使用另一个库的Tree类。这种冲突称为名称空间问题。
C++标准提供了名称空间工具以更好控制名称的作用域。

9.3.1 传统的C++名称空间

9.3.2 新的名称空间特性

通过定义一种新的声明区域来创建命名的名称空间,目的在于提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。(一班张三、二班张三)
如下,使用新的关键字namespace创建了两个名称空间Jack和Jill:

namespace Jack{
double pail;
void fetch(); //函数声明
int pal;
struct Well{};
}	

//
namespace Jill{
double bucket(double n);
double fetch;  //变量声明
int pal;
struct Hill{};
}

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。默认情况下,在名称空间中生命的名称的链接性为外部的(除非其引用了常量)。
访问名称空间中名称的方法:作用域解析运算符 ::,使用名称空间来限定该名称:

Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();

1. using声明using编译指令
using声明使特定的标识符可用,using编译指令使整个名称空间可用。
 using声明将特定的名称添加到它所属的声明区域中,语法格式:

using Jill::fetch;  //此代码位于main()中

上述代码将fetch添加到main()定义的声明区域中,完成改声明后,便可使用名称fetch代替Jill::fetch
示例代码:
在这里插入图片描述
上述示例using声明将名称添加到局部声明区域中,避免了将另一个局部变量也命名为fetch。 同其他局部变量一样,fetch也将覆盖同名的全局变量,如果需要读取全局变量则需添加::::fetch)。
 在函数外使用using声明将把名称添加到全局名称空间中:
在这里插入图片描述
上述代码中fetch(实为Jill::fetch)将为全局变量。
using声明使一个名称可用,using编译指令使所有的名称都可用。
using编译指令由关键字using namespace名称空间名组成,调用时不需要作用域解析运算符:: ,语法格式:

using namespace Jack;
//比如std:
#include<iostream>
using namespace std;     //std名称空间中的所有名称都可以直接使用,无需添加“std::”

using编译指令和using声明比较
如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名(类似隐藏同名的全局变量),但仍可以使用作用域解析运算符::
在这里插入图片描述

局部fetch,全局::fetchJill::fetch,此处区别于上边的using声明的示例。
综上可知,一般来说,using声明比using编译指令更安全。

9.3.3 名称空间及其前途

名称空间使用指导原则:
 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
 使用在已命名的名称空间中声明的变量,而非静态全局变量
 如果开发了一个函数库或类库,将其放在一个名称空间中
 不要再头文件中使用using编译指令(若如此会掩盖掉哪些名称可用,以及包含头文件的顺序可能影响程序的行为;若非要使用,应将其放在所有#include之后)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我宿孤栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值