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

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


以下内容为本人在阅读 《C++ primer 》第六版的第九章时的,整理的一些重点,只为自己巩固所学,也加深对C++的理解。重点的讲解了静态变量,extern关键字,命名空间的用法。


在这里插入图片描述

1. 编译

C++鼓励程序员将组件函数放在单独的文件中,单独的编译这些文件,然后再将这些文件链接成可执行的程序。通常的编译器是既可以编译程序,也可以管理链接器的。

为避免在程序开发中把同一程序放入多个文件的问题,引入了**#include**来处理这种情况。因此程序分为3个部分

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

注意,不要将函数定义或变量声明放在头文件中,这种做法对于简单的情况可行,但通常会引来麻烦。例如,可能引起重复定义而报错。以下是头文件常包含的内容:

  • 函数原型
  • #define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模版声明
  • inline函数

“ ” 还是 < >包含头文件的问题

  • ‘’ c++ 编译器将在存储头文件的主题系统中查找
  • < >首先在当前的工作目录下或源代码下查找,如果没有找到,就在标准位置查找

还要注意一点不要#include 源代码文件

牢记规则:在同一个文件中只能将同一个头文件包含一次。很可能不知情的时候多次包含。解决的办法是使用编译器预处理指令**#ifdefine**

最后关于编译的一个点就是确保使用同一个编译器来编译自己的程序,否则很容易链接失败。

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

在这里插入图片描述

  • 作用域:名称在文件内多大的范围内可见
  • 链接性:名称如何在不同的单元内共享

auto 在C++11中的用法上自动推断

	froob(int n)
	{
		auto float ford; // ford has automatix storage
		...
	}
自动变量和栈

深入的了解下典型的C+编译器中如何实现自动变量的原理。常用方法:留出一段内存,并视为栈,以管理变量的增减,新增的变量放在原数据之上,程序使用完后,释放这段内存。通过两个指针来控制,一个指针指向栈底,另一个指向栈顶。

寄存器变量

关键字register由c引入,建议编译器使用CPU寄存器来存储自动变量:

register int count_fast; // regiser for a register variable

目的是提高变量的访问速度。C++11中这种提示作用只能用于自动变量。书中关于这里的解释不是很明白。

静态持续变量

静态变量提供了三种链接性

  • 外部链接性:可在其他文件中访问
  • 内部链接性:只能在当前文件中访问
  • 无链接性:只能在当前函数或者代码块中访问

共同点: 在整个代码期间存在

程序在将分配固定的内存块来存储所有的静态变量,这些变量在整个程序的运行期间一直存在。如果没有显示的初始化静态变量,编译器将把它设置为0.默认情况下,静态数组和结构的每个元素或成员的所有味都设置为0

下面举例三种静态变量

	int global = 1000; //static duration, external linkage
	static int one_file = 50; // static duration, internal linkage
	
	int main()
	{
		...
	}
	
	
	void dunct(int n)
	{
		static int count = 0; //static duration, no linkage
		int llama = 0;
	}

上述的代码中, 所有的静态持续变量**(global, one_file, count)**都在整个程序的执行期间存在,count为作用域为局部,无链接性.与llam相同,两者的区别是,count在即使函数dunct()没有执行的时候,就存在内存中,one_file在这个文件的函数中都可使用,global可以在程序的其他文件中使用。

再次总结下,static在局部声明时,表明的是无链接性的静态变量,表示的是存储持续性。在代码块外声明的时候,static表示的是内部链接性,变量已经是静态持续性了,有人称这种用法为关键字重载。

引入命名空间前对5种不同的存储特性总结如下:

存储描述持续性作用域链接性如何声明
自动自动代码块代码块内
寄存器自动代码块代码块内,关键字register
静态,无链接性静态代码块代码块内,关键字static
静态, 外部链接性静态文件外部不在任何函数内
静态, 内部链接性静态文件内部不在任何函数内, 关键字static
静态变量初始化

