C++ primer plus 学习笔记-第九章:内存模型和名称空间

第九章:内存模型和名称空间

前言:本章重点阐述三个问题:

  • 变量作用范围
  • 内存管理的新操作
  • 名称空间

1.变量作用范围

当编写更加庞大的程序时,只使用一个源文件是不够的;为了使程序结构更加清晰,可以使用多个文件来包括源代码;
源文件可以组织为:

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

不要将函数定义和变量声明放在头文件中!这会引来同一个函数被反复包含的问题!

头文件通常包含:

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

关于#include指令包含文件时: 使用尖括号时,程序将首先在标准库目录中查找文件名,使用双引号时编译器将首先在当前目录查找源文件,匹配失败时再在标准库目录中查找;因此在包含自己定义的头文件时,应当使用双引号;

为了防止重复包含某个头文件可以这样做:

#ifndef 头文件名称
//判断当头文件名称没有被定义的时候执行下面的预编译指令
#define 头文件名称
//光是在#define指令后加上名称就足以使名称被定义了
#include"头文件名"
#endif
//#ifndef到此为止

数据有四种存储持续性:

  • 自动存储持续性:在程序开始执行其所属的函数或代码时被创建,在执行完函数或代码块时内存被释放
  • 静态存储持续性:在函数外定义的变量和使用关键字static定义的变量的存储行都是静态的,在程序执行的整个生命周期中都存在
  • 线程存储持续性:变量使用thread_local,本书不讨论
  • 动态存储持续性:使用new和delete分配销毁的内存,程序运行期间完全由程序员支配

作用域描述了名称在文件多大范围可见,例如: 函数中定义的变量只能在该函数中使用;文件中定义于函数定义之前的变量可以在所有函数中使用;

链接性描述了名称如何在不同变量间共享,例如: 链接性为外部的名称可以在文件间共享;链接性为内部的名称只能由一个文件中的函数共享;自动变量没有链接性不能共享;

作用域为局部的变量只在定义它的代码块中可用;作用域为全局的变量在定义位置到文件结尾之间可用;函数原型作用域中使用的名称只在包含参数列表的括号中可用;在类中声明的成员的作用域为整个类;在名称空间中声明的变量的作用域为整个名称空间;

默认情况下,在函数中声明的函数参数和变量的存储持续性为自动;另外,如果一个代码块中声明的变量和嵌套在其中的代码块有相同名称的变量,则对于内部代码块来说外部的变量名暂时变得不可见,离开内部代码块后外层变量名重新可见;

在C++11中关键字auto的作用是自动推断变量的类型,在以前的版本则是强调变量存储类型为自动;

自动变量的自动属性是使用“栈”实现的,遵循LIFO(后进先出)原则;程序使用两个指针来跟踪栈,一个指向栈顶,一个指向栈底;

要创建链接性为外部的静态持续变量,必须在代码块外面声明它;要创建链接性为内部的静态变量,必须在代码块外面创建它并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内部声明它并使用static限定符;

一个变量在多个文件中使用时,在创建它的文件中使用extern修饰并定义,在其它文件中只需要使用extern声明即可直接使用;

(创建变量的文件中,extern可以省略)

在嵌套的语句块中,作用域解析运算符::前置修饰变量名能使用外部代码块的同名变量

如果不想使用另一个文件中链接属性为外部的变量,仅仅省略extern是不够的,还必须将此文件中的同名变量用static关键字限制为内部链接属性;

C++中的名称修饰符是:auto,register,static,extern,thread_local,const,mutable,volatile;

其中:

  • mutable:表示该变量虽然从属于一个被const修饰的结构体中,它仍然是可以被修改的
  • volatile:表示虽然程序员没有进行修改变量的操作,但变量的值仍可能发生变化,并告诉编译器不要对这个属性进行优化

链接属性为外部的变量,被const修饰后默认链接属性变成内部,再使用extern修饰就可以变成外部链接属性;在其它使用该常量的文件中使用extern声明;

在函数或代码块中定义常量时,该常量只有在运行该代码块或者函数时才能使用;

函数的链接属性默认为外部,存储持续性为静态,但使用static修饰声明和定义的函数,其链接属性是内部的;

头文件中一般只包含函数声明而不包括定义,但内联函数的定义也可以包含在头文件中;

