一. 引用的概念及用法
1. 概念
引用是给一个已存在的变量起一个别名,而不是定义一个新变量。
2. 引用的使用格式
类型& 引用变量名 = 已定义过的变量名
3. 引用的特点
(1)一个变量可起多个别名
(2)引用必须初始化
(3)引用变量只能在初始化的时候引用一次,之后不能改变为再去引用别的变量
4. 引用的代码实现
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int& b = a;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
cout<<"a address"<<&a<<endl;
cout<<"b address"<<&b<<endl;
//可以对一个引用变量再引用,是别名的别名
int&c = b;
cout<<"c="<<c<<endl;
//修改引用变量,对应的原变量的值随之改变
c = 4;
cout<<"a="<<a<<" b="<<b<<" c="<<c<<endl;
cout<<"address "<<"a:"<<&a<<" b:"<<&b<<" c:"<<&c<<endl;
return 0;
}
具体运行结果为
可以看出引用只是在起别名。a,b的值与地址都一样。改变引用变量c的值,被引用变量的值也会随之改变。
5. const引用
(1)非const的变量可以被const变量引用
(2)const变量不可以被非const的变量引用
我们可以这样去理解,const变量我们可以认为它是只读的,非const变量我们可以认为它是可读可写的。一个只读的const的变量,我给别人之后,被它怎么能去写呢;同样的,一个可读可写的变量,我给别人之后,它只读是可以的。总结一句话就是“定义别名时,权限可以缩小,但是不能放大”。
#include <iostream>
using namespace std;
int main()
{
//int a = 5;
//const int& b = a;
//cout<<"a="<<a<<" b="<<b<<endl;
//a = 6;
//cout<<"a="<<a<<" b="<<b<<endl;
////常量不能被修改
////b = 7;//这句是错误的
////定义别名时,权限不能放大,只能缩小
//const int a = 5;
////int& b = a;//这句错误,只读变量不能转化为可写可读的
//const int&b = a;//只有常属性可以引用常量
//cout<<"a="<<a<<" b="<<b<<endl;
double a = 1.1;
//a赋值给b时要生成一个临时变量,b其实引用的是那个带有常性的临时变量,而不是a,所以不能赋值
//int& b = a;//这句是错误的
const int& b = a;
cout<<"a="<<a<<" b="<<b<<endl;
return 0;
}
具体运行结果为:
二. 引用作为参数
1. 引用作为参数的代码实现
在C语言的学习中,我们可以知道两种常用的函数传参方式,分别是:传值、传地址。在这里,我们学习的引用也可以作为参数传递。
就拿常见的交换两个变量的值的swap函数来举例,我们知道传递并不能交换两个变量的值,但是传地址可以。对于传值来讲,因为传值给函数,形参是实参的一份临时拷贝,在swap函数中,两个变量的值确实交换了,但是它们交换的仅是形参。它是临时变量,出了swap函数,它们就被销毁了,实际上要交换的两个变量的值并未改变。而对于传地址来讲,在swap函数中接收到的参数就是两个要交换变量的地址,swap函数可以通过地址访问到两个变量,并且出了swap函数,这两个变量依旧在,通过访问地址可以实现交换它们的值。这里的引用传参也可以实现两个变量值的交换。原因就是引用是给变量起别名,对引用变量作修改,被引用的变量也会随之修改,这边在上面我们已经验证过了。具体代码实现如下:
#include <iostream>
using namespace std;
//(1)值传递->交换不了两个数的值
void Swap1(int a, int b)
{
int ret = a;
a = b;
b = ret;
}
//(2)引用传递->可以交换两个数的值
//->可以提高效率
void Swap2(int& a, int& b)
{
int ret = a;
a = b;
b = ret;
}
//(3)指针传递->可以交换两个数的值
void Swap3(int* a, int* b)
{
int ret = *a;
*a = *b;
*b = ret;
}
int main()
{
int a = 10;
int b = 20;
cout<<"a="<<a<<" b="<<b<<endl;
Swap1(a, b);
cout<<"a="<<a<<" b="<<b<<endl;
int& c = a;
Swap2(a, b);
cout<<"a="<<a<<" b="<<b<<endl;
Swap3(&a, &b);
cout<<"a="<<a<<" b="<<b<<endl;
return 0;
}
运行结果如下:
2. 测试引用传递和值传递的效率
在数据较大时,引用传递的效率高于值传递。
编写测试代码如下:
#include <iostream>
#include <time.h>
using namespace std;
struct Bigdata
{
int arr[1000];
};
void DealBigdata(Bigdata& x)
{
x.arr[0] = 0;
x.arr[1] = 1;
x.arr[2] = 2;
}
void DealBigdata1(Bigdata x)
{
x.arr[0] = 0;
x.arr[1] = 1;
x.arr[2] = 2;
}
int main()
{
Bigdata bd;
struct timespec ts;
//在windows下也可以用GetTickCount()函数
clock_t begin = clock();//该函数用于获取从系统启动到执行到该句的毫秒数
int i = 0;
for(; i<1000000; i++)
{
DealBigdata(bd);
}
clock_t end = clock();
cout<<"引用传递:"<<end-begin<<endl;
clock_t begin1 = clock();
for(i=0; i<1000000; i++)
{
DealBigdata1(bd);
}
clock_t end1 = clock();
cout<<"值传递:"<<end1-begin1<<endl;
return 0;
}
运行结果如下:
从上图,我们可以明显的看到,引用传递的效率要高于值传递。
注意:我们有一个建议是说,若并不想函数内改变传入的引用变量的值,尽量使用const传参,如下所示:
void readata(const int& a)
{
int ret = a;
}
三. 引用作返回值
1. 引用作返回值、值作返回值
(1)用“引用”返回适用于:出作用域,return的值依旧在
(2)用”值”返回适用于:出作用域,return的值不在了
编写测试代码如下:
#include <iostream>
using namespace std;
//(1)传值作返回值
//->尽量用于出作用域,返回的变量不在了的情况
int Add(int a, int b)
{
int ret = a + b;
//带回返回值的并不是ret,因为出该函数ret已经销毁,开辟了临时变量,复制了ret返回
return ret;
}
//(2)传引用作返回值
//->尽量用于出作用域,返回的变量还在的情况
int ret1;
int& Add1(int a, int b)
{
ret1 = a + b;
//带回返回值的并不是ret,因为出该函数ret已经销毁,开辟了临时变量,复制了ret返回
return ret1;
}
int main()
{
int c = Add(3, 4);
cout<<c<<endl;
int d = Add1(3, 4);
cout<<d<<endl;
return 0;
}
运行结果如下:
对于用值返回来讲,因为出了作用域后,返回的值就被销毁,其实是有临时变量复制保存了要返回的值,带回去返回的。若是比较小的变量返回,一般是寄存器复制充当临时变量的角色;对于较大的变量,一般是要临时变量保存压栈带回的。这里只是简单介绍,想要深入了解的可以自行再去查阅一下。
2. 若引用变量已经引用一个固定的变量,只要变量的值修改,引用变量的值必须修改
具体情况我们通过代码分析,测试代码如下:
#include <iostream>
using namespace std;
int ret;
int& Add(int a, int b)
{
ret = a + b;
return ret;
}
int main()
{
int& c = Add(20, 30);
cout<<c<<endl;
Add(200, 300);
cout<<c<<endl;
return 0;
}
运行结果如下:
我们可以看到,在第一次调用add函数之后,用了引用变量c接收返回值ret,第一次调用结果为50。但是在第二次调用add函数之后,我们并没有用引用变量c去接收返回值,但是c的值依旧改变为了500。造成这样的原因就是,在c接收返回值ret的时候,c已经是ret的别名了,只要ret改变c就随之改变。虽然第二次调用并没拿c接收返回值,但是只要调用了add函数,最后都返回了ret,ret也改变了,c当然一起改变。
四. 汇编层看引用的特性
1. 通过汇编看传值返回和传引用返回
我们可以看出,引用是起了一个别名,但是它是通过指针实现的。
结论:
(1)不要返回一个临时变量的引用;
(2)若返回对象出了当前作用域还在,最好用引用返回,因为这样更高效。
五. 引用和指针的区别
(1)引用只能在定义时初始化一次,之后不能改变为指向其它的变量(从一而终);指针变量的值可变,可指向其他变量;
(2)引用必须指向有效的变量;指针可以为空;
(3)sizeof引用得到的所指向变量的大小;sizeof指针得到的是对象地址的大小;
(4)引用++表示所指变量的值+1;指针++表示+所指变量类型的大小;
(5)相对而言,引用比指针更安全。
总结:
指针比引用更加地灵活,但是也更危险。在使用指针的时候,一定要检验指针是否为空。指针所指地址释放之后最好置为0,否则存在野指针的问题。
注:
(1)在32位的操作系统下,只可能有32位的程序,所以指针的大小只可能是4字节;
(2)在64位的操作系统下,可能有32/64位的程序,对应指针的大小为4/8字节。