【格蕾读C++PrimerPlus】第九章内存模型和名称空间-创新互联

1 单独编译

程序分为三份

创新互联是一家专注于成都网站建设、成都做网站与策划设计,泽普网站建设哪家好?创新互联做网站,专注于网站建设10多年,网设计领域的专业建站公司;建站业务涵盖:泽普等地区。泽普做网站价格咨询:028-86922220
  1. 头文件:结构声明和使用结构的函数原型
  2. 源代码文件:包含与结构相关的函数的代码
  3. 源代码文件:包含调用与结构相关的函数的代码

不要#include源代码文件,否则会多重声明

1.1 头文件 内容

不要把函数定义或者变量声明放到头文件中

头文件中通常包含:

  1. 函数原型 prototypes
  2. 使用#define或者const定义的符号常量
  3. 结构声明
  4. 类声明 struct templates
  5. 模板声明
  6. 内联函数

结构声明,类声明,模板声明可以放在头文件中,因为它们不创建变量,

而是指示编译器生成和创建。

const数据和内联函数有特殊的链接属性,所以可以放在头文件中。

可以将成员放在名称空间中

引用方式

尖括号:编译器在存储标准头文件的主机系统的文件系统中查找

双引号:首先查找当前的工作目录和源代码目录,如果没找到,在标准位置找

同一个文件只能包含同一个头文件一次,否则编译错误

#ifndef COORDIN_H_
#define COORDIN_H_
//头文件内容
#endif
1.2 编译流程

以UNIX执行C为例

img

  1. 预处理器合并源码和包含的头文件
  2. 编译器创建合并代码的目标代码文件.o
  3. 汇编
  4. 链接程序将目标代码文件,库代码,启动代码合并,生成可执行文件.out
1.3 名称修饰

(71条消息) C+±-名字修饰_Emma-Zhang的博客-博客_c++名称修饰

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

名字修饰(Name Mangling)是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。

两个编译器对同一个函数可能生成不同的修饰名称,所以不同的名称会使链接器无法将编译器生成的函数调用与另一个编译器生成的函数定义匹配。

因此在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。

2 存储持续性,作用域,链接性

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

2.1 存储持续性

数据保存在内存中的时间

  1. 自动存储持续性:两种:函数定义中声明的变量(包括函数参数)。
    1. 创建时机:开始执行所属函数或者代码块时
    2. 释放:执行完函数或者代码块
  2. 静态存储持续性:三种:函数定义外定义的变量和static定义的变量。
    1. 在程序整个运行过程中都存在
  3. 线程存储持续性(C++11):用关键词thread_local声明的变量。
    1. 生命周期与所属线程一样长
  4. 动态存储持续性:new分配的内存,存放在自由存储区或堆中。
    1. new分配后到delete将其释放或者程序结束
2.2 作用域

作用域(scope)描述名称在文件的多大范围可见。

局部

作用域为局部只在定义的代码块可用。

全局

作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾都可用。

变量作用域
  1. 自动变量的作用域为局部
  2. 静态变量的作用域是全局还是局部取决于它是如何被定义的
  3. 函数原型作用域中使用的名称只在包含参数列表的括号内可用。
  4. 类中声明的成员的作用域为整个类
  5. 名称空间中声明的变量的作用域为整个名称空间(名称空间已经引入到了C++语言中,因此全局作用域是名称空间作用域的特例)
函数作用域

作用域可以是类或者名称空间(包括全局),但不能是局部的,不然只对自己可见。

2.3 链接性

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

链接性为外部的名称可以在文件间共享

链接性为内部的名称只能由一个文件中的函数共享

自动变量的名称没有链接性,因为它们不能共享。

3 变量的存储方式

这里介绍引入名称空间之前的情况

3.1 自动存储持续性

函数中声明的函数参数和变量:

存储持续性:自动

作用域:局部,从声明位置到代码块结束

链接性:无

分配内存的时机:执行到该代码块

同名自动变量

当两个同名的变量一个位于外部,一个位于内部时,内部的将被解释为局部代码块变量,为新定义。新定义隐藏了以前的定义,内部代码块中新定义可见,旧定义不可见。

auto关键字

C++11开始,auto用于自动类型推断,但是在C中或者C++11之前,auto用于显式地指出变量为自动存储。

register关键字

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

在C++11以前,register表明变量用的很多,编译器可以对其做特殊处理。

C++11后只限于指出变量是自动的

保留关键字的重要原因是避免使用了该关键字的现有代码非法

3.2 静态持续变量