除默认的零初始化外,静态变量可以进行常量表达式初始化动态初始化,在C++中,指针变量初始化为响应的内部实现,结构成员是零初始化。

零初始化和常量初始化为静态初始化,在编译器处理文件时进行,动态初始化为编译后初始化。
真实的处理哦过程如下,

  • 首先,所有静态变量被零初始化,不管程序员是否现实的初始化

  • 如果使用常量表达式初始化,且编译器仅根据文件内容就可计算出表达式,将执行常量表达式初始化

  • 最后,如果没有足够的信息,变量将动态初始化。实例如下

      #include <cmath>
      int x;								// zero-initialization
      int y = 5;							//constant-expression initialization
      long z = 13 * 13;					//constant-expression initialization
      const double pi = 4 * atan(1.0)	    //dynamic initialization
    

2.4 静态持续性,外部链接性

链接性为外部的变量称为外部变量,存储持续性为静态,作用域为整个文件,外部变量在定义后的任何函数中都可以使用。

这里需要说明一下的是单定义原则(One Defination Rule),即变量只能定义一次。为满足这种需求,C++提供两种声明方式

  • 定义声明(defining declaratuin)简称定义(define), 为变量分配存储空间
  • 引用声明(reference declararion)简称声明(declaration),因为它引用已有的变量

引用声明中使用关键字extern,且不进行初始化,否则声明为定义,导致分配内存空间。

double up; //definition, uo is 0
extern int blem; //blem is define elsewhere
extern char gr = 'z'; // definition because initialized

如果在多个文件中使用外部变量,只需要在一个文件中包含定义该变量的定义,但在使用该变量的其他所有文件中,都必须使用extern声明它。

	//file01.cpp
	extern int cats = 20; //definition because of initialization
	int dogs = 22;	// also a definition
	int fleas; // also a definition
	
	
	//file02.cpp
	//use cats and dogs from file01.cpp
	extern int cats; //not definitions because they use extern
	extern int dogs; // and have no initialization
	
	//file03.cpp
	//use cats and dogs form file01.cpp
	extern int cats;
	extern int dogs;
	extern int fleas;

在文件file01.cpp中extern关键字不是必不可少的,即使省略,效果也相同。

还有一点 关于单定义原则需要说明的是,单定义原则上并不是意味不能有多个变量的名称相同。在不同的函数中声明的同名变量彼此独立,分别有自己的地址。局部变量可能会隐藏同名的全局变量

对于使用同名函数的问题,c++引入了:: 作用域解析运算符,来特别指明所调用的变量所在的命名空间,这种方法更加安全,在命令空间的部分会更详细的说明。

2.5 静态持续性,内部链接性

当在不同的文件中使用相同的名称来表示变量时,可以使用如下的方式

	//file1
	int errors = 20; //external declration
	...
	
	---------------------------------------
	//file2
	static int errors = 5; //known to file2 only
	
	void froobish()
	{
		cout << erros; // uses errors in file2
	}

以上没有违反单定义原则,static指明该处定义的变量只在文件内部使用。

外部变量的使用目的是在多个文件的你不同部分中共享数据,链接性为内部的静态变量在同一文件中多个函数中共享数据。

2.6 静态存储持续性,无链接性

无链接性的局部变量,该变量只在代码块内使用,但在该代码块不处于活动状态的时候任然存在,两次函数调用之间,静态局部变量的值不变。

2.7 说明符和限定符

