C++:C++PrimerPlus第六版:Chapter9:内存模型和名称空间


本章节从三个大方面做总结

  1. C++多个源文件组织方式
  2. C++存储方案
  3. C++名称空间
  4. 说明符和限定符
  5. 函数链接性和语言链接性
    💚 在源文件组织方式这一章:我会分析总结C++关于多个源文件组织策略,在第二章:我会从C++存储持续性,作用域和链接性来分析存储模型,在第三章节:我会分析名称空间标识符使用,其中重点是第二章节。

1. C++源文件组织策略

我们先看下面程序清单:
coordin.h

#ifndef COORDIN_H_
#define COORDIN_H_
struct polar
{
	double distance;  // distance from origin
	double angle; // direction from origin
};

struct rect
{
	double x; // horizontal distance from origin
	double y; // vertical distance from oeigin
};

// prototypes
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif

main.cpp

#include<iostream>
#include "coordin.h"  // structure templates function
using namespace std;
int main()
{
	rect rplace;
	polar pplace;
	cout<< "Enter the X and Y values: ";
	while(cin >> rplace.x >> rplace.y)
	{
    	pplace = rect_to_polar(rplace);
    	show_polar(pplace);
    	cout << "Next two numbers(q to quit):";
    }
    cout << "Bye ! \n";
    return 0;
}

Coordin.cpp

#include<iostream>
#include<cmath>
#include "coordin.h"  
polar rect_to_polar(rect xypos)
{
	using namespace std;
	polar answer;
	answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
	answer.angle = atan2(xypos.y,xypos.x);
	return answer;
}

void show_polar(polar dapos)
{
	using namespace std;
	const double Rad_to_deg = 57.29577951;
	cout << "distance = " << dapos.distance;
	cout<< ", angle = " << dapos.angle * Rad_to_deg;
	cout << " degrees\n";
}

💚 下面我们通过 “ g++ ” 编译指令来编译链接 main.cpp 和 coordin.cpp 两个源文件。
在这里插入图片描述
注意点:

  1. 在同一个源文件cpp中 只能将同一个头文件包含一次,所以 coordin.h 使用: #ifndef 和 # endif 语句 ,如果没有使用预处理器编译指令 #define 定义的名称 COORDIN_H 时,才处理 #ifndef 和 # endif 之间的语句。
  2. 虽然我们讨论的是根据文件进行单独编译,但是为了保持通用性,C++标准使用了术语翻译单元 (translation unit),而不是文件。

2. 内存存储方案

首先我们介绍变量在内存的时间,即生命周期。
有四种不同方案来存储数据。

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。他们在程序开始执行其所属函数或代码块中被创建,在执行完函数或代码块时,他们使用的内存被释放。通常意义上,我们把这称之为:栈变量C++ 有两种存储持续性为自动的变量
  • 静态存储持续性:在函数定义外定义的变量和 关键字 static 定义的变量的存储持续性都称为静态,他们在整个程序运行过程中都存在。C++有三种存储持续性为静态的变量
  • 线程存储持续性:当前,多核处理器很常见,这些CPU可以处理多个任务,这让程序能够将计算放在可并行处理的不同线程中,如果变量时使用关键字 thread_Local 声明的,则其生命周期与所属线程一样长。
  • 动态存储持续性:用 new 运算符分配的内存将一直存在,直到使用 delete运算符将其释放或程序结束为止,这种内存的存储持续性为动态,有时被称为 自由存储或堆。

