“&”这个符号在c++中有三种用途:
- “&”作为双目运算符是位与,比如说0&1==>0000&0001==0000
- 取地址符,比如说:
int a=10;
int *p=&a;//此时“&”是取地址符
- 引用,比如说:char &x=a;此时“&”代表的是引用,表示x是a的别名,相当于给a起了个外号就叫做x。
- 引用有以下特点:
没有空引用,即int &c;这样是不允许的。也就是说定义引用时必须给予初始化。
引用不能中途被修改,就比如说x是a的一个别名,在a的生存期内x永远都只能是a的别名,而不能称为b或c或者是其它变量的名字。
接着我们分析一段程序:
void fun(int &x)
{
int *p=&x;
x=100;
}
int main()
{
int a=10;
int b=20;
int &c=a;
int *p=&c;
fun(a);
fun(c);
return 0;
}
引用在底层处理的时候当成一个指针来处理,下面红色框框画出来的是编译器编译之后对程序员的代码进行改写的部分:

更通俗易懂地来说,引用是将指针做了一个封装,我们也说引用就是指针的一个语法糖,用起来要比指针简单好多。比如说我们在写代码时经常需要用到的交换函数:
void Swap1(int *a,int *b)
{
int tmp=*a;
*a=*b;
*b=tmp;
}
void Swap2(int &a,int&b)
{
int tmp=a;
a=b;
b=tmp;
}
从这两个函数的写法来看,很明显,引用如果作为形参要比指针看着舒服,感觉好写一些,因为指针还需要判空,如果传进去的参数是两个空指针程序就会崩溃。
从语法层面来说,指针存放的是一个地址,需要解引用才能访问它所指向的内容;而引用是某个变量的别名,通过改变引用就是改变这个变量,不需要解引用。从代码层面来说,引用就是一个常性指针。
但是,引用不适合做函数的返回值,我们来看下面这种情况,首先给出一段代码:
class Object
{
private:
int value;
public:
Object(int x=0):value(x)
{
std::cout<<"create obj:"<<this<<" "<<value<<std::endl;
}
~Object()
{
std::cout<<"destroy obj:"<<this<<" "<<value<<std::endl;
}
int Getvalue()
{
return value;
}
};
Object& fun()//以引用作为函数的返回值
{
Object x(10);
return x;
}
int main()
{
Object a;
a=fun();
return 0;
}
首先底层编译器对这段代码进行改写,由于一个对象的生存期只在函数内部有效,所以当函数被调用完成,它占有的相应的内存也要被系统回收,如果此时再去访问它原来内存里面的数据时,就会访问不到,用如下图来详细解释这个问题:

根据上面的这种情况我们不难分析出:当一个变量或者一个对象的生存期不受函数影响时,我们就可以用引用作为一个函数的返回值来使用了。像我们知道的全局变量、用static关键字修饰的变量等,它们的生存周期不受函数影响。
下面我们了解一下常引用,首先来看几行代码:
int main()
{
const int a=10;
int &b=a;
b=100;
return 0;
}
通过我们以前对指针的了解以及上面对引用的介绍我们可以很容易分析出来这几行代码是有问题的,由于引用在底层是一个指针,对引用的改变相当于对指针所指向的数据的修改,但是当我们把一个变量拿const这个关键字来修饰时,代表这个变量不允许被修改,因此,我们在给变量加引用的时候要将它设置成一个常引用。我们应将代码做如下修改:
int main()
{
const int a=10;
const int &b=a;//或者int const &b=a;
//const int * const b=&a;
return 0;
}
下面是两种情况的运行结果:


除此之外,常引用还可以引用字面常量,而普通引用不可以引用字面常量,举例如下:
int &y=10;//error
const int &x=100;//true
//1. int tmp=10;
//2.const int &x=tmp;
实际上常引用在引用字面常量时,编译器在底层做了两个工作:首先,定义一个临时变量,将变量的值赋给这个临时量,其次,常引用引用了这个临时变量的空间。
下面是对引用,常引用,将亡值以及右值引用的介绍:
int fun()
{
int x = 100;
return x;
}
int main()
{
int a = fun();//true
int& b = fun();//error,由于fun()的返回值是一个将亡值,引用不能引用将亡值
const int& c = fun();//true,常引用相当于一个万能引用可以引用一个将亡值
//int tmp=fun();
//int &c=tmp;
int&& d = fun();//true,右值引用可以引用将亡值,也可以引用常量
}
接着我们再来分析一段程序:
int& fun()
{
int x = 100;
return x;
//由于该函数是以引用作为返回值的,因此当函数结束时x变量的生存期还没有结束,将x的地址存放在寄存器Eax中由eax带出
}
int main()
{
int a = fun();//如果在a接收fun()返回值之前又有函数被调用开辟栈帧了的话a的值将也会是一个随机值
//a=*(eax)
int& b = fun();//error
//&在底层相当于一个指针,同样的也指向eax带出的地址
cout << a << endl;//100
cout << b << endl;//是一个随机值,原因是在b是以指向的方式指向已经销毁的fun()的空间的,在这之前cout<<a<<endl;已经破环了原来fun()的那块空间,因此打印的b是一个随机值。
return 0;
}
若想要b的值能被打印出来我们只需要将a和b的打印顺序换一下即可,但是这两种写法都是不安全的。
以引用返回一个局部对象是绝对不允许的,例如下面这种情况:
Int & fun()
{
Int a(10);
return a;
}
int main()
{
Int a=fun();
//由于在这个赋值过程中会调动赋值函数,所以原来的空间已经被破坏了所以打印出来的是一个随机值
cout<<a<<endl;//随机值
return 0;
}
本文介绍了C++中的引用,包括其作为位与运算符、取地址符和引用的三种用途。引用作为变量的别名,不能为空且一旦绑定不能改变。讨论了引用在函数参数、返回值及常引用等方面的特点,强调了引用作为函数返回值的限制及其与指针、常量和右值引用的关系。
1万+

被折叠的 条评论
为什么被折叠?



