全局做用域是最大的名字空间做用域,不一样于用户自定义的名字空间做用域,全局做用域不须要显示地定义,它自然存在于C++程序中。全局做用域是一个最外层的容器,是全部做用域的父做用域。在全局做用域中,能够定义其余的名字空间,类型,函数,变量,模版等。函数
在全局做用域中定义的函数是全局函数,在全局做用域中定义的变量是全局对象。全局函数和全局对象在整个全局做用域及其子做用域中有效,它们的生命周期贯穿于整个程序的运行。从定义它们开始直到整个程序运行结束。spa
变量能够被声明屡次,但只能被定义一次。声明和定义是两个不一样的概念。线程
在变量定义的时候,除了向程序代表变量的类型和名称外,还要为变量分配存储空间以及进行初始化操做。在定义全局变量的时候,若是没有为变量赋初值,那么系统将执行默认的初始化工做,如:整型变量赋值0,字符串变量赋空值等。全局变量的值存储在程序的全局存储区,在全局存储区中还存储着常量的值,以及静态变量的值。指针
声明操做是定义操做的子集,在声明变量的时候,仅仅向程序代表变量的类型和名称,它不会为变量分配存储空间,所以也不会有初始化工做。对象
全局变量和全局函数的声明和定义的格式以下:blog
//声明接口 Void myFunction(int);//声明一个函数,指定该函数的名称,返回值,以及参数列表。生命周期 Extern void myFunction(int);//在声明函数的时候,extern关键字可选,加上该关键字表示显//示声明。进程 Extern in myVar; //声明一个变量。必须加关键字extern。代表此处仅仅是声明,而定义在其//他地方内存 Extern const char* const myConstVar;//声明一个指向常量的常量指针。 //定义 Void myFunction(int Para)//函数定义,包括函数的声明部分和函数体 { //函数的功能代码 } Int myVar = 10;//变量定义 const char* const myConstVar = new char[10];//常量定义。
Extern int myIntVar = 50;//变量的定义。 |
在声明全局变量的时候,必须加关键字extern,而且不能对变量进行初始化。若是对变量执行了初始化操做,即便在前面加了extern关键字,也会被认为是变量的定义,而不是变量的声明。
声明和定义的关系是:定义也是声明,定义包含了声明,但声明仅仅是定义的一个子步骤。有的时候,声明和定义能够合在一块儿进行,如局部变量的声明和定义,而在某种状况下,声明和定义是须要分开进行的。当一个变量须要在多个文件中被使用的时候,就须要将声明和定义分开(通常为全局变量);不然使用一个定义语句同时完成变量的声明和定义便可(通常为局部变量)。
一个变量能够被声明屡次,但它只能被定义一次。在这里存在一个法则,一次定义法则:全局对象和全局函数或者只有一次定义,或者在一个程序中有多个彻底相同的定义(内两函数和全局常量的状况)。
为了遵照一次定义法则,须要合理的文件组织形式。在通常状况下,全局做用域中定义的全局变量和全局函数都须要在多个文件中被使用。在这种状况下,最合理的文件组织形式就是:“头文件+cpp文件”的形式。具体的过程描述以下:
头文件是用来声明而不是定义函数和变量的,而源文件则是用来实现变量和函数定义的。头文件中通常包含类的定义,枚举的定义,符号常量的定义,内联函数的定义,extern变量的声明,函数的声明,typedef声明。若是将变量定义到头文件中,那么当其余文件引入该头文件的时候,就会认为该对头文件中的变量又执行了一次定义。根据一次定义法则,这是不容许的。可是也有三个例外,类,内联函数,在编译时值能够肯定的符号常量是能够在头文件中定义的。
按照头文件的方式处理全局变量和全局函数的具体代码的格式以下:
//头文件中实现函数和变量的声明 --------------------A.h---------------------------------- Extern int myIntVar;//声明一个变量,必须以关键字extern开头。 Void myFunction(int);//声明一个函数,指定函数名称,返回值,参数列表。 Const double mydlVar = 3.14;//定义一个常量。该常量的值在编译时可知。 Inline double GetdlVar()//定义内联函数 { Return mydlVar; } Extern const char* const myConstVar;//声明全局常量,该常量的值在编译时不可知。
//源文件中实现变量和函数的定义。 ---------------------------A.cpp----------------------------------- #include “A.h” Int myIntVar = 100;//定义变量 Void myFunction(int Para) { //函数的实现代码 } const char* const myConstVar = new char[100];//常量的值在运行时才能肯定,所以在这里实现它的定义,而在头文件中仅仅是声明。 //在其余文件中使用定义的全局变量和全局函数,首先引入该头文件。 ----------------------------other.cpp-------------------------------- #include “A.h” --如下是具体使用。 |
在C++中,编译一个程序能够划分为以下的阶段:
若是在一个源文件中使用了“#Include”命令,将一个头文件引入到该源文件中。那么在编译器执行编译以前,会首先执行该预处理命令。即:将头文件中所包含的代码所有合并的目标源文件中,合并后的文件将做为一个文件存在。也就是说,编译器在执行编译的时候,只会看到源文件,头文件在编译阶段是不可见的。
那么,假设有以下的实现方式:在头文件A.h中,实现了一个变量的定义(不是声明),如:“int a = 10;”。每当该头文件被其余源文件引用一次之后(#include “A.h”),那么编译器就会认为对该变量执行了一次定义。若是该头文件被引用两次以上,就会发生重定义错误。这是违反一次定义法则的。
基于以上缘由,对于普通的全局变量,正确的使用方法是:首先在头文件中使用extern关键字实现该全局变量的声明;而后在源文件中引入该头文件,而且实现该全局变量的定义;最后,在使用该全局变量的源文件中引入声明了该全局变量的头文件,而后使用之。
对于普通的全局变量,只能使用此方法处理。
在全局做用域中定义的常量只具备文件做用域。也就是说,在全局做用域中定义的常量只在定义它的文件内有效,它不会影响到其余文件中定义的同名常量。举例以下:
--------------------------A.h---------------------------- Const int myIntVar = 100;//在头文件中定义了一个常量 ------------------------B.cpp---------------------------- #include “A.h” Int a = myIntVar; ------------------------C.cpp---------------------------- #include “A.h” Int b = myIntVar;
|
示例1
上面的代码可以正确运行。虽然“A.h”头文件被引用了屡次,可是因为全局常量只具备文件做用域,因此在B.cpp文件中定义的全局常量“myIntVar”与在C.cpp文件中定义的全局常量“myIntVar”互不影响。
这是使用全局常量的一种方式,只要该全局常量的值在编译时刻是肯定的。在上面的示例中,全局常量“myIntVar”不会被正真地存储到全局区。在编译阶段,编译器会用该全局常量的值去替换使用“myIntVar”名称的地方。所以,要求在使用全局常量的地方,该全局常量的定义是可见的。示例1中的方式可以知足这种要求。当对符号常量使用extern关键字,或者取符号常量的地址的时候,编译器会为符号常量分配存储空间,不然只是执行编译时刻的替换。
如今考虑以下问题,若是全局常量的值在编译时刻不肯定呢?好比:使用new操做符定义一个全局常量,那么状况会如何?举例以下:
-------------------A.h--------------------- Char* const pChar = new char[100];//定义一个常量指针。注意与:const char* pChar = new char[100]的区别。 -------------------B.cpp------------------ #include “A.h”
------------------C.cpp-------------------- #include “A.h” |
示例2
每当在源文件中引用一次该头文件,那么就会执行一次内存分配。这显然与咱们指望的结果不符。咱们想要的是一个常量指针,该指针指向一个字符串。而后,咱们能够在其余多个文件中使用该常量指针。
可使用关键字extern打破const常量的文件做用域,使之具备全局做用域。具体的做法举例以下:
-------------------------A.h--------------------- Extern char* const pChar;//声明全局常量,该头文件能够被其余文件引用 ------------------------A.cpp-------------------- #include “A.h” Char* const pChar = new char[100];//定义常量,编译器分配存储空间 Memset(pChar,’\0’,100); -------------------------B.cpp------------------- #include “A.h”//引入头文件,能够在此使用声明的全局常量 …. |
示例3
该做法是:使用关键字extern在头文件中声明全局常量。使用extern关键字后,该全局常量具备全局做用域,而不在局限于文件做用域。而后在源文件中实现该全局常量的定义。最后,在使用该全局常量的源文件中引用声明了该全局常量的头文件便可。这是使用全局常量的第二种方式,当全局常量的值在编译时刻不肯定的时候,将采用这种方式。
由此咱们能够看出,全局常量有两种使用方式。当全局常量的值在编译阶段是能够肯定的状况下,咱们可使用第一种方式,如:示例1;当全局常量的值在编译阶段是不肯定的,但在运行阶段是能够肯定的状况下,咱们可使用第二种方式,如:示例3。
在全局做用域中定义的静态变量也具备文件做用域。该静态变量只在定义它的文件中有效。在多个文件中定义的同名静态变量互不影响,不会出现重定义错误。而且,这些同名静态变量各自保持一份独立的数据拷贝,一个文件中的静态变量值发生变化的时候,不会影响到另一个文件中的同名静态变量的值。
静态变量的文件做用域是不可打破的,不能使用关键字extern使在全局做用域中定义的静态变量具备全局做用域。
所以,全局静态变量只有一种使用方式,相似于使用全局常量的第一种方式。举例以下:
-----------------------A.h------------------------- Static in myIntVar = 100; ----------------------B.h------------------------- Int myFunction1(); --------------------C.h---------------------------- Int myFunction2(); ----------------------B.cpp------------------------- #include “A.h” //引用定义静态变量的头文件,至关于在该源文件中定义了一次静态变量 #cindlue “B.h” Void myFunction1() { Int a = myIntVar; myIntVar++; return a; } ----------------------------c.cpp--------------------------- #include “A.h”//引用定义静态变量的头文件,至关于在该源文件中定义了一次静态变量 #include “C.h” Int myFunction2() { Int b = myIntVar; myIntVar++; return b; } -----------------------------.main.cpp----------------------------- #include “B.h” #include “c.h” Void main() { Int a = myFunction1(); Int b = myFunction2(); //在执行完毕后,a,b的值均为100。说明在文件做用域中的静态变量各自保持一份独立的数据拷贝,互相不影响。 } |
基于以上缘由,全局静态变量定义在源文件中便可,哪里须要,哪里定义。不须要事先定义到头文件中。
注意全局常量与全局静态变量的区别:默认状况下,全局常量和全局静态变量都具备文件做用域。可是,能够适应关键字extern,打破全局常量的文件做用域,使其在其余文件中也具备可访问性;不能使用关键字extern打破全局静态变量的文件做用域。
在编译阶段,编译器会将内联函数在调用点展开,将函数体中的代码合并到调用点。而不是在运行阶段执行压栈,出栈方式的函数调用。在编译器展开内联函数的时候,在当前文件中,内联函数的定义必须是可见的。所以,内联函数必须定义在头文件中,当其余的源文件引入了该头文件后,就至关于该内联函数的定义在该源文件中是可见的。根据一次定义法则,内联函数能够屡次定义,只要保证屡次定义的形式是相同的便可。
使用头文件和源文件结合的方式完成一个类的定义。类的定义,内联函数的定义,静态成员变量的声明实如今头文件中;类成员函数的定义,静态成员变量的定义实如今源文件中。在实现源文件的时候,必须引入定义该类的头文件。具体的示例代码以下:
---------------------------A.h------------------------------------ Class myClass { Typedef int sb4;//typedef定义,引入int类型的助记符。 Public: myClass(sb4 Para);//声明构造函数 //内联函数的声明在当前位置解析,内联函数的//定义在整个类域解析 inline void setValue(sb4 Para)//内联函数的声明部分。sb4能够直接被使用,由于在它以前已经声明了sb4。 { M_IVar = Para;//内联函数的定义部分。在整个类域解析,因此能够直接使用成员变量m_IVar。 } Static sb4 getValue();//声明静态成员函数 Private: Sb4 m_IVar;//定义成员变量 Static sb4 m_StaticVar;//声明静态变量 };//在整个花括号范围内,都属于类域 ----------------------------A.cpp--------------------------------- #include “A.h”//必须引入头文件 myClass::sb4 myClass::m_StaticVar = getValue();//静态成员变量的定义,并初始化。红色部分属于类域,成员函数getValue()能够被直接调用;绿色部分不属于类域,必须使用修饰限定名称引用。类型sb4必须使用修饰限定名称引用。 myClass::myClass(sb4 Para) { M_IVar = Para;//成员变量的初始化 }//红色部分属于类域,类型sb4能够直接使用,成员变量m_IVar能够直接使用。 myClass::sb4 myClass::getValue()//静态成员函数的定义。绿色部分不属于类域,必须使用修饰限定名称引用。类型sb4必须使用修饰限定名称引用。 { Return m_StaticVar; } |
在上面的代码中,将类定义分红了两大部分,分别在头文件和源文件中实现。头文件是对外的接口,源文件中封装具体实现。
每定义一个类就会引入一个类域,不一样的类具备不一样的类域。每个类成员,包括成员变量和成员函数都属于该类域。在类域内部,能够直接使用成员名称访问类成员;在类域外部,必须经过成员访问操做符或域解析操做符访问类成员。类域由以下三部分组成:
在源文件中,成员函数修饰限定名称或者静态变量修饰限定名称以前的部分不属于类域。若是要在这部分引用类域中的名称,必须使用修饰限定名。见2.4.1节的绿色部分。
在源文件中,属于类域中的成员名称能够被直接使用,不须要修饰限定。见2.4.1节红色部分。
当须要在某个程序文本文件中使用某个类的时候,须要在该文件中引入实现该类定义的头文件。在使用类成员的时候,有两种形式,分别是:在类域外部使用类的某个成员和在类域内部使用类的某个成员。
状况1:在类域外部使用某个类的成员的时候,必须使用成员访问操做符或域解析操做符。具体代码以下:
//开始在类外部使用该类的成员 Void main() { myClass objClass;//定义类对象; myClass* pClass = new myClass;//定义类指针。 objClass. setValue (100);//在类域外部,使用类成员访问操做符-点号的形式访问类成员。 pClass-> setValue (100);//在类域外部,使用类成员访问操做符-箭头的形式访问类成员。 myClass:: getValue ();//在类域外部,使用域解析操做符的形式访问类的静态成员。 myClass::sb4 myVar = 100;//在类域外部,使用域解析操做符的形式使用类中定义的类型。 } |
在类域外部,对于类对象或指向类对象的指针,须要使用成员访问操做符访问类成员;对于类的静态成员或在类中定义的类型,须要使用域解析操做符进行访问。
状况2:在类域内部使用某个类的成员的时候,能够直接使用成员名称。可是,该成员名称在使用之间必须被声明,不然没法使用。具体代码以下:
----------------------A.h----------------------------- Class myClass { Void setValue(sb4 Para);//错误。不能在此位置使用类型sb4,由于在使用以前没有声明该类型。 Typedef int sb4; Void setValue(sb4 Para);//正确。能够在此位置使用类型sb4,由于在使用之间已经声明了该类型。 }; |
由此能够看出,类成员的声明顺序很重要,它会影响到在类域内部对类成员的使用。
特例:在这里存在一个特殊状况,当在内联函数中使用类成员的时候,内联函数的声明部分在当前位置解析,内联函数的定义部分,在整个类域中解析。所以,在内联函数的声明部分,只能使用在它以前所声明的名称,遵照状况2所描述的规则;在内联函数的定义部分,能够直接使用整个类域中声明的名称。具体代码见2.4.1部分关于内联函数的示例。
在局部做用域中定义的变量是局部变量,局部变量又能够进一步划分为:自动变量,寄存器变量,以及静态局部变量。在C++程序中,除了局部变量外,还存在着其余类型的变量,如:全局变量,常量,静态变量,以及使用new操做符定义的变量,这些变量在内存中的分布状况以下图所示:
当启动一个应用程序的时候,就会启动一个进程。在32的系统中,为该进程分配4G的内存空间。一个进程下面,又会根据须要启动若干个线程,其中一个线程是主线程。在这4G的内存空间中,包含以下类型的存储区域:
不管是变量,函数,枚举,指针,引用仍是类型(包括内置类型或类类型),在使用以前,它们必须被声明或者定义。举例以下:
Int a = 10; a = a + 1; class myClass;//声明一个类
void myFunction(myClass& objClass)//定义一个函数。该函数的参数为myClass类型的引用。虽然在这里myClass类尚未被定义,可是这是容许的。 { } myClass * pClass = NULL;
class myClass { Public: Int m_IValue; } pClass = new myClass; myClass objClass; myFunction(objClass);//调用函数。 |
在语句“int a = 10;”中,若是要定义变量“a”,那么类型“int”必须首先被定义。由于类型int为内置类型,在使用该类型以前,它已经被定义了。因此在语句“int a = 10;”中,编译器知道为变量a分配四个字节的内存,而且在该内存位置存储数据10。若是在执行该语句以前,类型int是未知的,那么将会出现未定义错误。
在语句“a = a + 1;”中,若是要执行该语句,那么变量a必须在该语句以前被定义。在执行该语句的时候,从变量a所对应的内存中取出数据,加1后再存储到变量a所对应的内存中。若是在以前没有对变量a定义,也就是说没有为变量a分配内存,那么在执行该语句的时候,将会出现错误。
在语句“myClass * pClass = NULL;”中,该语句可以被正确执行,由于在该语句的前面,已经声明了myClass类。注意,这里仅仅是声明,类myClass尚未被定义。但这是容许的,由于对于任何类型的指针变量,它的大小都是固定的四个字节,在知道该类声明的状况下,就能够定义指针变量,而且为该指针变量分配四个字节的内存。在这时候,属于该指针的内存并无存储myClass对象的地址,由于myClass类尚未被定义,编译器不知道为该对象分配多少内存,该指针被初始化为NULL。当完成myClass类的定义之后,在执行语句“pClass = new myClass;”的时候,编译器知道了须要为myClass类型对象分配多大的内存。因此,开始定义一个myClass类型的对象,而且将它的地址存储在指针pClass所属的内存中。
在定义类对象指针的时候,能够分两步进行:第一步:完成类的声明,在类声明以后能够定义该类的指针,该指针还不能被初始化为有意义的值,通常指向NULL。第二布:完成类的定义,在此以后,能够定义类的对象,而且将类对象的地址赋给指针。
根据上面的规则,在一个类定义的内部,能够定义该类自身类型的指针,可是不容许定义该类自身类型的对象。举例以下:
Class Node { Public: Node * pNext;//正确,这是容许的。编译器只分配四个字节的内存。 Node objNext;//错误。类定义还没有完成,编译器不知道分配多少内存。 };
Node objNode;//正确,类定义已经完成,编译器知道应该分配多少内存。 |
在定义某个类型的指针或引用的时候,该类型能够先声明,而后在后续部分实现定义;在使用变量,函数,枚举等对象的时候,这些对象必须被提早定义。
在C++中,若是要使用一个对象实体(如:内置类型,类类型,用户定义的变量,函数,指针等),那么该对象实体在被使用以前必需要被声明或定义。编译器在编译C++程序的时候,若是在程序代码中发现一个实体名称(如上面代码中的int,a,myClass等),那么编译器就会在C++的各类做用域中查找该名称的声明或定义(由于须要知道该对象实体内存的大小,内存地址,以及内存中的值)。咱们将这一过程叫作名字解析。
这些可以被编译器查找的做用域包括:全局做用域,名字空间做用域,类域,局部做用域等。在该节主要讲述两个问题:
名字解析的过程和顺序以下图所示:
在名字解析的过程当中,名字解析的顺序是:从名字的被使用位置开始,从小做用域到大做用域的顺序进行查找,当查找到该名字的声明或定义后,名字查找动做结束。
当在两个不一样的做用域中定义相同名称的实体的时候,小做用域中定义的实体会隐藏大做用域中定义的实体。由于编译器从小做用域开始查找,找到第一个名称的定义或声明后,名字查找动做中止,因此小做用域中定义的名称会隐藏大做用域中定义的名称。
在上图中,线索1的查找顺序是:类域,名字空间做用域,全局做用域;线索2的查找顺序是:局部做用域,名字空间做用域,全局做用域;线索3的查找顺序是:局部做用域,类域,名字空间做用域,全局做用域;线索4的查找顺序是:名字空间做用域,全局做用域。
根据名字被使用的位置,能够将名字解析划分红以下的状况:
Int a = 10;//被隐藏 Namespace mySpace [ Int a = 100;//在此处找到a变量的定义,查找中止。若是这里不定义a变量,那么查找将会继续向前,直到在到全局做用域中找到a变量的定义。 a = a + 1;//在使用a的时候,开始从该位置向前查找a的定义。 a = ::a + 1//使用全局做用域中被隐藏的变量。 } |
Typedef short myData;//该类型定义被mySpace中的定义隐藏。 Namespace mySpace { Typedef int myData;//查找到此处,找到myData名称的定义,名字解析结束。 Class myClass { myClass(myData Para);//今后位置向前查找。Para的类型为int。
typedef double myData;//该位置不会被查找到。 } } |
-------------------------------------A.h-------------------------------------- Int myData = 10;//被隐藏 Namespace mySpace { Int myData = 100;//被隐藏 Class myClass { Public: Void myFunction(); int myData;//变量的名字解析在此处找到。 };
Void DealData()//函数的名字解析在此处找到 { } }
-------------------------------A.cpp-------------------------------- #include “A.h” Void myClass::myFunction() { DealData(myData);//在这里,须要进行两次名字解析。首先是函数DealData()的名字解析,由于在调用该函数的时候,在前面的全局做用域中已经完成了该函数的定义,所以名字解析成功。第二个名字解析的是变量myData,在整个类域中找到了该变量的定义。 } |