静态变量的数目在程序运行期间不变,所以不需要用栈管理,只需要固定内存分配,在程序执行期间一直存在。

如果没有初始化,编译器将静态变量设为0,称为零初始化的(zero-initialized)

静态存储持续性变量有三种链接性:

  1. 外部链接性:
    1. 必须在代码块的外面声明
    2. 可以在其他文件中访问
  2. 内部链接性:
    1. 必须在代码块的外面声明,必须使用static限定符
    2. 只能在当前文件中访问
  3. 无链接性:
    1. 必须在代码块中声明,并使用static限定符
    2. 只能在当前函数或代码块中访问
int global=1000;		//外部链接性静态变量
static int one_file=50;	//内部链接性静态变量
int main()
{...}
void func1()
{
	static int count =0;//无链接性静态变量
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuPN0hEO-1672942524360)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221230205803189.png)]

关键字重载

代码块内static指存储持续性,代码块外部static表示内部链接性,这种关键词含义取决于上下文的现象叫关键词重载

静态变量初始化

静态变量都必须先零初始化,如果显式初始化了,那就有两种初始化方式

  1. 常量表达式初始化(与零初始化统称静态初始化)
  2. 动态初始化

要调用函数,必须等到函数被链接且程序执行时,是动态初始化。

但是常量初始化可以使用sizeof()和C++11关键字constexpr,这增加了创建常量表达式的方式。

3.3 外部变量

外部链接性的静态持续性的变量

链接性为外部的变量称为**(常规)外部变量**,又称为全局变量(相对于局部的自动变量),每个使用外部变量的文件必须声明它。

单定义规则

C++有单定义规则:(one definition rule,ODR)变量只能有一次定义。

C++提供两种变量声明:

  1. 定义声明/定义:给变量分配存储空间
  2. 引用声明/声明:不给变量分配存储空间,引用已有的变量,使用extern,且不能进行初始化

要在多个文件使用外部变量,需要在其他文件用extern声明。

定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。

作用域解析运算符

C++提供作用域解析运算符::,放在变量名前,表示使用变量的全局版本

3.4 静态外部变量

指内部链接性的静态持续性的变量

static限定符用于作用域为整个文件的变量,该变量的链接性变为内部,称为静态外部变量

如果在文件中定义了一个静态外部变量,与另一个文件的常规外部变量重名,则该文件中隐藏常规外部变量

用处:在同一个文件中的多个函数间共享数据

3.5 无链接的静态变量

指无链接性的静态持续性的变量

函数内的静态变量只在程序开始运行时被设置为0,当再次调用函数时不会再次初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TcGnLnFB-1672942524361)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221231162728980.png)]

int a=1000;		//外部链接性静态变量
static int b=50;	//内部链接性静态变量
extern int c=20;
int main()
{...}
void func1()
{
	static int g =0;//无链接性静态变量
}
4 存储说明符和cv-限定符

存储说明符(storage class specifier)有

  1. auto
  2. register
  3. static
  4. extern
  5. thread_local
  6. mutable

cv-限定符(cv-qualifier)(cv取两个单词的首字母)有

  1. const
  2. volatile
static关键字

作用域为整个文件时,表示内部链接性;局部声明中,表示存储持续性。

  1. 修饰局部变量时,使得该变量在静态存储区分配内存;只能在首次函数调用中进行首次初始化,之后的函数调用不再进行初始化;其生命周期与程序相同,但其作用域为局部作用域,并不能一直被访问;
  2. 修饰全局变量时,使得该变量在静态存储区分配内存;在声明该变量的整个文件中都是可见的,而在文件外是不可见的;
  3. 修饰函数时,在声明该函数的整个文件中都是可见的,而在文件外是不可见的,从而可以在多人协作时避免同名的函数冲突;
  4. 修饰成员变量时,所有的对象都只维持一份拷贝,可以实现不同对象间的数据共享;不需要实例化对象即可访问;不能在类内部初始化,一般在类外部初始化,并且初始化时不加static;
  5. 修饰成员函数时,该函数不接受this指针,只能访问类的静态成员;不需要实例化对象即可访问。

C++中static作用和使用方法

extern关键字

引用声明,声明引用在其他地方被定义的变量,见”外部变量“。

thread_local关键字

同一个声明中不能使用多个说明符,除了thread_local,可以和staticextern结合使用

指出变量的持续性与其所属线程的持续性相同,thread_local之于线程,犹如常规静态变量之于整个程序。

const关键字

内存被初始化后,程序不能对它进行修改。

