C语言中我们知道解引用操作符是直接去寻找指针所指的地址里面的内容,但是在C++中引用的含义是:给已存在变量取一个别名,但是编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
举例
这&只有在类型和变量的中间才叫做引用,这就是引用变量名=引用实体。此时就相当于将a复制了一份,而这复制的一份又有一个名字就叫做b,所以此时b发生改变的话就会带动着a的改变。注意:引用类型必须和引用实体是同一种类型才行。
引用注意事项
引用在定义时必须初始化
而这个初始化也是有要求的,并不是一般的初始化,必须初始化赋一个变量,常量可不行。
int main()
{
int i = 0;
double& j = i;
return 0;
}
这其实也是不可以的,i是整型,赋值给浮点型实际上是创建了一个double型的临时变量,先将i的值赋给临时变量,再将临时变量给j,但是临时变量具有常性,所以不可以直接赋值,相当于权限扩大,所以这样就可以了
int i = 0; const double& j = i;
一个变量可以有多个引用
可以有多个变量指向同一个内存数据。
引用一旦引用一个实体,再不能引用其他实体
类似于这种就相当于重定义一样,不可行。
引用占用空间吗
原理上引用是不占用空间的,但实际在内存空间上,引用本身也占用一块内存,里面存储着所引用的变量的地址,大小与指针相同,字面上也表现为unsigned long int型。 只是经过编译器处理后,访问这块内存时将直接转而访问其指向的内存。 因此在程序中无法读取到这块内存本身。 这可以理解为“编译器的把戏”或“程序的谎言”,但这一机制不是为了捉弄程序员,而是为了真正实现别名的效果。 综上:引用的实现实际上是占用内存空间的,但程序把它按照不占用内存空间来处理。
引用的使用
做参数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
一般传引用传参会比一般传参的效率更高,而且使用起来更简便。
做返回值
int Count()
{
int n = 0;
n++;
return n;
}
这里我们要先了解函数栈帧,进入Count函数时会创建栈帧,并且创建了变量n,但是出了函数作用域,栈帧就会销毁,函数中创建的(局部)变量n也就随之销毁,此时就会生成一个临时变量(具有常性防止意外更改),如eax(32位机器时)寄存器充当,或者其他临时栈帧空间的方式充当。而临时变量其实本质就是延长数据的生命周期,不会受到函数退出销毁的影响,所以在调用函数获取返回值时本质就是在访问寄存器或栈中存放的数据。
int& Count()
{
int n = 0;
n++;
return n;
}
这里就运用到了引用,相当于返回了n的别名,但是很不好,因为n出栈空间销毁(本质就似乎使用权释放,其中的数据不一定会被清空),如果继续返回n的别名(还是同一块空间),所以就相当于是再次访问这块空间里数据内容,如果不在,那么就会返回一个随机值(取决于该快空间的内容),在的话就会相当于是返回原值的效果。
int& Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int& ret = Count();
return 0;
}
这里我们已经知道返回的是n的别名,而接受返回值有用到了引用,此时ret也是n的别名,访问的是与n同一块空间,而上一个例子是又创建了一个变量来接受别名n的值,并不是与n同一块空间。但这个不一样,这个接收值ret就是与n同一块空间,而且地址都一样。
而结果就取决于原来创建栈帧时n的空间里还是不是原来的值(n这块空间有没有被覆盖)
int& Add(int x,int y)
{
int sum = x + y;
return sum;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "ret = " << ret << endl;
return 0;
}
这里就又变了一点点,返回sum的别名,ret也是sum的别名,指向的是栈帧中创建的sum这块空间,再次调用Add函数,创建同一块栈帧,就又创建了sum变量,而这是的sum变量和原来第一次调用Add函数时创建的sum变量地址是一样的,此时就将同地址下的值改成了7,如果原创建栈帧时n的空间里的值是否销毁:
切记可不要经常将引用做返回值,这样十分有风险,因为你也不能确定返回值是随机值还是你想要的那个值。
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
所以一般在静态区和堆区的变量(全局变量,static修饰的静态变量,动态开辟的变量)我们会用到引用返回提高效率。