原创 C++作用域 (二)
2.3全局作用域
2.3.1概述
全局作用域是最大的名字空间作用域,不同于用户自定义的名字空间作用域,全局作用域不需要显示地定义,它天然存在于C++程序中。全局作用域是一个最外层的容器,是所有作用域的父作用域。在全局作用域中,可以定义其他的名字空间,类型,函数,变量,模版等。
在全局作用域中定义的函数是全局函数,在全局作用域中定义的变量是全局对象。全局函数和全局对象在整个全局作用域及其子作用域中有效,它们的生命周期贯穿于整个程序的运行。从定义它们开始直到整个程序运行结束。
2.3.2一次定义规则
2.3.2.1声明和定义
变量可以被声明多次,但只能被定义一次。声明和定义是两个不同的概念。
在变量定义的时候,除了向程序表明变量的类型和名称外,还要为变量分配存储空间以及进行初始化操作。在定义全局变量的时候,如果没有为变量赋初值,那么系统将执行默认的初始化工作,如:整型变量赋值0,字符串变量赋空值等。全局变量的值存储在程序的全局存储区,在全局存储区中还存储着常量的值,以及静态变量的值。
声明操作是定义操作的子集,在声明变量的时候,仅仅向程序表明变量的类型和名称,它不会为变量分配存储空间,因此也不会有初始化工作。
全局变量和全局函数的声明和定义的格式如下:
//声明 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关键字,也会被认为是变量的定义,而不是变量的声明。
声明和定义的关系是:定义也是声明,定义包含了声明,但声明仅仅是定义的一个子步骤。有的时候,声明和定义可以合在一起进行,如局部变量的声明和定义,而在某种情况下,声明和定义是需要分开进行的。当一个变量需要在多个文件中被使用的时候,就需要将声明和定义分开(一般为全局变量);否则使用一个定义语句同时完成变量的声明和定义即可(一般为局部变量)。
2.3.2.2合理的文件组织形式
一个变量可以被声明多次,但它只能被定义一次。在这里存在一个法则,一次定义法则:全局对象和全局函数或者只有一次定义,或者在一个程序中有多个完全相同的定义(内两函数和全局常量的情况)。
为了遵守一次定义法则,需要合理的文件组织形式。在一般情况下,全局作用域中定义的全局变量和全局函数都需要在多个文件中被使用。在这种情况下,最合理的文件组织形式就是:“头文件+cpp文件”的形式。具体的过程描述如下:
- 在头文件中声明全局变量和全局函数;
- 在头文件中定义内联函数,以及在编译时可以确定值的常量;
- 在源文件(.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” --以下是具体使用。 |
2.3.3全局作用域的成员
2.3.3.1普通变量
在C++中,编译一个程序可以划分为如下的阶段:
- 预处理阶段,处理#include命令。
- 编译阶段,
- 链接阶段。
如果在一个源文件中使用了“#Include”命令,将一个头文件引入到该源文件中。那么在编译器执行编译之前,会首先执行该预处理命令。即:将头文件中所包含的代码全部合并的目标源文件中,合并后的文件将作为一个文件存在。也就是说,编译器在执行编译的时候,只会看到源文件,头文件在编译阶段是不可见的。
那么,假设有如下的实现方式:在头文件A.h中,实现了一个变量的定义(不是声明),如:“int a = 10;”。每当该头文件被其他源文件引用一次以后(#include “A.h”),那么编译器就会认为对该变量执行了一次定义。如果该头文件被引用两次以上,就会发生重定义错误。这是违反一次定义法则的。
基于以上原因,对于普通的全局变量,正确的使用方法是:首先在头文件中使用extern关键字实现该全局变量的声明;然后在源文件中引入该头文件,并且实现该全局变量的定义;最后,在使用该全局变量的源文件中引入声明了该全局变量的头文件,然后使用之。
对于普通的全局变量,只能使用此方法处理。
2.3.3.2 Const常量
在全局作用域中定义的常量只具有文件作用域。也就是说,在全局作用域中定义的常量只在定义它的文件内有效,它不会影响到其他文件中定义的同名常量。举例如下:
--------------------------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。
2.3.3.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打破全局静态变量的文件作用域。
2.3.3.4内联函数
在编译阶段,编译器会将内联函数在调用点展开,将函数体中的代码合并到调用点。而不是在运行阶段执行压栈,出栈方式的函数调用。在编译器展开内联函数的时候,在当前文件中,内联函数的定义必须是可见的。因此,内联函数必须定义在头文件中,当其他的源文件引入了该头文件后,就相当于该内联函数的定义在该源文件中是可见的。根据一次定义法则,内联函数可以多次定义,只要保证多次定义的形式是相同的即可。
2.4类域
2.4.1类定义的组成方式
使用头文件和源文件结合的方式完成一个类的定义。类的定义,内联函数的定义,静态成员变量的声明实现在头文件中;类成员函数的定义,静态成员变量的定义实现在源文件中。在实现源文件的时候,必须引入定义该类的头文件。具体的示例代码如下:
---------------------------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.2类域的组成部分
每定义一个类就会引入一个类域,不同的类具有不同的类域。每一个类成员,包括成员变量和成员函数都属于该类域。在类域内部,可以直接使用成员名称访问类成员;在类域外部,必须通过成员访问操作符或域解析操作符访问类成员。类域由如下三部分组成:
- 在类定义的头文件中,类体部分属于该类的类域。具体范围是:从正花括号“{”开始,到反花括号“}”结束;
- 在成员函数定义的源文件中,成员函数修饰限定名称之后的部分属于类域。见2.4.1节红色部分;
- 在静态成员变量定义的源文件中,静态变量修饰限定名称之后的部分属于类域。见2.4.1节红色部分。
在源文件中,成员函数修饰限定名称或者静态变量修饰限定名称之前的部分不属于类域。如果要在这部分引用类域中的名称,必须使用修饰限定名。见2.4.1节的绿色部分。
在源文件中,属于类域中的成员名称可以被直接使用,不需要修饰限定。见2.4.1节红色部分。
2.4.3类成员的访问方式
当需要在某个程序文本文件中使用某个类的时候,需要在该文件中引入实现该类定义的头文件。在使用类成员的时候,有两种形式,分别是:在类域外部使用类的某个成员和在类域内部使用类的某个成员。
情况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部分关于内联函数的示例。
2.5局部作用域
在局部作用域中定义的变量是局部变量,局部变量又可以进一步划分为:自动变量,寄存器变量,以及静态局部变量。在C++程序中,除了局部变量外,还存在着其他类型的变量,如:全局变量,常量,静态变量,以及使用new操作符定义的变量,这些变量在内存中的分布情况如下图所示:
当启动一个应用程序的时候,就会启动一个进程。在32的系统中,为该进程分配4G的内存空间。一个进程下面,又会根据需要启动若干个线程,其中一个线程是主线程。在这4G的内存空间中,包含如下类型的存储区域:
- 全局存储区。在该区域存储全局变量,静态变量,和常量。初始化的全局变量,静态变量存储在全局存储区的一个部位;未初始化的全局变量,静态变量存储在全局存储区的另外部分;堆的声明周期贯穿于整个程序的声明周期。
- 堆。一般情况下,一个进程对应一个堆。在堆中存储使用new操作符定义的变量;
- 栈。一个线程对应一个栈。线程启动,分配栈空间,线程结束,栈空间被收回。在栈中存储着局部变量。这些局部变量包括函数的参数,在函数内部定义的变量。随着函数调用完毕,这些变量被释放。
3名字解析
无论是变量,函数,枚举,指针,引用还是类型(包括内置类型或类类型),在使用之前,它们必须被声明或者定义。举例如下:
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;//该位置不会被查找到。 } } |
- 在类成员定义中进行名字解析。被使用的名字出现在类成员定义的cpp文件中,一般从局部作用域开始。被使用的名字可能会出线在函数的形式参数部分,或者是函数体内部。名字解析的顺序描述如下:
- 在局部作用域中查找。从该名字被使用的位置开始,查找该名字被使用之前的局部作用域部分,不会查找该名字被使用之后的局部作用域部分。
- 在类域中查找。在整个类域中查找,不分前后。这一点与在名字空间作用域中查找有所不同。
- 在名字空间作用域,以及名字空间作用域的父作用域,直到全局作用域中查找。规则同2中的描述。
-------------------------------------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,在整个类域中找到了该变量的定义。 } |
原创 C++作用域 (二)的更多相关文章
- 原创 C++作用域 (一)
1概述 在所有的计算机程序中,一个基本的目标是操作一些数据,然后获得一些结果.为了操作这些数据,需要为这些数据分配一段内存,我们可以将这段内存称为变量.为了方便操作,以及程序可读性方面的考虑,需要使用 ...
- 【原创】MHA二次检测功能测试
MHA提供了很多扩展的功能,其中有一个参数是secondary_check_script,这个参数可以使我们自定义扩展多路由,多链路的二次检测功能.减少网络故障切换,降低脑裂的发生. 在虚拟机上做了如 ...
- 【原创】美团二面:聊聊你对 Kafka Consumer 的架构设计
在上一篇中我们详细聊了关于 Kafka Producer 内部的底层原理设计思想和细节, 本篇我们主要来聊聊 Kafka Consumer 即消费者的内部底层原理设计思想. 1.Consumer之总体 ...
- 【原创】项目二Lampiao
实战流程 1,nmap扫描C段 ┌──(root㉿heiyu)-[/home/whoami] └─# nmap -sP 192.168.186.0/24 Starting Nmap 7.92 ( ht ...
- MySQL基础原创笔记(二)
表索引关键字:PRI primary key 表示主键,唯一 写法: id bigint(20) unsigned primary key not null ,uni UNIQUE 表示唯一 id b ...
- 【原创】(二)Linux物理内存初始化
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【原创】(二)Linux进程调度器-CPU负载
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- seo伪原创技术原理分析,php实现伪原创示例
seo伪原创技术原理分析,php实现伪原创示例 现在seo伪原创一般采用分词引擎以及动态同义词库,模拟百度(baidu),谷歌(google)等中文切词进行伪原创,生成后的伪原创文章更准确更贴近百度和 ...
- JavaScript 词法、静态、动态作用域初级理解
开始之前 由于本人也是JavaScript初学者,记录学习经过,怕以后会忘记. 对于JavaScript 初学者来说,最难的不是代码部分,而是对很多书籍中的术语的理解,大多时候想要理解一段JavaSc ...
随机推荐
- SQL Server数据库损坏、检测以及简单的修复办法
简介 在一个理想的世界中,不会存在任何数据库的损坏,就像我们不会将一些严重意外情况列入我们生活中的日常一样,而一旦这类事情发生,一定会对我们的生活造成非常显著的影响,在SQL Server中也 ...
- Thinking in Unity3D:基于物理着色(PBS)的材质系统
关于<Thinking in Unity3D> 笔者在研究和使用Unity3D的过程中,获得了一些Unity3D方面的信息,同时也感叹Unity3D设计之精妙.不得不说,笔者最近几年的引擎 ...
- 使用自定义 classloader 的正确姿势
详细的原理就不多说了,网上一大把, 但是, 看了很多很多, 即使看了jdk 源码, 说了罗里吧嗦, 还是不很明白: 到底如何正确自定义ClassLoader, 需要注意什么 ExtClassLoade ...
- SI与EMI(一) - 反射是怎样影响EMI
Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...
- Atitti 载入类的几种方法 Class.forName ClassLoader.loadClass 直接new
Atitti 载入类的几种方法 Class.forName ClassLoader.loadClass 直接new 1.1. 载入类的几种方法 Class.forName ClassLo ...
- PHP 高级编程(1/5) - 编码规范及文档编写
PHP 高级程序设计学习笔记20140612 软件开发中的一个重要环节就是文档编写.他可以帮助未来的程序维护人员和使用者理解你在开发时的思路.也便于日后重新查看代码时不至于无从下手.文档还有一个重要的 ...
- JavaScript权威设计--JavaScript类型,值,变量(简要学习笔记三)
1.负号是一元求反运算 如果直接给数字直接量前面添加负号可以得到他们的负值 2.JavaScript中的运算超出了最大能表示的值不会报错,会显示Infinity. 超出最小也不报错,会显示-I ...
- 防御性编程习惯:求出链表中倒数第 m 个结点的值及其思想的总结
防御性编程习惯 程序员在编写代码的时候,预料有可能出现问题的地方或者点,然后为这些隐患提前制定预防方案或者措施,比如数据库发生异常之后的回滚,打开某些资源之前,判断图片是否存在,网络断开之后的重连次数 ...
- Http协议相关内容
http协议概述 HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义浏览器与WEB服务器之间交换数据的过程. 客户 ...
- CatchPacket网络抓包软件
CatchPacket网络抓包软件 qq 22945088431.技术特点:基于WinPcap库,c# winform2.实现获取机器所有网卡,可任意选择监听3.可以捕获常见网络协议arp dns ...