存储说明符分类如下:

  • auto :c++11自动推断
  • register:寄存器变量
  • static
  • extern
  • thread_local :同一个生命中不能使用多个说明符,thread_local除外,可以和extern或static结合
  • mutable: 与const对应
  1. cv-限定符

    • const :内存初始化后,就无法改变
    • volatile: 用于程序代码没有对内存修改时,内存也可能改变,
  2. mutable

    指出const结构的某个成员也可以改变

     struct data
     {
     	char name[30[;
     	mutable int accesses;
     	...
     }
    
  3. const

c++ 中const限定符对默认的 存储类型有影响。默认情况下,全局变量的链接性为外部的,但是全局的const定义同static说明符一样

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

需要为某个文件使用一组定义,然而在其他的文件中使用另一组定义的时候,由于const是外部定义的,内部链接属性的变量,可以在所有的文件中使用相同的声明,内部链接性意味着每个文件中都有一组属于自己的常量,而不是共享。

当在需要某个常量的链接属性为外部时,需要使用extern关键字来覆盖默认的内部链接性

	extern const int status = 10; //definition with external linkage

在所有需要使用该常量的文件中使用extern来声明它。与常规的变量不同,定义常规变量时,不必使用extern关键字,在此时必修使用const.并且,由于单个const在多个文件中共享,只有一个文件对其初始化。

2.8 函数和链接性

对比变量存在链接性,函数也有链接性。

C++不允许在一个函数定义中定义另外一个函数。因此所有的函数的存储持续性为自动静态的,**在整个程序的执行期间都存在。默认的情况下,函数的链接性为外部的,可以在文件之间共享。实际上,可以使用函数原型中的关键字extern来指出另一个文件中定义的函数。要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分编译或者是由链接程序搜索为的库文件。还可以使用static关键字将函数的链接性设定为内部的,只在单个文件中使用。必须同时在函数原型和函数定义中使用关键字。

	static int private(double x);
	...
	
	static int private(double x
	{
	...
	}	

定义静态函数的文件中,静态函数将覆盖外部定义,即使外部定义了同名的函数,该文件将使用此静态函数。

单定义规则也适用于非inline函数。对于每个非inline函数,程序中只包含一个定义。对于链接性为外部的函数来说,这意味着在多个文件程序中,只能定义一次,但使用该函数的每个文件都应包含其函数原型。

inline函数不受这项规则的约束,但是C++要求同一个函数的所有inline定义必须相同。

在程序运行的过程中,C++寻找函数的定义,遵循以下的规则:

  • 如果该文件的函数类型指出是静态的,编译器只在该文件内查找
  • 未指明函数为静态,编译器在所有的文件中查找。如果找到了两个定义将报错
  • 如果程序文件中没有找到,编译器将在库中搜索。如果定义一个与库函数同名的函数,编译器将使用程序员定义的版本。
2.9 语言链接性

对于在c++和c相互调用的问题。由于c中,一个名称对应一个函数,c++中,同一个名称对应多个函数。相互调用的适应会出现问题。解决方案是使用关键字extern.

	extern "c" void spiff(int); // use c protocal for name look-up
	extern void spoff(int); // use c++ protocol foir name look-up
	extern "c++" void spaff(int) // use c++ protocal for name look-up
2.10 存储分配和动态分配

之前说的5种分配方式不适用于C++运算符new, (或C函数malloc()分配的内存),动态分配由new, delete控制,不受作用域和链接规则的控制。可以在一个函数中分配内存,在另外一个函数中释放内存。通常情况下,编译器中有三块独立的内存,静态变量(可再次细分),自动变量和动态变量。

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

	float *p_fees = new float [20];

由new分配80个字节(假设一个float 4个字节)的内存一直存在内存中,直到使用delete运算将其释放。但当包含该声明的语句执行完毕后,p_fees指针将消失。如果希望另一个函数中使用这80个字节的内容,需要把地址传递或返回给该函数。如果将p_fees的链接性声明为外部的,该文件中声明的所有函数都可以使用。

1.使用new初始化
初始化动态分配的变量.

	//初始化内置标量类型的方式:
	int pi = new int (6); // *pi set to 6
	double * pd = new double (99.99); // *pd set to 99.99
	
	//初始化常规结构体
	struck where
	{
		double x;
		double y;
		double z;
	};
	
	where * one = new where {2.5, 5.3, 7.2};
	int * ar = new int [4] {2, 4, 6, 7};

2.new失败
new找不到请求的内存量,c++之前的设定是返回空指针,现在的操作是**引发异常 std::bad_alloc.
关于异常的文件会在后面的文章中总结。

3.new: 运算符,函数和替换函数

	//new, new []
	void * operator new(std::size_t); //use by new
	void * operator new [](std::size_t); // use by new[]
	
	//delete , delete []
	void operator delete (void *);
	void operator delete [](void *);

4.定位new运算符
通常new在堆(heap)中找到一个足以满足要求的内存块,还有一种用法**(placement)new元算符**,可以指定要使用的位置。使用placement new需要包含头文件new.这种情况下的new不能用delete.

5.placement new的其他形式
调用一个接受两个参数的new()

	int * pi = new int; // invokes new(sizeof(int))
	int * p2 = new(buffer) int; //invokes new(sizeof(int), buffer)
	int * p3 = new(buffer) int[40]; //incokes new(40*sizeof(int), buffer)

palcement new不可替换,但是可以重载。

3.命名空间

3.1 传统的C++命名空间

需要复习的名称概念。

  • 声明区域(declaration region).声明在函数外的微全局变量,,声明区域为整个文件,声明在函数内的,声明区域为该代码块
  • 潜在作用域(potential scope).从声明点开始,到声明区域结尾。

3.2 新的名称空间特性

C++提供一种新的声明区域来创建命名的名称空间。
名称空间可以是全局的,也可以是在另一个名称空间中,但不能在代码块内,在默认的情况下,在命名空间中声明的名称的链接性是外部的(除非它引用了常量)

除了用户定义的名称空间外,还存在一个全局命名空间(global space),它对应文件层级。前面说的全局变量现在被描述为全局名称空间中。

任何名称空间内的名称都不会和其他名称空间的中名称冲突。名称空间是开放的,即可以把名称加入已有的名称空间中,

	//假设已经定义过Jill
	namespace Jill
	{
	char * goose(const char *);}

对于访问给定名称空间中的名称,最简单的方法是,通过作用域解析运算符::.不带装饰的名称称为未限定的名称,包含命名空间的名称称为限定的名称

  1. using声明和using 编译指令
  • using 声明使特定的标识符可用

  • using 编译使整个命名空间可用

      using Jill::fetch; //a using delcaration
    

using 声明的变量和函数也会覆盖同名的全局变量和函数

	using namespace Jill; // make all names in Jill available

使用上述的两种用法会增加命名冲突的可能性。更推荐的用法是用命名空间的来解析的方式。

	Jill::pal = 3;
	Jack::pal = 4;
  1. using 编译指令和using声明比较
    使用using编译指令,像是大量的使用作用域解析运算
    使用using声明,像声明了相应的名称。如果使用using编译指令将名称空间中的名称导入该声明区域,则局部版本将隐藏名称空间版本

  2. 名称空间的其他特性

     //using 	嵌套使用
     namespace elements
     {
     	namespace fire
     	{
     		int flame;
     		...
     	}
     	float water;
     }
     
     ---------------------------------
     
     //using 编译和声明混合使用
     namespace myth
     {
     	using Jill::fetch;
     	using namespace elemets;
     	using std::coput;
     	using std::cin;
     }
    

    using编译命令有传递性,例如上例中 using namespace myth

     using namespce myth
     using namespace elements
    

    作用相同。

    还有一个特别的用法,可以对名称空间创建别名

  3. 为命名的名称空间

     namespace
     {
     	int ice;
     	int bandcoot;
     }
    

    使用这种定义类似与静态的全局变量

3.3 名称空间的使用建议

  • 使用已命名的名称空间中的变量,而不是使用全局变量
  • 使用已命名的名称空间中的变量,而不是使用静态全局变量
  • 开发的库放入一个命名空间内
  • 仅把使用using作为将旧代码转换的权宜之计
  • 不要在头文件中使用using编译命令
  • 导入名称式,首选作用域解析运算符或using 声明方法
  • 对using声明,首选设置为局部
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值