C++作用域 (一)

1概述

在所有的计算机程序中,一个基本的目标是操作一些数据,然后获得一些结果。为了操作这些数据,需要为这些数据分配一段内存,我们可以将这段内存称为变量。为了方便操作,以及程序可读性方面的考虑,需要使用一个有意义的名称来引用这段内存,这个名称就是变量名。

将名称和一段内存关联起来的工作可以分成两个阶段来进行,分别是变量的声明和定义。在变量声明的时候,只是引入了一个名称,该名称并没有和一段特定的内存关联。也就是说,在声明变量的时候,只是引入了一个助记符,并没有执行内存分配。在定义变量的时候,将前面声明过程中引入的名称关联到了一段特定的内存,内存的大小由变量的类型决定。也就是说,在定义变量的时候,真正执行了内存分配。在有的情况下,变量的声明和定义是需要分开进行的,如:全局变量的声明和定义,可以在多个文件中使用该变量;而在某些情况下,使用一个语句就可以完成变量的声明和定义,如:局部变量的声明和定义。只需要在一个文件中使用该变量。

在C++程序中,当声明并定义了一个变量以后,需要关注如下两个问题:

由声明引入的变量名可以用在什么地方,如何进行名字解析;
由定义分配的内存的生命周期是多少。
为了解决这两个问题,就需要引入作用域的概念。作用域是C++程序中的一段区域,一般用正反两个花括号来界定它的范围。在同一个作用域范围内,一个名称只能唯一关联到一个实体,这个实体可以是变量,函数,类型,模版等。也就是说,在同一作用域范围内,不同的实体必须对应不同的名称,绝对不允许出现两个不同的实体对应同一个相同的名称的情况。一个名称可以和不同作用域中的不同实体相对应。也就是说,对于同一个名称,在不同的作用域中可以重复使用。

在本文的后续部分,将对各种类型的作用域进行描述,并且介绍在作用域中进行名字解析的规则。

2作用域的分类

2.1概述

我们可以将整个C++程序(在程序中包括各种类型,函数,模版,变量等,并且分布在很多个*.cpp文件中)看成一个很大的整体区域。为了方便对C++程序中已经定义的各种类型,函数,模版,变量的管理,可以把这片大的区域划分成一片片小的命名区段。然后根据各个类型,函数,模版,变量的功能以及用途等,再把这些类型,函数,模版,变量等分别放置在不同的区段中。这些小的区段叫做作用域,C++程序支持四种形式的作用域,分别是:名字空间作用域,类域,局部作用域,语句作用域。

名字空间作用域就是程序员利用名字空间定义在C++程序中划分出来的一块比较大的程序区段。在该程序区段内部,可以定义类型,函数,模版,变量。名字空间作用域可以跨越多个*.cpp文件而存在。在名字空间作用域内部还可以继续定义其他的名字空间作用域,也就是说,名字空间作用域是可以互相嵌套的。

全局作用域是C++程序最外层的名字空间作用域,也是最大的名字空间作用域。全局作用域天然存在于C++程序中,它不需要由程序员人为地定义。在全局作用域内部,可以包含其他的,由程序员定义的名字空间作用域,以及没有包含在其他名字空间作用域中的类型,函数,模版,变量。在全局作用域中定义的变量是全局变量,在全局作用域中定义的函数是全局函数。

在C++程序中,每定义一个类就会引入一个类域。类体所包含的范围就是类域的范围,在类中定义的所有成员都属于该类域。类域位于名字空间作用域内部,该名字空间作用域可能是全局作用域,也可能是用户定义的名字空间作用域。

每一个函数体内部都是一个局部作用域。该作用域起始于函数体的左花括号“{”,结束于函数体的右花括号“}”。每一个函数都有一个独立的局部作用域。在局部作用域内定义的变量都是局部变量。

在C++程序中,当要求使用单个语句,但程序逻辑却需要不止一个单个语句的时候,我们可以使用复合语句。复合语句通常被称为块,是用花括号括起来的一些单个语句的集合。在复合语句花括号内部的区段也属于局部作用域。