const char * const months[12]=
{
  "January","February",···  
};

第一个const防止字符串被修改,第二个const确保数组每个指针始终指向它最初指向的字符串。

const全局变量

C++中,const全局变量的链接性为内部的,如同使用了static。这样每个文件都有头文件中定义的const变量但是不会因为单定义规则而冲突。

如果希望定义在函数外部的const变量的链接性为外部的,需要用extern关键字来覆盖。而且必须在所有使用变量的文件中用extern声明。

不管如何,只有一个文件能初始化。

//使用const
//1.cpp
extern const int states=50;
//2.cpp
extern const int states;

//不使用const
//1.cpp
int states=50;
//2.cpp
extern int states;
代码块中的const

作用域为代码块

volatile关键字

volatile:不稳定的

关键词volatile表明:即使程序代码没有对内存单元进行修改,值也可能变化。

使用场景:

  1. 指针指向硬件,取的值为串行端口的信息,硬件可能对其进行修改
  2. 两个程序可能相互影响共享数据
  3. 防止编译器错误优化:如果程序短期多次使用某个变量,编译器可能不会查找两次,而是把值存在寄存器,加上volatile则编译器不会进行这种优化
mutable关键字

mutable:会变的,可变的

关键词mutable指出:即使结构/类为const,某个成员也可以被修改

struct data
{
    char name[30];
    mutable int accesses;
};
const data a ={"hi",0};
strcpy(a.name,"hello");	//不允许
a.accesses++;			//允许
5 其他链接性 5.1 函数的链接性 存储持续性

C++和C都不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,在程序执行期间一直存在。

默认链接性
  1. 默认情况下,函数链接性为外部,可以在文件间共享
  2. 可选:在函数原型中用extern,表示函数的定义在另一个文件
  3. 要让程序在另一个文件查找函数,另一个文件必须作为程序组成部分被编译,或者是链接程序搜索的库文件
静态链接性
  1. static可以将函数的链接性设为内部,必须在原型和函数定义中使用该关键词
  2. 在定义静态函数的文件中,静态函数将覆盖外部同名定义
  3. 使用函数的文件都应该包含函数原型,只有一个能包含函数定义,除了内联函数。所以内联函数的定义能放在头文件。

如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数,但是C++保留了标准库函数的名称。

5.2 语言链接性

C语言中一个名称只对应一个函数,C++同一个名称可能对应多个函数,必须翻译为不同的符号名称,因此通过C++语言链接来实现。

C++语言链接:为函数名称翻译成不同的符号名称,执行名称矫正或者名称修饰。

涉及链接程序对函数的处理。见书262

6 动态内存

动态内存由new和delete控制,而不是由作用域和链接性规则控制。可以在一个函数中分配,另一个函数中释放。

存储方案不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。

float * p_fees =new float [20];

由new分配的动态内存会一直保留,但是声明的语句块执行完后,指针将消失,所以必须在代码结束后返回地址,不然无法使用该内存。

6.1 分配内存
int *pi=new int (6);
double *pd=new double (99.99);

//列表初始化
struct where {double x;double y;double z;};
where *one =new where {2.5,5.3,7.2}//C++11 对结构 列表初始化

int * ar =new int [4] {2,4,6,7};//C++11 对数组 列表初始化
double *pd =new double {99.99};//C++11 对单值变量 列表初始化
分配函数 new()

void * operator new(std::size_t);

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

使用运算符重载。size_t是个typedef,对应合适的整型

int * pi=new int;被转化为int * pi=new(sizeof(int))

分配函数和释放函数是可替换的,可以为new和delete提供替换函数

分配失败

最初返回空指针,现在引发异常std::bad_alloc

6.2 内存释放

程序结束后,new分配的内存通常将被释放,但请求大型内存块将导致代码块在程序结束不会被自动释放

这时需要delete

释放函数delete()

void operator delete(void *);

void operator delete[](void *);

6.3 定位new运算符

具体参考书P264

new运算符处理在堆中找内存块还有一种变体,让程序员可以指定要使用的位置,这样可以处理通过特定地址进行访问的硬件或者在特定位置创建对象。

与常规new的区别

常规new调用一个接受一个参数的new()函数,而定位new调用的接受两个参数

头函数:#include

使用定位new,变量后面可以有方括号也可以没有

可以用于结构和对象等

用法

可以用静态数组为定位new提供内存空间

char buffer[500];
int main(){
    int *p;
    p=new (buffer) int[20];
}
   
工作原理