💚💚💚
下面介绍变量的作用域(可被程序使用)以及链接性的细节。
作用域(ascope 😃
描述了名称在文件(翻译单元)的多大范围内可见: 例如 :函数中定义的变量可在该函数中使用,但不能在其他函数中使用。
链接性(linkage):
描述了名称如何在不同单元间共享,链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中函数共享,例如:自动变量的名称没有链接性,因为它们不能共享。

💚

  1. 自动变量的作用域为局部。
  2. 静态变量的作用域为全局还是局部:这取决于变量该如何定义。如:在函数原型作用域定义,作用域为局部。 在类中声明的成员的作用域为整个类,在名称空间中声明的变量作用域为 整个名称空间。

2.1 自动存储持续性

默认情况下,函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。
💚 自动变量的初始化

int w;   // value of w is 模糊
int x = 5;  // initialized with a numeric literal
int big = INT_MAX-1;  // initialized with a constant expression
int y = 2*x;  // use previously determined value of x
int z = 3*w;  // use new value of w 

💚 自动变量和栈
例子:定义函数 fib(int real, long tell) ,我们来看下 函数栈运行流程。
注意:程序中使用两个指针来跟踪栈,一个指针指向栈底:栈的开始位置,一个指针指向栈顶:下一个存储单元。
在这里插入图片描述

2.2 静态存储持续变量

C++ 为静态存储持续性变量提供了3种 链接性:

  • 外部链接性(可在其他文件中访问)
  • 内部链接性(只能在当前文件中访问)
  • 无链接性(只能在当前函数或代码块中访问)
    这三种链接性都在整个 程序中存在,与自动变量相比,它们的寿命更长。
    🧡重点:由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊装置(如栈)来管理他们,编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存储。
    下面,我们有一个图来说明,如何创建上述三种变量。
    在这里插入图片描述
    🧡 静态变量的初始化
    除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。
    零初始化:意味着将变量设置为零,对于标量类型,零将被强制转换为合适的类型。 如: 空指针用0表示,结构成员被零初始化,填充位将都被设置为零。
    常量初始化:变量设置为常量
    🧡零初始化和常量初始化决定因素
    零初始化和常量初始化都被称为静态初始化,首先,所有静态初始化都被零初始化,不管程序员是否显示初始化了它,接下来,如果使用常量初始化,编译器将指针常量表达式。
#include<cmath>
int x;    // zero-initialization
int y = 5;  // constant ant-expression initialization
long z = 13*13;  // constant ant-expression initialization
int enough = 2*sizeof(long);  // constant expression initialization
// 因为:pi 的计算需要链接 函数 atan()且被执行时,才能计算出来,它被称为动态初始化
const double pi = 4.0*atan(1.0);// dynamic initialization

💚 💚静态持续性,外部链接性
链接性为外部的变量通常简称为 外部变量,它们的存储持续性为静态(整个程序生命周期),作用域为整个文件。
这里有一个概念:单定义规则
一方面,在每个使用外部变量的文件中,都必须声明它,另一方面,C++中有 “单定义规则(one definition Rule ODR)”,该规则指出,变量只能定义一次,为了满足这种需求,C++提供了两种变量声明:

  1. 定义声明(defining declaration):或简称定义(declaration) 它给变量分配存储空间,
  2. 引用声明(referencing declaration ):或简称声明declaration 它不给变量分配存储空间,因为它引用已有变量
  3. 引用声明使用关键字 extern, 且不进行初始化,否则,声明即为定义,导致分配存储空间。
double up;  // definition, up is 0
extern int blem;  // blem defined else where
extern char gr = 'z'; // definition because initialized

下面例子说明了,如果要在多个文件中使用外部变量,只需要在一个文件中定义该变量,且在使用该变量的文件中,必须使用关键字 extern。
在这里插入图片描述

💚 💚静态持续性,内部链接性
将 static限定符用于作用域为整个文件的变量时,该变量的链接性为内部,存储持续为静态(整个程序生命周期)
在这里插入图片描述
💚💚静态存储持续性,无链接性
这种变量是这样创建的:将static 限定符用于代码块中定义的变量,这将会使局部变量的存储持续性为静态(扩展了变量生命周期),但它无链接性,作用域仅限于代码块。
🧡 初始化:程序只在启动时进行一次初始化,以后再调用函数,并不会像自动变量再次初始化

2.3 存储方案和动态分配

前面我们谈了C++变量静态存储和自动存储模型,下面我们来讲讲C++运算符 new(或者C函数 malloc()) 分配的内存,这被称为动态内存。
动态内存由运算符 new/delete 控制,不是由作用域和链接性规则控制。

  1. 动态内存可用在一个函数中分配,另一个函数中释放
  2. 可用用来跟踪动态内存的自动和静态指针变量。
    🧡例如:当你打算使用由别人编写的库文件的时候, 动态内存可用让 函数申请并返回一个指向内存块的指针,也就是从函数返回内存
// 基本思路:在函数里调用 new 语句为某种对象或某种基本数据类型分配一块内存,再把那块内存的地址返回给程序的主代码,主代码将使用那块内存
// 并在完成相关操作后将该内存离开立刻释放

#include<iostream>
using namespace std;

// 定义一个返回值为 指向整型的指针地址函数newInt ,它需要传入一个参数,为整型参数
int* newInt(int value) {
	// 在堆中申请整型的内存块,此内存块与整型指针 myInt 相同
	int* myInt = new int;
	// 将指针指向的内存地址赋值
	*myInt = value;
	// 从函数返回内存
	return myInt;
}

int main()
{
	int *x = newInt(20);
	cout<< *x;
	delete x;
	x = NULL;
	return 0;
}
// 打印结果
可以正常打印 20

💚💚 使用new 运算符初始化
初始化内置的标量类型(int 或者 double)分配存储空间并初始化,可在类型名后面加上初始值。

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;
	doubel x;
};
// 列表初始化: 结构体
where *one = new where{2.5,5.3,7.2};
// 列表初始化: 数组
int *ar = new int[4] {2,4,6,7};

// 列表初始化:单值变量
double *pdo = new double{99.99}

💚💚 new/delete 操作符函数原型
运算符 new 和 new[] 函数原型如下:
这些函数被称为 分配函数(alloction function),他们位于全局名称空间中,同样,也有delete和delete[] 调用的 函数原型

void *operator new (std::size_t);   // useed by new
void *operator new[](std::size_f); // used bt new[]

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

由于 std::size_t 是一个 typedef ,对应于合适的整型,所以,下面这样的语句会做如下转换

int *pi = new int;
// 等价于
int *pi = new(sizeof(int));

int *pa = new int[40];
// 等价于
int *pa = new (40* sizeof(int))

3. 名称空间