有些语句存在控制结构,并且允许在控制结构中定义变量。如:

 
示例一:
for ( int K = 0; K < 100;K++ )
  cout << K;     //该行语句属于语句作用域范围,K仅在这一行有效。

示例二:
for (int K = 0; K < 100;K++)
{//其他代码
   cout << k;   //花括号内部是复合语句,都属于语句作用域。K在整个花括号内有效。//其他代码
}
从控制语句的开始到控制语句结束这一段区域被称为语句作用域。在该控制结构中定义的变量,仅在该语句作用域内有效。如:示例二中,K在花括号内有效,或者示例一中,仅在语句“cout << K;”中有效。语句作用域是最小的作用域。

全局作用域,名字空间作用域,类域,局部作用域,语句作用域之间的关系如下图所示:
在这里插入图片描述

从上图可以看出,在全局作用域中,定义了两个名字空间H和K。名字空间H又分别位于两个CPP文件A和B中。由此可见,名字空间作用域是可以跨越CPP文件的。在名字空间K中,除了定义了类型外,又定义了一个名字空间N,所以说,名字空间之间是可以互相嵌套的。另外,在名字空间中可以定义类,函数,变量,模版等。

在全局作用域中,除了定义的名字空间H和K外,又定义了一个类D,以及全局函数,全局变量和模版。在类D中,定义了一些成员函数,因此引出了局部作用域。在局部作用域中,如果存在控制语句,就会存在语句作用域。

在各种作用域中定义的变量或对象,其生命周期从该变量被定义开始,直到该作用域结束。如:在全局作用域中定义的变量,其生命周期是整个程序的生命周期,程序运行结束,该变量被释放;在局部作用域中定义的变量,其生命周期是从定义该变量开始,直到该函数执行完毕。

2.2名字空间作用域

2.2.1名字空间的意义

使用名字空间可以在一定程度上解决命名冲突的问题。假设没有名字空间,那么在C++程序中,所有的实体,如:函数,类型,变量,模版等,都必须被放置在全局域中作为全局实体而出现。在全局域中,这些实体必须具有唯一的名称,不允许存在多个实体同名的情况。因此,当在全局域中引入一些第三方开发的类库的时候,必须要保证第三方类库中命名的实体与全局域中命名的实体在命名方面不冲突。但是,这是很难保证的。为了解决这个问题,就引入了名字空间的概念。

第三方开发方在开发类库的时候,可以首先声明一个名字空间,每一个用户声明的名称空间都代表一个不同的名字空间域。在该名字空间中,可以包含嵌套其他的名称空间,以及函数,类型,变量,模版等的声明和定义。在该名称空间内部声明的实体被称为名称空间成员。用户在名字空间中声明的每个实体的名字必须是唯一的,不允许重名。因为在不同用户声明的名字空间中引入了不同的域,所以在这些由不同用户声明的名字空间中可以使用相同的名称。通过这种方式解决了命名冲突的问题。

在使用名字空间中的成员的时候,名字空间成员的名字会自动与该名字空间重合,或者说被其限定修饰。如:在名字空间A中声明的类B,它的名字是:A::B。

2.2.2名字空间的定义

用户声明的名字空间以namespace关键字开头,后面是名字空间的名称。名字空间的范围以花括号界定,具体的格式如下:

namespace mySpace //mySpace是名字空间的名称

{

   Class myClass {}; //类定义

   Int myFunction(int para1,int para2); //函数的声明

   Extern double myVar; //变量的声明

}
在上面的示例中,声明了一个名称为mySpace的名字空间,该名字空间的作用域由花括号界定,在花括号内部的部分都属于该名字空间的作用域。在该名字空间中,定义了一个类:myClass,声明了一个函数:myFunction,以及一个变量myVar。它们都是该名字空间的成员。

用户声明的名字空间可以位于全局作用域中,也可以位于其他的名字空间的作用域中。在当前的作用域中,名字空间的名称是唯一的,不能与其类型的实体重名。

在同一个作用域中,可以多次声明相同名称的名字空间。在这种情况下,将会实现名字空间的累加。比如,A.h头文件和A.cpp源文件都位于全局作用域中,在这两个文件中分别声明如下的名字空间:

A. h文件的代码实现:
namespace mySpace //在这里实现了函数和变量的声明,属于接口部分。
{
   Int AddData (int para1,int para2); //函数的声明
   Extern double myVar;  //变量的声明
}


B.cpp文件的代码实现:
Include “A.h”

namespace mySpace // 在这里实现了函数和变量的定义,属于实现部分。
{
   Int AddData(int Para1,int Para2) //函数的定义
   {
      Return Para1+Para2;
   }
    Double myVar = 3.14;  //变量的定义,并初始化。
}

在这里,存在这样一个规则:在同一个作用域中,如果新声明的一个名字空间的名称与前面声明过的名字空间的名称同名,那么这个后声明的名字空间就是前面声明的名字空间的累加,这两部分内容属于同一个名字空间;如果新声明的这个名字空间不与当前作用域中任何名字空间同名,那么就会定义一个新的名字空间。

在上面的示例中,A.h和A.cpp文件位于全局作用域中。在全局作用域中,两次声明的名字空间具有相同的名称:mySpace。因此,认为这两次声明的名字空间属于同一个名字空间。

通过对上面所描述的规则的使用,在程序设计的时候,可以根据需要,将名字空间的声明拆分成若干个部分来实现,只要这几个部分的声明都在同一个作用域中即可。这个规则的一个典型应用就是:实现接口和具体实现的分离。

在上面的示例中,我们将函数AddData和变量myVar的声明放在了A.h头文件中,而将它们的定义放在了另外一个A.cpp的源文件中。 A.h头文件实现的是函数库的接口的,而A.cpp文件中的内容则是针对接口的实现。因此,在程序设计和开发的时候,这两部分内容可以分别由不同的人在不同的时间实现。通过这种方式,实现了接口和具体实现分离的原则。

2.2.3名字空间成员的定义

当定义了名字空间以后,就可以想名字空间中添加成员。这些被添加的成员可以是:类型,函数,变量,模版等。可以通过两种方式向名字空间中添加成员。

第一种方式是:在定义名字空间的同时,在名字空间的花括号内直接完成名字空间成员的定义。也就是说,无论名字空间的定义是采用累加的形式,还是该名字空间分布在多个物理文件中,名字空间成员的声明和定义都在名字空间内部进行。具体示例如下:

方式一:在名字空间中直接完成成员的定义。成员的定义不在划分为声明和定义两部分。

Namespace mySpace
{
   Double myVar = 3.14;
   Int myFunction(int Para1)
   {
       Return Para1*10;
   }
}

方式二:在名字空间中先完成成员的声明,然后采用名字空间累加的方式,在其他部分完成成员的定义。这个“其他部分”,可以是其他的物理文件,也可以是同一个物理文件。

Namespace mySpace
{
	Extern double myVar;
	Int myFunction(int Para1);
}
Namespace mySpace
{
	Double myVar = 3.14;
	Int myFunction(int Para1)
	{
   		Return Para1*10;
	}
}

在上面的代码中,在定义了名字空间的同时(无论是采用累加方式,还是一次性完成),在名字空间内部完成了函数myFunction和变量myVar的定义。名字空间的定义和名字空间成员的定义同步完成。

第二种方式是:在定义名字空间的时候,仅仅在名字空间中完成对名字空间成员的声明,而名字空间成员的定义在名字空间之外被实现。具体代码如下:

//首先在一个文件中完成名字空间的定义,以及名字空间成员的声明。一般情况下,该文件为头文件(A.h)。

Namespace mySpace
{
	Class myClass {.}//声明一个类型
	myClass myFunction(myClass Para1);//声明一个函数,该函数返回myClass类型,并以myClass类型为参数。
}

在上面的代码中,完成了对名字空间mySpace的定义,同时在名字空间内部,完成了类myClass的定义,以及对函数myFunction的声明。接下来需要在其他地方,名字空间以外,完成对名字空间成员myFunction函数的定义。具体代码如下:

//实现函数myFunction定义的位置,可以是另外一个文件,一般为cpp文件,但是也可以在原来的头文件中(一般不会这么干)。

