一、引用
1.引用的定义
类型 & 引用变量名称 = 变量名称
&和类型结合称之为引用符号,不是取地址的符,代表别名意思。
引用分为左值引用、右值引用。
2.左值、右值、将亡值(亡值)
左值:可以取地址的变量
右值:不能取地址的变量
将亡值:在表达式的计算过程产生,计算结束则消失(并不一定在计算过程产生)
int main()
{
int a=10;
int b=a;
int &c=a; //c为a的别名,在c++11中称为左值引用
int &&d=10; //此时为右值引用,并且这时的d可以被左值引用,即int &p=d;
}
c为a的引用,与a在同一个地址,b虽然与a的值一样,但是不在同一地址。
二、引用的特点
1.定义引用必须初始化
2.没有空引用
3.没有引用的引用,即所谓的二级引用
int main()
{
int a=10;
int &x; //err
int &y=NULL; //err
int &b=a;
int &&c=b; //err 注意区分它和右值引用的区别
}
三、const引用
int main()
{
int a=10;
const int b=20;//b为常性值,只读不写
int &x=b; //x可读可写
const int &x=b;
const int &y=a;
const int &z=10;
return 0;
}
const引用(万能引用),可以引用左值,也可以引用右值
四、引用作为形参替代指针
引用比指针更加安全,但引用没有指针灵活(引用不可改变对象)
引用可以对实参进行改变,而指针不可,且引用不存在NULL引用,不需要判空,比指针安全
指针占地址空间,而引用不占(或者说是引用占地址空间,但系统忽略,认为它不占)
//C语言的实现
void my_swap(int *ap,int *bp)
{
assert(ap!=NULL&&bp!=NULL);
int tmp = *ap;
*ap=*bp;
*bp=tmp;
}
int main()
{
int a=10,b=10;
cout<<"a ="<<a<<"b = "<<b<<endl;
my_swap(&a,&b);
cout <<"a="<<a<<"b="<<b<<endl;
return 0;
}
//c++的实现
void my_swap(int &x,int &y)
{
int tmp=x;
x=y;
y=tmp;
}
int main()
{
int a=10,b=10;
cout<<"a ="<<a<<"b = "<<b<<endl;
my_swap(a,b);
cout <<"a="<<a<<"b="<<b<<endl;
return 0;
}
五、其他引用形式
int main()
{
int a=10,b=20;
int ar[5]={1,2,3,4,5};
int *p=&a;
int *&rp=p; //引用指针 rp为p的别名
int &x=ar[0];
int (&br)[5]=ar; //引用数组(此时的ar是数组的整体,而不是数组首地址) br为ar的别名
}
注意:只有引用指针、引用数组,而无指针引用、数组引用
int &br[10]; //可以实现吗?
不可,我们在引用之前是无法知道被引用对象的类型的,而要实现int &br[10]我们必须定义类型,故无法实现。
int &*p; //可以实现吗?
不可,引用时,我们必须要进行初始化,而指针可以不进行初始化,互相冲突,故不可实现,且引用不可改变引用对象,但指针可以改变指向对象,互相冲突。
六、引用与指针的区别
1.指针变量存储某个实例(变量或对象)的地址,引用是某个实例的别名
2.程序为指针变量分配内存区域,而不为引用分配内存区域
3.解引用是指针使用时要在前加" * ",引用可以直接使用
4.指针变量的值可以发生改变,存储不同实例的地址;引用在定义时就被初始化,之后无法改变(不能是其他实例的引用)
5.指针变量的值可以为空(NULL,nullptr),没有空引用
6.指针变量作为形参时需要检测它的合法性(判空),引用不需要判空
7.对指针变量使用“sizeof”的到的是指针变量的大小(指针解引用"sizeof"得到的是变量的大小),而对引用变量使用“sizeof”得到的是变量的大小
8.理论上指针的级数没有限制,但引用只有一级,即不存在引用的引用,但可以有指针的指针
9.++引用与++指针变量的效果不一样
对指针变量的操作,会使指针变量指向下一个实体(变量或对象)的地址,而不是改变所指实体(变量或对象)的内容
对引用的操作直接反应到所引用的实体(变量或对象)
int ar[10]={12,23,34,45,56,67,78,89};
int *ip = &ar[1]; //*ip=23
int &ra = ar[1]; //ra=23
++*ip; //ar=24 23+1
++ra; //ra=24 23+1
++ip; //*ip=34
七、引用的本质
从汇编层次理解引用与指针变量的区别
编译器在编译过程中,会将引用变为指针,并且引用对于原码来说没有内存空间,在机器码上有内存空间。
//原码
int main()
{
int a=10;
int *ip=&a;
int &b=a; //int * const b = &a;(机器码)
*ip = 100;
b = 200;
return 0;
}
为什么int &b变为int * const b?
答:引用在定义时就初始化,不可发生改变,当int &b=a时,则b只能为a的别名,但指针变量的值是可以改变的,故const是为使b只能指向a,不可发生改变。
引用本质上为指针(* const)
八、引用作为函数的返回值类型
不可以对函数中的局部变量或对象以引用或指针方式返回
//在函数结束后,内存空间将还给系统,因此不可返回a的地址
//若要返回a的地址,将a设为全局变量或静态变量,即生存期不受函数的影响
int * func_1()
{
int a=10;
return &a;
}
//在机器码中,引用最终还是变为指针,return &a;
int & func_2()
{
int a=10;
return a;
}
//以上两种都是错误的
引用使用的变量不受函数的影响,就可返回变量的地址。
在函数结束,其空间还给栈区,但数据区不受函数影响,而全局变量和静态在数据区,因此这两者可以返回变量的地址。
九、分析效率
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[8];
int s_age;
};
void funa(struct Student sx) //传值(效率低),不需要定义变量
{
//在funa中,对sx进行改变,不会影响s1
}
void funa(struct Student *ps) //传地址,需要定义变量
{
//使用指针必须判空,ps改变,会改变S1
}
void funa(struct Student &st) //引用,传地址,需要定义变量
{
//st改变,会改变s1
//不用判空,但灵活性差
}
int main()
{
struct Student s1={};
funa(s1); //传52个字节
funb(&s1); //传4个字节
func(s1); //传4个字节
return 0;
}