C++学习之路(八),C++primer plus 第九章 内存模型和名称空间

​​​​在这里插入图片描述

C++primer plus 第九章 内存模型和名称空间

本章内容包括:
● 单独编译。
● 存储持续性、作用域和链接性
● 定位(placement)new 运算符。
● 名称空间

  C++ 为在内存中存储数据方面提供了多种选择。可以选择数据保留在内存中的时间长度(存储持续性)以及程序的哪一部分可以访问数据(作用域和链接)等。可以使用 new 来动态地分配内存,而定位 new 运算符提供了这种技术的一种变种。C++ 名称空间是另一种控制访问权的方式。通常,大型程序都由多个源代码文件组成,这些文件可能共享一些数据。这样的程序涉及到程序文件的单独编译,本章将首先介绍这个主题。

9.1 单独编译

  和 C 语言一样,C++也允许甚至鼓励程序员将组件函数放在独立的文件中,第 1 章介绍过,可以单独编译这些文件,然后将它们链接成可执行的程序。(通常,C++ 编译器既编译程序,也管理链接器。)如果只修改了一个文件,则可以只重新编译该文件,然后将它与共创文件的编译版本链接。这使得大程序的管理更便捷。另外,大多数 C+ 环境都提供了其他工具来帮助管理。例如,UNIX 和 Linux 系统都具有 make 程序,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行 make 时,如果它检测到上次编译后修改了源文件,make 将记住重新构建程序所需的步骤。大多数集成开发环境(包括 Embarcadero C++ Bulider、MIcorsoft VIsual C++、Apple Xcode 和 Freescale CodeWarrior)都在Project 菜单中提供了类似的工具。
  现在看一个简单的示例。我们不是要从中了解编译的细节(这取决于实现),而是要重点介绍更通用的方面,如设计。
  例如,假设程序员决定分解程序清单 7.12 中的程序,将支持函数放在一个独立的文件中。清单 7.12 将直角坐标转换为极坐标,然后显示结果。不能简单地以 main()之后的虚线为界,将文件分为两个。问题在于,main()和其他两个函数使用了同一个结构声明,因此两个文件都应包含该声明。简单地将它们输入进去是自找麻烦。即使复制了结构声明,如果以后要作修改,则必须记住对这两组声明都进行修改。简而言之,将一个程序放在多个文件中将引出新的问题。
  谁希望出现更多的问题呢?C 和 C++ 的开发人员都不希望,因此他们提供了 #include 来处理这种情况。与其将结构声明加入到每一个文件中,不如将其放在头文件中,然后在每一个源代码文件中包含该文件。这样,要修改结构声明时,只需在头文件中做一次改动即可。另外,也可以将函数原型放在头文件中,因此,可以将原来的程序分成三部分。
  ● 头文件: 包含结构声明和使用这些结构的函数和原型。
  ● 源代码文件: 包含与结构有关的函数的代码。
  ● 源代码文件: 包含调用死心塌地结构相关的函数的代码。
  这是一种灰常有用的组织程序的策略。例如,如果编写另一个程序时,也需要使用这些函数,则只需要包含头文件,并针函数文件添加到项目列表或 make 列表中即可。另外,这种组织方式也与 OOP 方法一致。一个文件(头文件)包含了用户定义类型的定义;另一个文件包含操作用户定义类型的函数和代码。这两个文件组成了一个软件包,可用于各种程序中。
  请表将函数定义或变量声明放到头文件中。这样做对于简单的情况可能是可行的,但通常会引来麻烦。例如,如果在头文件中包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该文件,则同一个程序 中将包含同一个函数的两个定义,除非函数是内联的,否则这将出错,下面列出了头文件中常包含的内容。
  ● 函数原型。
  ● 使用 #define 或 const 定义的符号常量。
  ● 结构声明。
  ● 类声明。
  ● 模板声明。
  ● 内联函数。
  将结构声明放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为 const 的数据和内联函数有特殊的链接属性(稍后将介绍),因此可以将其放在头文件中,而不会引起问题。
  程序清单 9.1、程序清单 9.2 和程序清单 9.3是将程序清单 7.12分成几个独立部分得到的结果。注意,在包含头文件时,我们使用 “coordin.h",而不是 <coodin.h>。如果文件名包含在尖括号中,则 C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器),如果没有在那里找到头文件,则将在标准位置查找。因此在包含自己的头文件时,应使用引号而不是尖扣。
  图 9.1 简要地说明了在 UNIX 系统中将该程序组合起来的步骤。注意,只需执行编译命令 CC 即可,其他步骤将自动完成。g++ 和 gpp 命令行编译器以及 Borland C++ 命令行编译器(bcc32.exe)的行为类似。Apple Xcode、Embarcadero C++ Builder(错误,多打了一个 r) 和 Microsoft Visual C++ 基本上执行同样的步骤,但正如第 1 章介绍的,启动这个过程的方式不同 —— 使用能够创建项目并将其与源代码文件关联起来的菜单。注意,只需将源代码文件加入到项目中,而不用加入头文件。这是因为 #include 指令管理头文件。另外,不要使用 #include 开包含源代码文件,这样做将导致多重声明。
  警告:在 IDE 中,不要将头文件加入到项目列表中,也不要在源代码文件中使用 #include 来包含其他源文件。

