一、什么是引用
引用是C++中的一个新的语言特性,从表面来说,引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明如下:
类型标识符 &引用名 = 目标变量名;
这里面的&不是位运算、不是取地址,而是一个引用的标识符,可以认为是一个“类型标识符 &
”类型,类型标识符是指目标变量的类型。如:
int a = 10;
int &b = a;
b = 5;
std::cout << "the value of a is:" << a << std::endl;
其中符号b就是引用a,是int &
类型的,表示一切对b的操作都是对a的直接操作,所以打印的结果是5。关于引用需要注意以下几点:
- 声明引用时,必须同时对其进行初始化成某个变量
- 引用一旦声明,不可以更改,不能再作为其它变量的引用
- 只能引用变量,不能引用常量和表达式
- C++标准并没有规定引用是否占用内存空间,很多书和博客认为引用本身不占存储单元,系统也不给引用分配存储单元。个人觉得这是以偏概全,是错误的,因为引用怎么实现还是得依赖于编译器的,而是否占空间也要视情况而定。
二、引用的本质
前面我们说到,引用的具体实现得看编译器,如果我们在结构体类型中定义一个引用,然后测试大小:
#include <iostream>
typedef struct mystruct1
{
char *a;
char b;
char &c = b;
}MS1;
int main(void)
{
mystruct1 test1;
test1.b = 'a';
test1.c = 'b';
std::cout << "test1.b = " << test1.b << std::endl;
std::cout << "sizeof(MS1) = " << sizeof(MS1) << std::endl;
return 0;
}
测试结果如下
我们看到,如果用64位的g++
编译,结构体的大小为24,我们知道char *
是占8字节的,如果引用不占内存空间的话,那么结构体大小应该为16。在IDE
中,IDE
也给出了“小警告”:"padding struct 'mystruct1' with 7 bytes to align 'c'":
这是说结构体会填充7字节去对齐c。更加说明了结构体中引用是占内存的,而且在64位环境下是8字节(如果是4字节/2字节,结构体只需填充3字节去对齐c,最后的大小为16)
那么,g++
是怎么实现引用的呢?我们先来看一段测试代码,里面是两个函数:
void func1(int *a)
{
int &b = *a;
}
void func2(int *a)
{
int * const b = a;
}
我们对其进行编译,然后把目标文件进行反汇编,结果如下:
是不是很吃惊??!两个函数编译之后进行反汇编得到的结果是一样的!也就是说,在g++
中将引用实现为一种类型int &
,本质上就是int * const
类型,也就是说这是一个弱化了的指针,所以是占内存空间的!我们知道对于int * const p = &a
来说,指针p指向的地址是不可变的,但是该地址的内容是可变的;这和引用一旦声明,不可以更改,不能再作为其它变量的引用是一致的。
三、引用的使用
引用主要用在函数传参和返回值中,在一定范围内用来代替指针
3.1 const与引用
int a = 10;
const int &b = a;
如上述代码段,我们在定义引用的时候在前面加const
关键字,称为“常引用”。这时b的类型是const int &
,其值是无法修改的,等价于const int * cont
,也就是说不能通过常引用去修改其引用的值,这个一般是在函数传参时使用,防止错误操作
int mul(const int &a)
{
return a * 10;
}
3.2 引用的典型案例————swap函数
如果我们需要一个函数交换a和b的值,在C语言中是这样做的
void swap(int *pa, int *pb)
{
*pa ^= *pb;
*pb ^= *pa;
*pa ^= *pb;
}
swap(&a, &b);
由于指针过于强大,如果程序员无法控制,就会引发一系列不可预知的错误。在C++中,我们可以通过引用来实现对指针的局部替换
void swap(int &ra, int &rb)
{
ra ^= rb;
rb ^= ra;
ra ^= rb;
}
swap(a, b);