默认定位new函数:返回传递给定位new运算符的地址,并强制转化为void *,这样可以赋给任何指针类型

C++允许重载定位new函数,但定位new是不可替换的

7 名称空间

为了防止两个库的名称冲突,需要用名称空间控制名称的作用域

7.1 传统的名称空间 声明区域|潜在作用域|作用域

声明区域:可以在其中进行声明的区域

潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾,潜在作用域比声明区域小

作用域:变量对程序可见的范围被称为作用域

7.2 名称空间

命名的名称空间

特征
  1. 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中
  2. 名称空间中,声明的名称的链接性默认为外部
  3. 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突
  4. 名称空间是开放的,可以把名称加入到已有的名称空间中,可以将函数原型和定义分在两个名称空间中写

用作用域解析运算符来使用名称空间来限定名称,未被修饰的叫未限定的名称,包含名称空间的名称叫限定的名称

7.3 using声明

using声明由被限定的名称和前面的关键字using组成

using声明会把特定的名称添加到所属的声明区域内

在函数中添加到局部声明区域,覆盖同名的全局变量;在函数外添加到全局名称空间中

using声明对于函数如果只给出了

namespace Jill
{
    double fetch;
}
char fetch;
int main()
{
    using Jill::fetch;
    //double fetch;		//错的,已经定义了fetch
    cin>>fetch;			//读取Jill::fetch
    cin>>::fetch;		//读取全局的fetch
}
7.4 using 编译指令

using声明使得一个名称可用,而using编译指令使得所有的名称都可用

using编译指令由名称空间名和using namespace组成

全局声明区域内使用using namespace,使得名称空间的全部名称全局可用

函数中使用using编译指令,则函数中可用

函数重载与using

C++using声明和using指示_莲娃的博客-博客_using

两者比较

同:using编译指令和using声明存在可能的二义性,比如两个名称空间都定义了同样的名称,这时候编译器不允许同时使用这两个using声明


异:using声明与声明类似,如果函数中已经声明,则不能用其导入相同的名称

using编译指令类似于作用域解析运算符,让函数内部将其视为函数之外声明的。即如果已经有局部声明,则名称空间名被隐藏,但是仍然可以通过::变量名取得全局的对应变量,名称空间::变量名取得名称空间的对应变量

一般认为using声明更加安全,因为编译指令如果重名,编译器不会警告,而且使用名称空间的变量也没有明确特征和说明

using namespace std;	//减少这么使用

int x;					//用法1
std::cin>>x;

using std::cin;			//用法2
int x;
cin>>x;
名称空间的嵌套

对于:

namespace elements
{
	namespace fire
	{
		int flame;
	}
}

使用:using namespace elements::fire

再例如:

namespace myth
{
	using Jill::fetch;	//Jill名称空间内的fetch变量
	using namespace elements;
}

直接使用:myth::fetch访问即可

using编译指令可传递,使用using namespace myth;相当于同时还使用了using namespace elements;

名称空间别名
namespace m=myth;
namespace MEF=myth::elements::fire;
using MEF::flame;
未命名的存储空间

相等于链接性为内部的静态变量

namespace
{
	int counts
}
//等于
static int counts2;

int main()
{...}
8 C++的内存分区

编译器使用前三块独立内存,程序在此基础上还需要代码区:

  1. 静态变量:全局区
  2. 自动变量:栈区
  3. 动态存储:堆区
  4. 代码区:用来存放函数体的二进制代码

数据区包括:堆,栈,静态存储区。
静态存储区包括:常量区(静态常量区),全局区(全局变量区)和静态变量区(静态区)。
常量区包括:字符串常量区和常变量区。
代码区:存放程序编译后的二进制代码,不可寻址区。

在这里插入图片描述

9 小结

对于大型编程项目的管理:

  1. 尽量使用在已命名的名称空间中声明的变量,而不是使用外部/静态全局变量
  2. 类库和函数库都放在名称空间中
  3. 少用using编译指令,并且不在头文件中用
  4. 选作用域解析运算符和using声明
  5. 对于using声明,将定义域尽量设置为局部而不是全局
  6. 一句话:能用局部using声明就别用别的

当然,一个文件的程序不受此限制

10 留给读者的问题
  1. p273 为什么使用using声明导入了debts对pers的编译指令?

感谢看到这里,写到凌晨两点,麻烦点下赞谢谢

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章标题:【格蕾读C++PrimerPlus】第九章内存模型和名称空间-创新互联
本文路径:http://ybzwz.com/article/jgdpo.html