程序清单 9.1 coordin.h
  —————————————————————————————————————————————————————————

// coordin.h -- structure templates and function prototypes
// structure templates
#ifndef COORDIN_H_	// #ifndef 是"if not defined"的简写,作用是:1、防止头文件的重复包含和编译;2、便于程序的调试和移植;
#define COORDIN_H_

struct ploar
{
	double distance;		// distance from origin
	double angle;			// direction from origin
};

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

// prototypes
ploar rect_to_polar(rect xypos);
void show_polar(ploar dapos);

#endif

——————————————————————————————————————————————————————————
  式
头文件管理
  在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的 C/C++ 技术可以避免多次包含同一个头文件。它是基于预处理器编译指令 #ifndef (既 if not defined )的。下面的代码片段意味着仅当前没有使用预处理器编译指令 #define 定义名称 COORDIN_H_ 时,才处理 #ifndef 和 #endif 之间的语句:

#ifndef COORDIN_H_
...
#endif

  通常,使用 #define 语句来创建符号常量,如下所示:

#define MAXIMUM 4096

  但只要将 #define 用于名称,就足以完成该名称的定义,如下所示:

#define COORDIN_H_

  程序清单 9.1 使用了这种技术是为了将文件内容包含在 #ifndef 中:

#ifndef COORDIN_H_
#define COORDIN_H_
// place include file contents here // 在此处放置包含文件内容
#endif

  编译器首次遇到该文件时,名称 COORDIN_H_没有定义(我们根据 include 文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称)。在这种情况下,编译器将查看 #ifndef 和 #endif 之间的内容(这正是我们希望的),并读取定义 COORDIN_H_ 的一行。如果在同一个文件中遇到其他包含 coordin.h 的代码,编译器将知道 COORDIN_H_ 已经被定义了,从而跳到 #endif 后面的一行上。注意:这种方法并不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。大多数标准 C 和 C++ 头文件都使用这种防护(guarding)方案。否则,可能在一个文件中定义同一个结构两次,这将导致编译错误。

程序清单 9.2 file1.cpp
——————————————————————————————————————————————————————————

// file.cpp -- example of a three- file protram
#include <iostream>
#include "coordin.h"	// structure templates, function protytypes
nsing namespace std;

int main()
{
   
	rect rplace;
	polar pplace;

	cout << "Enter the x and y values: ";
	while (cin >> rplace.x >> rplace.y)		// slick use of cin
	{
   
		pplace = rect_to_polar(rplace);
		show_polar(PPlace);
		cout << "Next two numbers (q to quit): ";
	}

	cout << "Bye!\n";
	return 0;

——————————————————————————————————————————————————————————
程序清单 9.3 file2.cpp

// file2.cpp -- contains functions called in file1.cpp
#include <iostream>
#include <cmath>
#include "coordin.h"	// structure templates, function prototypes

// convert rectangular to polar coordinates
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;		// returns a polar structure
}

// show polar coordinates, converting angle to degrees
void show_polar(polar dapos)
{
   
	using namespace std;
	const double Rad_to_deg = 57.5957751;

	cout << "distance = " << dapos.distance;
	cout << ", angle = " << dapos.angle * Rad_to_deg;
	cout << " degrees\n";
}

——————————————————————————————————————————————————————————
  将这两个源代码和新的头文件一起进行编译和链接,将生成一个可执行程序。下面是该程序的运行情况:在这里插入图片描述
  顺便说一句,虽然我们讨论的是根据文件进行单独编译,但为保持通用性,C++ 标准使用了术语翻译单元(translation unit),而不是文件;文件并不是计算机组织信息时的唯一方式。出于简化的目的,本书使用术语文件,你可将其解释为翻译单元。