在C++中,名称可以是变量,函数,结构,枚举,类以及类和结构的成员,随着项目增大,名称相互冲突的可能性也随着增加。
C++标准提供了名称空间工具,以便更好地控制名称的作用域。
🧡下面我们直接学习:新的名称空间特性
C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域,一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。

💚💚 我们来学习下 using 声明和 using 编译指令
C++体用了两种机制来简化对名称空间中名称的使用。 using 声明是特定的标识符可用, using 编译指令使整个名称空间可用。
在这里插入图片描述
💚💚 名称空间嵌套

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

// 可以使用 using 编译指令和 using 声明
using namespace elements::fire;

4. 说明符和限定符

有些被称为存储说明符(storge class specifier) 或 cv-限定符(cv-qualifier)的C++关键字提供了其他有关存储的信息,下面是存储说明符:

  • auto
  • register
  • static
  • extern
  • thread_local
  • mutable
    🧡
  1. C++ 规定在同一个声明中不能使用多个说明符,但是thread_local除外,它可与 static 或 extern 结合使用。
  2. 在C++11 之前 auto 指出变量为自动变量,但在 C++11之后,auto用于自动类型推断。
  3. register 用于在声明中指示寄存器存储,而在 C++11中,它只是显示的指出变量时自动的
  4. static 被用在作用域为整个文件声明中时,表示内部链接性,被用于局部声明中时,表示局部变量的存储持续性为静态
  5. extern表明是引用声明,即声明引用在其他地方定义的变量
  6. thread_local 指出变量的持续性与所属线程的持续性相同,thread_local 变量之于线程,犹如常规静态变量至于整个程序。

💚💚 c-v 限定符号

  • const
  • volatile
    这就是 c-v限定符。
    🧡
  1. 我们知道 const 限定符,在内存被初始化后,程序便不能再对它进行修改
  2. volatile 表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。

例如:假设将一个指针指向某个硬件位置,或者两个程序可能互相影响,共享数据,该关键字就是为了改善编译器的优化能力。
如果没有该关键字:编译器发现程序在几条语句中两次使用了某个变量,编译器可能优化成这样:不让程序查找这个值两次,而是将这个值缓存在寄存器中。
但是没有该关键字,编译器就不会进行这种优化,也不会将值缓存到寄存器中。

💚💚 mutable 修饰符
可以用它来指出,即使结构(类)变量为 const , 其某个成员也可以被修改

struct data
{
	char name[30];
	mutable int accesses;
};
const data veep = {"Claybourne clodde",0};
strcp(veep.name,"Joye");   // not allowed
strcp.accesses++ ; // allowed

💚💚 const 和指针一起使用
const和指针一起使用

5. 函数和链接性

  1. 和变量一样,函数也有链接性。C++ 不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间一直存在,默认情况选,函数的链接性为外部的,即可以在文件间共享。
  2. 可以在函数的原型中使用 extern 关键字来指出 函数是在 另外一个文件中定义的。
  3. 使用static 关键字将函数的链接性设置为 内部的,使之只能在一个文件中使用。但是它必须同时在函数原型和定义中使用该关键字。
static int private(double x);
static int private(doubel x)
{
	......
}
// 使用 static 限定:可以让函数在其他文件定义同名函数,这也是符合单定义规则
  1. 内联函数:在第三点中,函数的定义也需要满足 单定义规则, 但是内联函数不受这项规则约束。

这允许程序员能够将内联函数的定义放在头文件中,这样,包含了头文件的每个文件都有内联函数的定义,但是C++要求同一个函数的所有内联定义都必须相同。

💚💚💚 思考:C++在哪里查找函数
假设 在程序的某个我呢间中调用一个函数,C++将在哪里去寻找该函数的定义了 ?

  1. 如果该文件中的函数原型指出该函数是静态的,则编译器只在该文件中查找函数定义,否则编译器(包括链接程序)将在所以文件中查找,如果找到两个定义,编译器将发出错误。
  2. 如果在程序中没有找打,编译器将在库中搜索,这意味着如果定义了一个与库函数同名函数,编译器将使用程序员定义的版本,而不是库函数。

6. 语言链接性

另一种形式的链接性----称为语言链接性(language linking )也对函数有影响。

  1. 链接程序要求每个不同函数都有不同的符号名,在C语言中,一个名称只对应一个函数,这很容易做到。
  2. 为了满足内部需要,C语言编译器可能将 spiff 这样函数翻译为 _spiff 这种方法被称为 C语言的链接性(C language linkage)
  3. 但是在C++中,同一个名称可能对应对个函数,那么必须将这些函数翻译为不同的符号名称,可能将spiff 转换成 _spiff_i ,这种方法被称为 C++语言的链接性(C++ language linkage).

💚💚 如果要在C 库中查找函数

extern "C" void spiff(int);  // use C protocol for name look-up
extern void spoff(int);  //use C++ protocol for name look-up
extern "C++" void spaff(int);  // use C++protocol for name look-up

第一个原型使用 C语言链接性,后面两个使用 C++语言链接性,第二个原型是通过默认方式支持这一点,第三个显示的指出了这一点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值