C++查找函数的规则: 如果该函数在此文件中是被static声明的,那就只在该文件中查找函数定义,如果没有static关键字修饰则在多个文件中查找定义,找到两个及以上定义时报错,没找到定义时在标准库函数中查找;

也就是说:当用户定义了一个和标准库函数中的某个函数同名的函数时,编译器将使用用户定义的版本;

C++会对函数名私下进行修饰,要使用其它语言标准修饰过的函数,要这么做:

extern "c" void spiff(int);
//将使用c语言标准修饰的函数

extern void spoff(int);
//将使用c++标准修饰的函数

extern "c++" void spaff(int);
//将使用c++标准修饰的函数

2.内存管理的新操作

编译器使用三块内存来放置变量:静态变量,自动变量,动态存储;

在使用new来申请内存空间时,可以对变量进行初始化;方法是:

int * p = new int(6);
//将变量初始化为6

int * p = new int{6};
//将变量初始化为什么6,但使用大括号
//大括号中可以包括列表;为空时初始化为为;

当new申请内存空间失败时,C++11之前会返回一个空指针,现在和以后会引发异常std::bad_alloc;

内存控制函数new和delete虽然是内置函数,但它们实际上是可以由用户自己定义的,当然除非你真的很厉害否则不建议这么做;C++提供的原型已经很好了;

new还有从指定的位置中申请空间的功能,是这么用的:

#include <new>
struct chaff
{
	char dross[20];
	int slag;
}
char buffer1[50];
char buffer2[500];
int main()
{
	chaff *p1, *p2;
	int *p3. *p4;
	
	//下面的代码是直接从堆中申请空间的
	p1 = new chaff;
	p3 = new int[20];
	
	//下面的代码是从上文声明的buffer1和buffer2中分配空间的
	p2 = new (buffer1) chaff;
	p4 = new (buffer2) int[20];
}

使用new定位运算符分配内存的要点是:包含头文件;指明使用的空间;

注意:定位new运算符不跟踪哪些空间被使用过,也不查找未使用过的内存块,都需要程序员自己进行手动跟踪;

定位new分配的内存是不能被常规delete删除的,它只能管理堆中的内容;

3.名称空间

名称空间存在的意义是方便管理更庞大复杂的名称作用域;

声明区域: 可以在其中进行声明的区域; 潜在作用域: 变量的潜在作用域从声明点开始,到声明区域结尾结束;

创建名称空间的方式:

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

namespace Jill{
	double bucket(double n){...}
	double fetch;
	int pal;
	struct Hill{...};
}

值得注意的是,虽然两个名称空间中出现过同名的变量或函数,但以内它们是不同的名称空间,因此不发生冲突;

全局名称空间: 对应文件级声明区域,前面说的全局变量实际上是位于全局名称空间中;

名称空间是开放的,可以添加新的成员:

//名称空间Jill是之前已经存在的
namespace Jill{
	char * goose(const char *);
}

要访问名称空间中的名称,有三种办法:

  • 使用作用域解析运算符::
  • 使用using namespace使整个名称空间可用
  • 使用using使名称空间中的某个名称可用

名称空间可以嵌套:

namespace elements
{
	namespace fire
	{
		int flame;
		...
	}
	float water;
}
//访问内层元素时:elements::fire::flame

using操作是可传递的,以下操作只会针对最后一个被操作对象:

namespace MEF = myth::elements::fire;
//为名称空间创建了别名

using MEF::flame;

注意: 名称空间可以没有名称,作用域默认从定义位置开始到文件尾结束;

在大型项目中名称空间是非常有用的,你应当在大型项目中遵循这些规范:

  • 使用在已命名的名称空间中声明的变量,而不是外部全局变量
  • 使用在已命名的名称空间中声明的变量,而不是使用静态局部变量
  • 如果开发了一个函数库或类库,将其放在一个名称空间中;同时C++也建议将所有属于标准库的函数放在标准空间中,而C++中C的函数没有在名称空间中
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计
  • 不要在头文件中使用using编译指令,这会掩盖要让哪些名称可用,另外包含头文件的顺序可能影响程序的行为;如果非要使用编译指令using,应当将其放在所有预处理器编译指令#include之后
  • 导入名称时,首选使用作用域解析运算符或using声明的方法
  • 对于using声明,首选将其作用域设置为局部而不是全局

以上为本文所有内容。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值