加粗样式
  C++ 标准允许每个编译器设计人员以他认为合适的方式实现名称修饰(参见第 8 章的旁注“什么是名称修饰”),因此由不同编译器创建的二进制模块(对象代码文件)很可能无法正确地链接。也就是说,两个编译器将为同一个函数生成不同的修饰名称。名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误。

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

  介绍过多文件程序后,接下来扩展第 4 章对内存方案的讨论,即存储类别如何影响信息在文件间的共享。现在读者阅读第 4 章已经有一段时间了,因此先复习一下有关内存的知识。C++ 使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。
  ● 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们的程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
  ● 静态存储持续性:在函数定义外定义的变量和使用关键字 static 定义的变量的存储性都为静态。它们在程序整个运行过程中都存在。C++ 有 3 种存储持续性为静态的变量。
  ● 线程存储持续性(C++11):当前,多核处理器很常见,这些 CPU 可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字 thread_local 声明的,则其生命周期与所属的线程一线长。本书不探讨并行编程。
  ● 动态存储持续性:用 new 运算符分配的内存将一直存在,直到使用 delete 运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。

  下面介绍其他内容,包括关于各种变量何时在作用域内或可见(可被程序使用)以及链接性的细节。链接性决定了哪些信息可在文件间共享。

9.2.1 作用域和链接

  作用域(scope)描述了名称在文件(翻译单元)的多大范围内可见。例如,函数定义的变量可在该函数中使用,但不能在其他函数中使用;而在文件中的函数定义之前定义了变量则可在所有函数中使用。链接性(linkaget)描述了名称如何在不同单元间共享。链接性为外部的名称可以文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。
  C++ 的变量的作用域有多种。作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起的一系列语句。例如函数体就是代码块,但可以在函数体嵌入其他代码块。作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾之间都可用。自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域(function protytype scope)中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因)。在类中声明的盛况的作用域为整个类(参见第 10 章)。在名称空间中声明的变量的作用域为整个名称空间(由于名称空间已经引入到 C++ 语言中,因此全局作用域是名称空间作用域的特例)。
  C++ 函数的作用域可以是整个类或整个名称空间(包括全局听),但不能是局部的(因为不能在内码块内定义函数,如果函数的作用域为局部,则只对它自己是可见的,因此不能被其他函数调用。这样的函数将无法运行)。
  不同的 C++ 存储方式是通过存储持续性、作用域和链接性来描述的。下面来看看各种 C++ 存储方式的这些特征。首先介绍引入名称空间之前的情况,然后看一看名称空间带来的影响。
  在这里插入图片描述
在这里插入图片描述

9.2.2 自动存储持续性

  在默认情况下,在函数中声明的函数 参数和变量的存储持续性为自动,作用域为局部,没有链接性。也就是说,如果在 main()中声明了一个名为 texas 的变量,并在函数 oil()中也声明了一个名为 texas 变量,则创建了两个独立的变量 —— 只有在定义它们的函数中才能使用它们。对 oil()中的 texas 执行的任何操作都不会影响 main()中的 texas,反之亦然。另外,当程序开始执行这些变量所属的代码块时,将为其分配内存;当函数结束时,这些变量都将消失(注意,执行到代码块时,将为变量分配内存,但其作用域的起点为其声明位置)。
  如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。例如,假设在 main()的开头定义了一个名为 telideli 的变量,然后在 main()中开始一个新的代码块,并(真是DD痛,书中少一个“在”字)在其中定义了一个新的变量 websight,则 teledili 在内部代码块和外部代码块中都是可见的,而 websight 就只能在内部代码块中可见,它的作用域从定义它的位置到该代码块的结尾:

int main()
{
   
	int telidili = 5;
	{
   
		cout << "Hello\n";		// websight allocated < 划分给websight  >
		int websight = -2;		// websight scope begins < websight 作用域开始 >
		cout << "websight << ' ' << teledili << endl;
	}							// websight expires < websight 终止 >
	cout << teledili << endl;
	...
}	// teledili expires

  然而,如果将内部代码块中的变量命名为 teledeli,而不是 websight,使得有两个同名的变量(一个位于外部代码块中,另一个位于内部代码块中),情况将如果呢?在这种情况下,程序执行内部代码块中的语句时,将 teledili 解释为局部代码块变量。我们说,新的定义隐藏了(hide)以前的定义,新定义可见,旧定义暂时不可见。在程序离开该代码块是,原来的定义又重新可见(参见图 9.2)。
在这里插入图片描述
  程序清单 9.4 表明,自动变量只在包含它们的函数或代码块中可见。
  程序清单 9.4 auto.cpp
——————————————————————————————————————————————————————————

// autoscp.cpp -- illustrating scope of automatic variables < 说明自动变量的范围 >
#include <iostream>
void oil(int x);

