一个由C++编译的应用程序,占用的内存能够划分为以下几个部分:程序员
具体代码举例以下:数组
//main.cpp数据结构 int a = 0; // 全局初始化区函数 char *p1; // 全局未初始化区性能 main()spa {操作系统 int b; // 栈设计 char s[] = "abc"; // 变量s在栈中,文字常量”abc”在文字常量区。将从文字常量区向栈中执行初始化操做。即:将”abc”从文字常量区拷贝到栈中为数组s分配的内存区域中。指针 char *p2; // 栈对象 char *p3 = "123456"; // 123456\0在文字常量区,p3在栈上。P3指向文字常量区的地址 static int c =0; // 全局(静态)初始化区 p1 = (char *)malloc(10); //分配的10字节的区域在堆区。 p2 = (char *)malloc(20); //分配的20字节的区域在堆区。 strcpy(p1, "123456"); // 123456\0放在文字常量区,p1指向的内容在堆中。将从文字常量区向堆中执行数据拷贝。 free(p1); free(p2); } |
编译生成的二进制代码中,各个变量的名称将转换成对应的地址。对于应用程序,其设计的变量一般转换为绝对内存地址,即程序运行时这些变量实际对应的地址。做为动态连接库等共享库的代码中的变量的地址一般为相对内存地址,当动态连接库被加载时,这些相对内存地址会进行从新定位,转换为对应的内存地址。
在程序代码中出现的文字常量,在编译阶段,由编译器分配内存到文字常量区。文字常量不可寻址,不能对文字常量执行取地址操做。若是在程序代码中出现多个相同的文字常量,那么在文字常量区只保持该文字常量的一份拷贝,其余使用该文字常量的代码将今后拷贝处取值。具体代码举例以下:
Char* pStr = “lifeng”; Char arrStr[] = “lifeng”;//假设arrStr定义在局部做用域中 |
在文字常量区,只保持文字常量”lifeng”的一份拷贝。指针pStr指向文字常量区中文字常量”lifeng”的首地址;变量arrStr在栈上分配内存空间,在数组初始化的时候,从文字常量区将字符串”lifeng”拷贝到栈上为该数组分配的内存空间中。
文字常量区具备只读属性,任何对该区数据的修改操做都会引发错误。
符号常量的内存分配状况以下表所示:
|
全局做用域 |
文件做用域 |
局部做用域 |
内存分配 |
在文字常量区分配内存,该区域内存具备只读属性。任何试图更改该区数据值的操做都会引发错误。 |
不分配内存,符号常量的名称只保存在名称列表中。若是在代码中执行取符号常量地址的操做,也会引发内存分配。 |
在栈中分配内存,因为栈不具备只读属性,所以,符号常量的只读性由编译器在编译阶段保证 |
常量折叠 |
不执行常量折叠 |
执行常量折叠 |
执行常量折叠 |
内存分配的时刻 |
编译时 |
|
运行时 |
符号常量默认具备文件做用域,在编译时刻,若是其值明确,编译器将执行常量折叠,而且不会为该符号常量分配内存。若是对符号常量执行了取地址操做,或者对符号常量使用了关键字extern,那么在编译阶段,编译器将为该符号常量分配内存。
若是想让符号常量具备全局做用域,或者符号常量的值在编译时刻不可知,这时候就须要对符号常量使用关键字extern。
关键字const只是告诉编译器由它修饰的数据其内容不能更改,这个规则是在编译阶段控制的。在编译的时候,若是编译器发现有更改常量数据值的代码,那么编译器就会报错。
符号常量与指针结合之后,将会涉及到三方面的内容,它们分别是:
l 指针常量,即指针自己不可变,定义以后必须初始化;
l 指向常量的指针,即指针所指向的数据值不可变,定义以后能够不初始化;
l 指向常量的指针常量,即指针自己和指针所指向的数据值均不可变,定义以后必须初始化。
具体的示例代码以下:
//指针常量,即指针所指向的地址不可变。 Char * const pStr = “lifeng”;//能够用文字常量初始化字符类型指针 Int a = 10; Int * const pInt = &a;//pInt不能再指向其余的地址,但能够经过pInt改变a的值 //指向常量的指针,即指针所指向的数据值不可变 Const char* pStr = “lifeng”;//定义并初始化 Const char* pChar;//定义以后能够不初始化 pChar = “lifeng”;//后续赋值
Int a = 10; Const int* pInt = &a;//指向常量的指针能够存储变量的地址。不能经过pInt改变a的值。 //指向常量的指针常量,即指针自己和指针所指向的数据值均不可变 Const char * const pStr = “lifeng”; Int a = 10; Const int * const pInt = &a; |
这里有一个规则,关键字const位于星号(*)的左边表示指针所指向的数据的值不可变;关键字const位于星号(*)的右边表示指针自己不可变。
指针与常量和变量之间的关系以下图所示:
指向常量的指针便可以保存符号常量的地址,也能够保存普通变量的地址。不能经过指向常量的指针去改变它所指向的常量或变量的数据值。指向变量的指针不能存储符号常量的地址,由于经过指向变量的指针能够改变它所指向的数据的值。若是指向变量的指针指向了符号常量,那么就能够改变该符号常量的数据值,这是不容许的。
关键字const所修饰的指针的常量性是由编译器在编译阶段保证的。
从定义上来讲,引用是一个变量的别名,全部对该引用的操做都至关于对这个变量的操做。但从本质上来说,引用就是指针。可是引用是被限制了的指针,它至关于指针常量。引用一旦指向一个变量之后,就不能再次指向其余的变量。
引用是C++语法范畴的概念,提出引用的概念是为了方便程序员编写程序。从汇编语言的角度来看,在处理方式上,引用和指针没有却别。也就是说,在汇编的层面上,不存在引用的概念。具体代码以下:
-------------------------------C++代码--------------------------------- double pi = 3.14;
double& ypi = pi;
double* ppi = π --------------------------------------------汇编代码---------------------------------------- double pi = 3.14;//为浮点数据分配内存,并初始化 00A4141E fld qword ptr [__real@40091eb851eb851f (0A45800h)] 00A41424 fstp qword ptr [pi]
double& ypi = pi;//取出pi的地址,并赋给ypi 00A41427 lea eax,[pi] 00A4142A mov dword ptr [ypi],eax
double* ppi = π//取出pi的地址,并赋给ppi。从这两处代码能够看出,对指针和引用的处理方式是同样的 00A4142D lea eax,[pi] 00A41430 mov dword ptr [ppi],eax |
若是关键字const不与引用结合,那么只能采起下面的方式定义和初始化引用:
//只能采用下面的方式定义,并初始化引用 Int a = 10;//定义变量a Int& b = a;//定义a的引用。 //下面的做法是错误的 Int& b = 10;//错误 Double pi = 3.14; Int& c = pi;//错误 |
当关键子const与引用结合之后,能够采用以下的方式定义并初始化引用:
Const Int& a = 10;//使用整型文字常量初始化整型引用 Const Int& b = 3.14;//使用浮点型文字常量初始化整型引用 Double pi = 3.14; Int& c = pi;//使用浮点型变量初始化整型引用 Double&ypi = pi; |
可使用不一样类型的对象初始化Const引用,只要能从一种类型转换成另一种类型便可;也可使用不可寻址的文字常量。一样的初始化方式对于非const引用是不合法的。
可以这样作的缘由是:在编译阶段,编译器首先生成一个临时变量,而后将这些数据值,如上面代码中的10,3.14赋给这个临时变量,而后再将常量引用指向这个临时变量。当咱们定义的引用是常量引用的时候,因为不能修改被引用的对象的值,临时变量的值和实际的数据值保持一致,不会有错误产生;当咱们定义的引用是很是量引用的时候,若是也采用临时变量的方式处理,由于能够更改引用所指向的变量的值,但这时候更改的是临时变量的值,而实际的数据值没有变化。因此,非const引用不会采起临时变量的方式处理。
Const引用通常会被看成函数参数来使用,具体代码举例以下:
//使用const引用做为参数的函数 Void dealData(const int& Para); //能够有以下的调用方式 dealData(10); dealData(3.14); double pi = 3.14; dealData(pi); int a = 100; dealData(a);
//使用非const引用做为函数的参数 Void dealData(int& Para); //可用的调用方式 Int a = 10; dealData(a); |
由上面的代码能够看到,使用const修饰了引用类型的参数之后,能够采用更灵活的方式来调用该函数。
函数的参数有三种形式,分别是:类型对象(内置类型或类类型),指针,引用;函数的返回值也有三种形式,分别是:类型对象(内置类型或类类型),指针,引用。
关键字const能够修饰函数的参数,函数的返回值。若是该函数是类的成员函数,那么关键字还能够修饰整个函数,表示该函数不会修改类的数据成员。
符号常量与函数参数结合使用的时候,通常有两种状况:一种状况是修饰指针类型的函数参数,另一种状况是修饰引用类型的参数。
默认状况下,在传递参数的时候,函数采用值传递的方式。即:将实参的值复制一份到临时变量中,而后将临时变量值传递到函数中(压栈)。在函数体中,当对函数参数操做的时候,如改变参数的值,实际上操做的是临时变量的数据值,函数的实参不受影响。经过这种方式,起到了对函数实参保护的做用,即:在函数体中不能随意更改函数的实参值。
这种值传递的方式,对于C++内部数据类型,如:整型,浮点型,字符型等,是没有问题的。由于内部数据类型所占用的内存较小,在复制的时候,不会引发大的性能问题。
当函数的参数是一个类的对象,而且在这个类类型中包含了大量的成员数据的时候,若是依旧采用值传递的方式处理,那么就会引发大的性能问题。在这种状况下,咱们须要将传递的参数更改为指针或者引用。
若是函数的参数是指针,虽然依旧执行了值传递的规则(传递参数的时候,指针被复制了一份,但这两个指针都指向同一个对象),可是指针只占用4个字节,不会影响效率;若是函数的参数是引用,函数实参自身被传递到函数体中,在这个过程当中,不须要数据复制。不管是采用指针的方式仍是采用引用的方式,在函数体中均可以对指针指向的对象或引用所表明的对象的内容进行修改。
若是须要对函数的实参进行保护,即:在函数体中不能修改函数实参的内容,那么就须要在指针或引用前面使用关键字const。具体的代码举例以下:
//函数的声明 Class A;//声明类 Void dealData(const A* pA);//采用指针的方式传递参数 Void dealData(const A& objA);//采用引用的方式传递参数
//函数调用 A* pA = new A;//定义类A的指针 A objA;//定义类A的对象。
dealData(pA);//此处依旧执行了值传递,指针pA被复制一份传递到函数中。但指针所指向的内容不可修改。 dealData(objA);//用objA初始化引用参数。实参直接传递到函数中,因为使用关键字const修饰参数,引用所表明的实参不能被修改。 |
对于非内部数据类型的输入参数,为了考虑效率,通常采用指针或引用的方式传递参数值。若是在函数中不须要对实参的值进行更改,最好将关键字const与指针或引用参数结合使用。
对于内部数据类型的输入参数,采用值传递的方式处理便可。因为值传递规则的存在,关键字const与非指针或引用类型的参数结合起来是没有意义的。
另外,关键字const与引用参数结合使用之后,当在调用该函数的时候,就能够才用多种灵活的调用方式,如:直接传递文字常量,或其它的数据类型。具体状况见第六节的描述。
默认状况下,当从一个函数中返回一个数据值的时候,采用的是值传递的形式。即:在函数中生成一个临时变量,将要返回的数据值复制到该临时变量中,而后将该临时变量返回。当函数的返回值是引用类型的时候,在返回数据值的时候,该函数不产生临时变量,直接将要返回的数据值返回。
在对二目操做符重载,并产生新对象的状况下,通常用关键字const修饰返回值,而且该返回值为对象(非指针或引用)。具体代码举例以下:
Class myClass { Public: VoidmyClass(int Para1,Para2); Int m_Data1; Int m_Data2; };
ConstmyClass operator+ (constmyClass& Para1,const myClass& Para2) { Return myClass(Para1.m_Data1+Para2.m_Data1,Para1.m_Data2+Para2.m_Data2); } |
这样作的目的是为了防止以下状况的发生:
Class myClass; myClass A; myClass B; myClass C; (A*B) = C; |
除了这种状况外,通常不多用const修饰返回对象。由于一旦用const修饰了返回的对象,那么该对象就具备常量性,在该对象上只能调用常量函数。
关键字const能够修饰类的成员函数,使之称为常量成员函数。常量成员函数不能修改该类型的数据成员。应该把全部不修改数据成员的函数定义为常量成员函数。若是在常量成员函数中修改了类的数据成员,那么在编译阶段,编译器将报错。
关键字const能够修饰类对象,类指针,类引用,使这些类对象,类指针,类引用具备常量性。不能修改具备常量性的类对象,类指针所指向的类对象,以及类引用所关联的类对象的数据成员,而且只能使用这些类对象,类指针,类引用调用该类型的常量成员函数,不能调用很是量成员函数。普通的类对象,类指针,类引用能够调用全部的该类型的成员函数,包括常量成员函数。它们的关系以下图所示:
因为不能经过具备常量性的类对象,类指针,类引用修改类的数据成员,因此它们只能调用不修改类数据成员的常量成员函数。而普通的类对象,指针,引用没有这个限制,因此它们能够调用全部的类成员函数。具体的代码举例以下:
Class myClass { Public: Void Func(int Para);//普通函数成员 Void Func(int Para) const;//重载Func,常量函数成员。关键字const能够实现函数重载 Int GetData() const;//常量函数成员.在函数后面加关键字const,表示该函数不修改类的数据成员 Void DealData(int Para);//普通函数成员 };
MyClass objClass;//定义类对象 Const myClass objConstClass;//定义类的常量对象 objClass.Func(100);//正确。普通对象调用普通的成员函数。 objClass.DealData(50);//正确 objClass.GetData();//正确,普通类对象能够调用常量函数 objConstClass.Func(200);//正确,常量对象调用常量成员函数 objConstClass.GetData();//正确。 objConstClass.DealData(50);//错误,常量对象不能调用很是量函数
|
关键字const能够实现函数的重载。在函数调用的时候,普通类对象调用普通的成员函数(Void Func(int Para);),常量对象调用常量成员函数(Void Func(int Para) const;)。