#include “A.h”
mySpace::myClass mySpace::myFunction(myClass Para1)
{
    //下面完成函数的具体实现。}

在上面的代码中,我们可以看到两处差异。一处是函数的返回值类型,myClass被名字空间mySpace限定修饰了;而在函数的参数类型处,myClass直接使用,没有被名字空间mySpace限定修饰。

这里存在这样一个规则:在函数的限定修饰名称“mySpace::myFunction”之后,直到方括号结束的区域都属于mySpace名字空间的作用域范围。也就是上面代码中的红色部分。

也就是说名字空间的作用域可能会有两部分组成,在大多数情况下,名字空间的作用域是由定义名字空间的时候,名字空间体的花括号界定的。但是,当在名字空间之外定义名称空间的成员的时候,在名字空间成员的限定修饰名之后直到结束花括号(” }”),或者分号(;)的部分都属于该名字空间作用域范围。

因此,在上面的代码中,参数的类型不需要被限定修饰,因为那个区域是属于名字空间作用域内的;而函数的返回类型必须要被限定修饰,因为那个区域不属于名字空间的作用域内。

另外还需要注意,在名字空间之外实现名字空间成员的定义的时候,要有一个前提,那就是:名字空间成员的声明必须在名字空间之内实现。

2.2.4名字空间成员的使用

在C++程序中,使用名字空间的方式封装一些函数库或者类库的时候,一般情况下,通常的做法是这样的:首先在一个头文件中定义一个名字空间,然后在该名字空间的定义中声明所有的名字空间成员,如:函数,类型,变量等。之后将这个头文件引入到一个cpp文件中,并且在这个cpp文件中实现所有名字空间成员的定义。具体示例如下:

-----------------A.h------------------------------//头文件名称
namespace myCPlusPlusFunctionsV1.0
{
     Class myClass {//类成员的声明 }; //定义一个类型
     Extern double myVar; //声明变量
     Void DealClass(myClass*); //声明函数
}
-----------------A.cpp--------------------------//源文件

#include “A.h”

Namespace myCPlusPlusFunctionsV1.0
{
	myClass:: myClass() {// myClass构造函数的实现}//其他myClass类成员的定义。double myVar = 3.14;//变量的定义
	void DealClass(myClass*pClass)
	{//函数的具体实现。
	}
}

在使用这些函数库或者类库的时候,首先需要将这个定义了该名字空间的头文件引入,然后开始使用该名字空间中的一些成员。在使用名字空间成员的时候,有三种方式:

第一种方式:域操作符方式。通过域操作符加名字空间名称的方式对名字空间成员名进行限定修饰。具体代码如下:

------------------otherCPlusPlusFile.cpp-------------------------

#include “A.h”
Void main()
{
   myCPlusPlusFunctionsV1.0::myClass *pClass = new myCPlusPlusFunctionsV1.0::myClass;
   myCPlusPlusFunctionsV1.01::DealClass(pClass);
}

在上面的代码中,“::”是域操作符。名字空间成员的声明被隐藏在名字空间之中,所以,名称空间的成员名称不会与当前作用域中的对象实体名称产生冲突。在使用名字空间成员的时候,可以使用名字空间名+域操作符+名字空间成员名称的方式将名字空间成员引入到当前的作用域中。否则,在当前作用域中,编译器不会找到名字空间的成员。

域操作符也可以被用来引用全局作用域的成员。因为全局作用域没有名称,所以使用如下的符号:

::member_name

指向全局名字空间的成员。当全局名字空间成员的名称被局部作用域中的名字隐藏的时候,但又需要在局部作用域中使用全局成员的时候,就可以使用这种引用方式。

在上面的示例中,名字空间的名称“myCPlusPlusFunctionsV1.0”比较长,在使用的时候,可能会不方便,因此,C++在处理这个问题的时候,引入了名字空间别名的概念。

所谓名字空间别名就是为已经定义的名字空间取一个其他的、替代性的名称,一帮情况下,这个名称是简短的,容易记忆的。具体使用方式如下:
 
------------------otherCPlusPlusFile.cpp-------------------------
#include “A.h”
Namespace myC++ = myCPlusPlusFunctionsV1.0;
Void main()
{
   myC++::myClass *pClass = new myC++::myClass;
   myC++::DealClass(pClass);
}

在上面的代码中,为名字空间“myCPlusPlusFunctionsV1.0”定义了一个别名“myC++”。之后在引用该名字空间成员的时候,就可以使用该别名。

定义名字空间别名的格式是:以关键字namespace开头,后跟名字空间的别名,并且等于前面定义好的名字空间的名称。

第二种方式:使用using 声明,一次引入一个名字空间成员。

Using 声明的作用是:使一个名字空间成员在当前作用域中可见,可见的范围是从using声明的语句开始,直到当前作用域结束。如果在using声明语句之后,在当前作用域中又嵌套了其他的作用域,那么using声明在当前作用域中的嵌套作用域中也同样有效。

Using声明以关键字using开头,后跟名字空间的成员名称。该成员名称必须是名字空间名称+域操作符+名字空间成员名称形式的限定修饰名称。具体代码如下:

//名字空间的定义
Namespace mySpace
{
	Int myFunction(int Para)//在名字空间中定义了一个函数
	{
     	Return Para*10;
	}
}

//在全局作用域中使用using声明,将名字空间成员名引入当前作用域。
Using mySpace::myFunction;
//开始使用名字空间的成员
Void main()
{
   //也可以在此位置使用using声明,即在局部作用域使用using声明。
   myFunction(10);//使用名字空间的成员。因为使用了using声明,所以不需要使用限定修饰的形式。名称myFunction从using声明开始,直到当前作用域结束。
}

在上面的代码中,首先定义了一个名字空间,并在名字空间中定义了一个函数。然后在全局作用域中使用了using声明。之后,在main函数中使用名字空间的成员函数myFucntin。

可以在全局作用域,名字空间作用域,局部作用域中使用using声明。在使用了using 声明以后,一次只能从源名字空间向当前作用域中引入一个名字空间成员,但可以多次使用using声明。如果该名字空间成员是函数,并且在该名字空间中具有多个重载,那么在使用using声明的时候,所有的重载函数都会被引入到当前的作用域中。被引入的名字空间成员名只在当前作用域中有效,并且名称唯一。这个被引入的名字空间成员名会隐藏当前作用域外围作用域中的同名名称,也会被当前作用域的嵌套作用域中的同名名称隐藏。具体情况见如下代码:

namespace mySpace
{
   Int myIntVar = 10;//定义一个整型变量。名字空间成员。
}

Int myIntVar = 100;//全局变量
Int main()
{
	Using mySpace::myIntVar;//该using声明隐藏了全局变量myIntVar。
	Int k = 10;
	K = k + myIntVar;//使用的是名字空间的成员变量,所以k的值等于20.
	K = K + ::myIntVar;//这里使用的是全局变量,所以k的值等于110.
	{
     	Int myIntVar = 50;//在此语句作用域中声明的变量隐藏了前面using声明中引入的变量。
    	Int a  = myIntVar ;//a = 50
    	Int b  = ::myIntVar;//b = 100;
   	 	Int C  = mySpace::myIntVar;//c = 10;
	}
}

使用using声明将名字空间的成员引入到当前作用域的时候,除了重载函数以外,被引入的成员名称不能与当前作用域中定义的对象实体重名,否则会引起错误。

第三种方式:使用using 指示符,一次引入所有名字空间成员。

Using指示符以关键字using 开头后跟关键字namespace,最后是名字空间的名称。该名字空间的名称必须在前面已经定义。其作用域从using指示符开始,直到当前作用域结束。使用using指示符以后,将会把名字空间中的所有成员引入到当前作用域。具体的代码如下:

//定义名字空间
Namespace mySpace
{
	Int myFunction(int Para)
	{
   		Return Para*10;
	}
	Int myVar = 100;
}

//使用using指示符,将名字空间的所有成员引入到当前作用域。目前是全局作用域。
Using namespace mySpace;
Void main()
{
	Int k = myVar + 10;//使用using指示符以后,可以直接使用名字空间中的成员,就好像该//名字空间的成员在当前作用域中定义的一样,不需要限定修饰。
	myFunction(k);
}

在上面的代码中,首先定义了一个名字空间mySpace,同时在名字空间中定义了一个函数myFunction,以及一个变量myVar。然后使用using指示符将该名字空间中的成员引入到了全局作用域中。之后,在main函数中使用名字空间的成员,使用的时候,不需要限定修饰,就好像使用当前名字空间中定义的成员一样。

在当前作用域使用using指示符以后,被引用的名字空间将与当前的作用域合并,名字空间中的成员就好像在当前作用域被定义一样。因此,在当前作用域中,不能定义与名称空间成员重名的对象。否则会因此错误。

2.2.6 标准名字空间std

在名字空间的概念被提出之前,在C++中就已经存在了大量的库函数。这些库函数有的是标注C形式的,也有的是标准C++形式的。在声明这些库函数的时候,按照其功能和类别,它们被划分到很多不同的头文件中,如:iostream.h,complox.h,stdio.h。当名字空间的概念被提出之后,这些库函数被重新整理,将它们的声明和定义放到了名称空间名称为std的名称空间中。它们被称为标准C++库。

但是为了向前兼容以前实现的C++程序,在对这些库函数进行整理的时候,创建了新的头文件,并采用了新的命名规则,以区分原有的库函数。具体的处理方式描述如下:

对于支持C++的头文件,如:<iostream.h>,在被重新整理之后,它的名称为去掉了头文件的扩展名。新的头文件所包含的功能与旧头文件基本相同,但是它们在std名字空间中;
对于支持C标准的头文件,如:<stdio.h>,在被重新整理之后,它的名称为,在名称的前面加上了前缀字符“C”,并去掉扩展名。新的头文件所包含的功能与旧的头文件基本相同,但是它们在std名字空间中。
原有旧的C++标准头文件,如<iostream.h>,依然被支持,它们不在名字空间std中;
原有旧的C标准头文件,如<stdio.h>,依然被支持,它们不在名字空间std中。

具体情况如下图所示:

在这里插入图片描述

在C++标准库中,一共50个头文件,10个分类,其中18个C库功能。与宏相关的名称在全局作用域中定义,其他的在名字空间std中被定义。

2.2.5名字空间的嵌套

在用户声明的名字空间中还可以继续嵌套其他的名字空间,通过这种分层次的名字空间的结构可以改善函数库的代码组织结构。具体代码如下:

Namespace myFirstSpace

{

Int myVar = 10;

Namespace mySecondSpace

{

   int dlVar = 314;

   Int myVar = 100;//它会隐藏外围名字空间声明的变量。

}

}

只要需要,名字空间的嵌套可以一直向下持续下去。在名字空间嵌套的时候,外围名字空间声明的变量可能会被里面嵌套的名字空间声明的同名变量隐藏。在使用嵌套名字空间成员的时候,有三种方式,具体情况如下:

//第一种形式:限定修饰名称形式

Int a = MyFirstSpace::mySecondSpace::dlVar;

//第二中形式:using声明的形式:

Using myFirstSpace::mySecondSpace::dlVar;

Int a= dlVar;

//第三中形式:using指示符形式:

Using namespace myFirstSpace::mySecondSpace;

Int a = dlVar;

 

2.2.6未命名名字空间

使用未命名的名字空间,可以定义文件作用域。具有文件作用域的名字空间只在定义它的文件中有效,在其他文件中访问不到该作用域。

未命名名字空间的定义格式如下:

----------------------------A.cpp--------------------------

Namespace
{
   Int a = 10;
   Void myFunction(int Para)
}

//使用未命名名字空间中的成员
Void main()
{
   myFunciton(a);//直接使用,不需要限定修饰。
}
在使用未命名名字空间中的成员的时候,可以直接使用,不需要限定修饰。未命名名字空间中的成员只能在定义它的文件中使用,在其他文件中是无法访问的。

原文链接:https://www.cnblogs.com/wolf-lifeng/p/3156920.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值