int main()
{
   
	using namespace std;
	
	int texas = 31;
	int year = 2020;
	cout << "In main(), texas = " << texas << ", &texas = ";
	cout << &texas << endl;
	cout << "In main(), year = " << year << ", &year = ";
	cout << &year << endl;
	oil(texas);
	cout << "In main(), texas = " << texas << ", &texas = ";
	cout << &texas << endl;
	cout << "In main(), year = " << year << ", &year = ";
	cout << &year << endl;
	
	return 0;
}

void oil(int x)
{
   
	using namespace std;
	int texas = 5;
	
	cout << "In oil(), texas = " << texas << ", &texas = ";
	cout << &texas << endl;
	cout << "In oil(), x = " << x << ", &x = ";
	cout << &x << endl;
	{
   
		int texas = 113;
		cout << "In block, texas = " << texas;
		cout << ", &texas = " << &texas << endl;
		cout << "In block, x = "<< x << ", &x = ";
		cout << &x << endl;
	}
	cout << "Post-block texas = " << texas;
	cout << ", &texas = " << &texas << endl;
}

——————————————————————————————————————————————————————————
  下面是该程序的输出:
在这里插入图片描述
  在程序清单 9.4 中,3个 texas 变量的地址各不相同,而程序使用当前可见的那个变量,因此将 113 赋给 oil()中的内部代码块中的 texas,对其他同名变量没有影响。同样,实际的地址值和地址格式随系统而异。
  现在总结一下整个过程。执行到 main()时,程序为 texas 和 year 分配空间,使得这些变量可见。当程序调用 oil()时,这些变量仍留在内存中,但不可见。为两个新变量(x 和 texas)分配内存,从而使它们可见。在程序执行到 oil()中的内部代码块时,新的 texas 将不可见,它被一个更新的定义代替。然而,变量 x 仍然可见,这是因为该代码块中没有定义 x 变量。当程序流程离开该代码块时,将释放最新的 texas 使用的内存,而第二个 texas 再次可见。当 oil()函数结束时,texas 和 x 都将过期,而最初的 texas 和 year 再次变得可见。
 在这里插入图片描述

使用 C++11 中的 auto
  在 C++11 中,关键字 auto 用于自动类型推断,这在第 3、7 和 8 章介绍过。但在 C 语言和以前的 C++ 版本中,auto 的含义截然不同,它用于显式地指出变量为自动存储:

