一.const限定符
首先我们我们先来看一个例子:
for (int i = 0; i < 10; ++i)
{
/* code */
}
上面的这个10在这里循环里面代表的是什么意思呢?这个循环在做什么事情呢?也就是说10这个数字的作用何在?在这里,我们将这种不能表示任何意义的数字(比如这里的10)称之为魔数(magic number)。magic number在程序维护,程序理解上面都是一块堵路石。假如我们在程序里面要把10这个数字更改为20,要是只有单单只有该地方用到了magic number 10,那么改起来是比较容易的,那要是有一百处都写了10这个magic number呢?那岂不是要更改一百次?要是有些代码段的10是用于其他目的呢?一不小心改错了,程序就有可能出错。这样又难于理解又难于维护的程序对程序猿也算是种折磨。那有没有比较好的方法呢?答案是肯定的。
我们下面来看一下这段代码:
int eventNumber = 10;
for (int i = 0; i < eventNumber; ++i)
{
/* code */
}
上面这段程序看起来就没有那么的费劲了,i<eventNumber;在这里用eventNumber代替了前面的10,这使得程序更容易理解,而且维护起来也更加方便,要是有一百处用了eventNumber,那么不用每一次都去更改,只用修改eventNumber的值就可以了,岂不是更容易维护。
但是上面的代码存在一个问题,要是在程序运行中不小心更改了eventNumber的值,但是我们是不希望eventNumber更改的,那有什么办法可以避免这种情况呢?那就轮到const限定符上场了,将eventNumber定义为const int eventNumber =10;那么就避免了不小心修改了eventNumber的值了,下面我们来了解一下const限定符的一些特性:
1.定义const对象
const限定符所修饰定义的对象在定义以后就不能再赋值(除非const_cast来解除限定),所以const限定符在定义的时候就需要初始化!
比如:
const std::string str = "Hello";//ok
const int i , j=0;//error,i没有进行初始化,一个变量没有进行初始化,那么这个在没有进行赋值之前事没有任何意义的
2.const对象默认为文件的局部对象
在全局作用于中定义非const全局变量,那么这个程序默认是extern类型的,表明整个程序都可以访问:
//file1.cc
int a =10;
//file2.cc
extern int a ;
但是对于const对象就不一样了,如果没有extern限定符显式的修饰说明,那么在全局作用域声明的const变量是在兑现的文件的局部变量,不可被其他文件访问:
//file1.cc
const int a =10;//作用域限定于file1.cc里面 就像普通变量的static修饰符一样
//file2.cc
extern const int a ;//error,连接器不能连接file1.cc里面的const变量
如果想要file2.cc能够对file1.cc里面的a变量具有访问权限,那么就要显式的为const变量加上ertern,下面的程序就可以正常运行:
//file1.cc
extern const int a =10;//ok
//file2.cc
extern const int a;//ok
这里有一点就是需要注意的:
在file2.cc里面不要写成extern int a 这种形式,我在g++上面测试,虽然不加const限定符也可以编译通过,但是!一旦对变量a进行任何修改的话,马上就会段错误 (核心已转储),所以还是在加上const限定符的好!
二.引用
在传统的C语言里面是没有引用这种说法的,如果想要访问操作一块内存块,要么用这块内存块绑定的那个唯一的变量名,要么使用指向这块地址的指针来进行操作,但是C++里面就多了一种新的方法来访问操作一块内存块,那就是引用,上面我们说过C语言里面一个内存快只有一个变量名绑定,但是在C++里面就可以用多个变量名来绑定同一块内存,也就是变量的另外一个名字
int val = 1024;
int & refVal =iVal;
refVal就是指向val的引用!
下面介绍引用的一些特性:
1.引用只是别名
引用只是对象(内存中有类型的区域)的一个别名,作用在引用上的所有操作事实上都是操作在引用绑定的对象上,比如下面的例子:
int a =10;
int & refA1 = a;
int & refA2 = a;
初始化是指明引用指向那个对象的唯一方法!
2.const引用
const引用是指向const对象的引用。
const int val = 100;
const int & refVal = val;//ok
int & refVal = val;//error,val是一个const对象,却用一个非const引用去绑定这个对象,那么就有可能通过该引用修改这个const对象,所以这是错误的!
下面我们看两个例子:
例子1:
float number = 10;
int & intNumber = number;
编译结果为:
error: invalid initialization of reference of type ‘int&’ from expression of type ‘float’
///
但是如果是这样的呢?
float number = 10;
const int & intNumber = number;
编译顺利通过。
例子2:
int & number = 10;
编译结果:
invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
const int & number = 10;
编译通过。
那么为什么const引用和非const引用之间会存在如此的差别呢?我们观察下面的例子:
double dval = 3.14;
const int & ri = dval;
编译器将会将上面的代码转为:
int temp = 3.14;
const int & ri = 3.14;
因此每次在创建一个const引用时都会创建一个新的临时变量(新的对象),将const对引用绑定到新创建的这个temp对象上。那到底是不是这样的呢?
我们通过代码说话
code1:
int iNumber = 10;
int & refINumber = iNumber;
printf("iNumber address:%p\nrefINumber address:%p\n",&iNumber , &refINumber);
运行结果为:
iNumber address:0xbfe22718
refINumber address:0xbfe22718
可知iNumber和refINumber都指向同一个对象!
/
code2:
int iNumber =10;
const int & refINumber = iNumber;
printf("iNumber address:%p\nrefINumber address:%p\n",&iNumber , &refINumber);
运行结果为:
iNumber address:0xbf8640f8
refINumber address:0xbf8640f8
发现地址并无什么变化。
code3:
int iNumber =10;
const float & refINumber = iNumber;
printf("iNumber address:%p\nrefINumber address:%p\n",&iNumber , &refINumber);
运行结果为:
iNumber address:0xbfd2fd24
refINumber address:0xbfd2fd28
发现现在的地址已经发生变化了,说明新创建了一个对象!
///
所以const对象并不是每种情况下都创建一个临时的变量,只有在引用类型和对象类型不同时或者直接初始化为一个常量才会新创建临时对象!const引用重要特性:非const引用只能绑定到与该引用同类型的对象。const引用则可以绑定到不同当类型相关的对象或者绑定到右值!