浅谈引用类型
编译器是怎么实现引用类型的呢?
预备知识1:使用const定义常量
使用const可以采用类似定义变量的方法来定义常量,在定义的时候必须初始化以指明常量值。比如 const int a = 1; 。
那么编译器会给const定义的常量分配内存空间吗?如果分配了内存空间,那么每次使用这个常量都要访问这个地址,空间效率暂且不论,时间效率不也被大大浪费?在这里可能一开始大家都会有这样的疑问。特别是单片机编程出身的我,在那个一无所知的时候觉得浪费效率简直浑身难受(单片机的计算能力非常有限)。
先说是否给const常量分配空间,答案是会。可以对一个const常量取地址,因此无疑const常量在内存中是有一席之地的。
再说时间效率,实际上编译器都会采用常量折叠技术来优化代码。具体说就是像宏替换一样把常量替换成立即数。但与宏替换不同的是,这个是在编译阶段完成的。这样凡是用到const常量的时候,都不需要访问内存去取出常量值,而是直接用立即数(这个数是直接写在机器指令里的)。这样的时间效率和宏替换相当。尽可能多的使用const关键字吧,这样可以大大减少bug数量。
//C++语句与对应的反汇编,可以看到给a分配了空间并初始化为1 //但是在之后用到a的地方使用了立即数1
//如果看不懂反汇编也没关系,代码中要表达的在上文中已经全陈述过了
const int a = 1; 00C516EE mov dword ptr [a],1 int b; b = fun(a); 00C516F5 push 1
预备知识2:指针
一般说的“指针”是指“指针变量”。但是指针有时候也被理解成地址的同义词,所以指针变量不过是指“一个变量,里面存储的是一个指针/地址”。这里想说的是常量指针和指针常量。
常量指针是说“一个指向常量的指针”(也不一样指向一个常量,但*ptr被视为一个常量),本质上是一个指针变量。定义的方法是 const int * ptr ; 。
指针常量是说“一个保存了指针/地址的常量”,所以指针常量本质上是一个常量,定义的方法是 int * const ptr = &a;。就像一个const常量在定义时必须被初始化一样,常量指针也必须在定义时初始化,也就是说明自己是哪一个地址。
上一小节说明了什么叫常量折叠,和const常量是如何被替换成立即数的。那么指针常量当然也会被这样替换成立即数,只不过这个立即数表示一个地址,比如等于1480091972什么的。
预备知识3:变量的三个属性
变量的最基本的属性基本上有三种:
(1)名字(必须显示说明)
(2)类型 (必须显示说明)
(3)存储类别 (缺省方式或显示说明(使用:auto、register、static、extern))
作用域和生存期(作用域和生存期被认为是另两种属性,但这两个属性不像前三种基本)实际上由存储类别和定义变量的位置决定,所以变量最基本的属性就是上面三种。当看到一段C++源代码的某一个变量的时候,合格的程序员应该能说明这个变量的三种属性是什么,比如一个全局变量是int类型,名字是a。
可是这些属性只对程序员和编译器有意义,机器只懂机器码不懂C++。也就是说编译器在编译源代码的时候需要这些信息,这些信息也在源代码中有体现,所以编译器也可以得到这些信息。编译器根据这些信息进行编译,但是编译之后这些信息就都丢失了,不保证再能从机器码里得到这些信息。所以名字对编译器是重要的信息,但编译后一块内存不再需要有名字,因为内存只需要编号就够了。名字只对程序员和编译器重要。
引用
经典的解释什么是引用的说法是:引用是变量的别名。确实是的,这句话完美到无懈可击,如果说有什么瑕疵那就是这句话不太好理解。什么叫别名?那么为什么要给变量多起一个名字呢?特别是在形参类型是引用时,如何能把一个“名字”作为参数呢。前面说了名字只是变量的属性之一,如果只是一个名字的话那连变量都不是啊,什么叫引用呢?
我先接触的是C语言,对于指针并没有太多困惑,但是C++的引用着实让我困惑了一阵。看看引用的使用场景:
1 int a = 1; 2 int &b = a; 3 b = 2; 4 cout << a << endl; //输出2
上面的代码仿佛可以解释什么叫“引用是变量的别名”。b是a的别名,就是对b的操作和对a的操作一样, b = 2; 就可以理解成 a = 2; 。仿佛没什么难以理解。但是引用的应用价值在于作为形参的引用类型和返回引用类型。
在C语言中参数传递是传值的,想要实现对实参的修改需要借助指针,一个典型的swap函数的实现如下:
1 void swap(int*a,int *b){ 2 int temp = *a; 3 *a = *b; 4 *b = temp; 5 }
交换两个变量a和b,需要传入a和b的地址 swap(&a,&b); 。想要交换两个盒子里的苹果,必须先找到这两个盒子,所以变量的地址在这个算法中是必不可少的。只懂C语言的我认为这自然而然:想要修改实参的值就必须传入地址。可是到接触了c++就发现实现同样的功能可以借助引用,而且要简单得多。既然“变量的地址在这个算法中是必不可少的”,那么引用到底是怎么实现传入地址的?
实际上引用可以看成指针常量。对const常量有定义时必须初始化的要求,同样对引用也有这样的要求:定义引用时必须指明是谁的引用(作为形参的引用类型是在传入实参的时候创建并初始化的)。把引用看成指针常量就可以解释传入引用为什么和传入指针一样的作用了。
什么是别名呢,就是地址或者叫指针常量。