int froob(int n)
{
   
	auto float ford;	// ford has automatic storage < ford 是自动存储器 >
	...
}

  由于只能将关键字 auto 用于默认认为自动的变量,因此程序员几乎不使用它。它的主要用途是指出当前变量为局部自动变量。
  在 C++11 中,这种用法不再合法。制定标准的人不愿引入新关键字。因为这样做可能导致将该关键字用于其他的代码非法。考虑到 auto 的老用法很少使用,因此赋予其新含义比引入新关键字是更好的选择。
  1. 自动变量的初始化
  可以使用任何在声明时其值为已知的表达式来初始化自动变量,下面的示例初始化变量 x、y 和 z:`

int w;						// value of w is indeterminate
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
cin >> w;
int z = 3 * w;				// use new value of w

  2. 自动变量和sgt
  了解典型的 C++ 编译器如何实现自动变量有助于更深入地了解自动变量。由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其袖为栈,以管理变量的增减。之所以被称为栈,是由于新数据被象征性地放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一个内存单元中),当程序使用完后,将其从栈中删除。栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。程序使用两个指针来跟踪栈,一个指针指向栈底 —— 栈的开始位置,另一个指针指向堆顶 —— 下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的会是,从而释放新的变量使用的内存。
  栈是 LIFO(后进先出)的,即最后加入到栈中变量首先被弹出。这种设计简化了参数传递。函数调用 将其参数的值放在栈顶,然后重新设置栈顶指针。被调用 的函数根据其形参描述来确定每个参数的地址。例如,图 9.3 青蛙,函数 fib()被调用时,传递一个 2 字节的 int 和一个 4 字节的 long。这些值被加入到栈中,当 fib()开始执行时,它将名称 real 和 tell 同这两个值关联起来。当 fib()结束时,栈顶指针重新指向以前的位置。新值没有被删除,但不再被标记,它们所占据的空间被下一个将值加入到栈中的函数调用所使用(9.3 做了简化,因为函数调用可能传递其他信息,如返回地址)。
  在这里插入图片描述
  3. 寄存器变量
  关键字 register 最初是由 C 语言引入的。它建议编译器使用 CPU 寄存器来存储自动变量:

register int cout_fast;		// request for a register variable < 对寄存器变量的请求 >

  这旨在提高访问变量的速度。
  在 C++11 之前,这个关键字在 C++ 中的用法始终求变,只是随着硬件和编译器变得越来截止复杂,这种提示表明变量用得很多,编译器可对其做特殊处理。在 C++ 中,这种提示作用也失去了,关键字 register 只是显式地指出变量是自动的。鉴于关键字 register 只能用于原本就是自动的变量,使用它的唯一原因是,指出程序员想使用一个自动变量,这个变量的名称可能与外部变量相同。这与 auto 以前的用途完全相同。保留关键字 resgister 的重要原因是,避免使用了该关键字的现有代码非法。

9.2.3 静态持续变量

  和 C 语言一样,C++ 也为静态存储持续性变量提供了 3 种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这 3 种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将把它设置为 0。在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为 0.

  注意:传统的 K&R C不允许初始化自动数组和结构,但允许初始化静态数组和结构。 ANSI C 和 C++ 允许对这两种数组和结构进行初始化,但有些旧的 C++ 翻译器使用与 ANSI C 不完全兼容的 C 编译器。如果使用是这样的的实现,则可能需要使用这 3 种静态存储类型之一,以初始化数组和结构。

  下面介绍如休创建这 3 种静态持续变量,然后介绍它们的特点。要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用 static 既定符;要创建没有链接性的静态变量,必须在代码块内声明它,并使用 static 限定符。下面的代码片段说明这 3 种变量:

...
int globla = 1000;					// static duration, external linkage < 静态持续性,外部链接 >
static int one_file = 50;			// ststic duration, internal linkage < 静态持续性,内部链接 >
int main()
{
   
...
}
void funct1(int n)
{
   
	ststic int count = 0;		// static duration, no linkage
	int llama = 0;
	...
}
void funct2(int q)
{
   
...
}

  正如前面指出的,所有静态持续变量(上述示例中的 global、one_file和 count)在整个程序执行期间都存在。在 funct1()中声明的变量 count 的作用域为局部,没有链接性,这意味着只能在 funct1()函数中使用它,就像自动变量 llama 一样。然而,与 llama 不同的是,即使在 funct1()函数没有被执行时,count 也留在内存中。globle 和 one_file 的作用域都为整个文件,即在从声明位置到文件结尾的范围内都可以被使用。具体地说,可以在 main( )、funct1( ) 和 funct2( )中使用它们。由于 one_file 的链接性为内部,因此只能在包含上述代码的文件中使用它;由于 global 的链接性为外部,因此可以在程序的其他文件中使用它。
  所有的静态持续变量都有下述初始化特征:未被初始化的静态变量的所有位都被设置为 0。这种变量被称为零初始化的(zero-initialized).
  在这里插入图片描述
  在这里插入图片描述
  表 9.1 总结了引入名称空间之前使用的存储特性。下面详细介绍各种静态持续性。
  表 9.1 指出了关键字 static 的两种用法,但含义有些不同:用于局部声明,经指出变量是无链接性的静态变量时,static 表示的是存储持续性;而用于代码块外的声明时,static 表示内部链接性,而变量已经是静态持续性了。有人称之为关键字重载,即关键字的含义于上下文。在这里插入图片描述
  静态变量的初始化
  除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。你可能猜到了,零初始化意味着将变量设置为零。对于标题类型,零将被强制转换为合适的类型。例如,在 C++ 代码中,空指针用 0 表示,但内部可能采用非零表示,因此指针变量将初始化相应的内部表示。结构成员被零初始化,且填充位都被设置为零。
  零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件(翻遍单元)时初始化变量。动态初始化意味着将编译后初始化。
  那么初始化形式由什么因素决定呢?首先,所有静态变量都被零初始化,而不管程序员是否显式地初始化了它。接下来,如果使用常量表达式初始了变量,且编译器仅根据文件内容(包括被包含的头文件)就可计算表达式,编译器将执行常量表达式初始化。必要时,编译器将执行简单计算。如果没有足够的信息,变量将被动态初始化。请看下面的代码:

#include <cmath>
int x;									// zero - initialization < 0 初始化 >
int y = 5;								// constant - expression initialization < 常量表达式初始化 >
long z = 13 * 13;						// constant - expression initialization < 常量表达式初始化 >
const double pi = 4.0 * atan(1.0);		// dynamic initialization < 动态初始化 >

  首先,x、y、z 和 pi 被零初始化。然后,编译器计算常量表达式,并将 y 和 z 分别初始化为 5 和 169。但要初始化 pi,必须调用函数 atan(),这需要等到该函数被链接且程序执行时。
  常量表达式并非只能是使用字面常量和算术表达式。例如,它还可使用 sizeof 运算符:

int enouth = 2 * sizeof(long) +
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值