首先要强调引用和指针的区别,指针开空间存储数据的地址,自己本身也有地址,而引用是没有的
int a=0;
int&b=a;
//a和b的地址一样!
int a=0;
int*b=&a;
//a和b的地址不一样!
引用可以使用在函数传值上,这样就不用指针开辟额外空间了,这样运行时间也会减少,效率增加
#include<iostream>
using namespace std;
void my_swap(int&a,int&b){
int tmp=a;
a=b;
b=tmp;
}
int main(){
int a=1;
int b=0;
my_swap(a,b);
cout<<a<<" "<<b<<endl;
return 0;
}//输出0,1
然后具体看一下引用在返回值上的应用
先看个神奇的例子
#include<iostream>
using namespace std;
int& count(int x){
int n=x;
n++;
return n;
}
int main(){
int&ret=count(10);
cout<<ret<<endl;//输出11
count(20);
cout<<ret<<endl;//输出21
return 0;
}
加一行代码
#include<iostream>
using namespace std;
int& count(int x){
int n=x;
n++;
return n;
}
int main(){
int&ret=count(10);
cout<<ret<<endl;
count(20);
printf("ss\n");
cout<<ret<<endl;//输出一个随机数
return 0;
}
这里第一张图是因为栈帧还没被清理(销毁),之前的内容还在,所以临时变量n还在。这里函数返回的是n的引用,ret是n的引用的引用,类似于下面这图
int a=0;
int&b=a;
int&c=b;
这里ret和c一样,n和a一样
当然,这里进行一个简单的static处理就可以变正确
#include<iostream>
using namespace std;
int& count(int x){
static int n=x;
n++;
return n;
}
int main(){
int&ret=count(10);
cout<<ret<<endl;//输出11
count(20);
cout<<ret<<endl;//输出12
return 0;
}
再看点引用在返回值上的正确使用的例子(具体应用中也经常使用,在使用结构体时非常方便),因为结构体变量和上述的static效果是一样的,即使出了要去改变结构体变量的函数,变量本身也不会销毁,因此可以直接引用,并且直接改变它,作出必要的操作
#include<iostream>
#include<assert.h>
using namespace std;
struct SeqList
{
int a[100];
size_t size;
};
int& SLAt(SeqList& s, int pos)
{
assert(pos < 100 && pos >= 0);
return s.a[pos];
}
int main(){
SeqList s;
SLAt(s, 0) = 1;
cout << SLAt(s, 0) << endl;//输出1
SLAt(s, 0) += 5;
cout << SLAt(s, 0) << endl;//输出6
return 0;
}
还有更好的写法,把改变结构体变量的函数放在结构体里面,再加个迭代器,当然这是c++的类独有的效果,c的结构体是没有的
#include<iostream>
#include<assert.h>
using namespace std;
struct SeqList
{
int a[100];
size_t size;
int& at(int pos)
{
assert(pos >= 0 && pos < 100);
return a[pos];
}
int& operator[](int pos)
{
assert(pos >= 0 && pos < 100);
return a[pos];
}
};
int main()
{
SeqList s;
s.at(0) = 0;
s.at(0)++;
cout << s.at(0) << endl;//输出1
s[1] = 10;
s[1]++;
cout << s[1] << endl;//输出11
return 0;
}
引用还有一个重要的特性就是它的权限只能平移或者缩小,不能扩大
int main()
{
// 不可以
// 引用过程中,权限不能放大
const int a = 0;
int& b = a;//报错
------------------------
// 可以,c拷贝给d,没有放大权限,因为d的改变不影响c,这里只是赋值
const int c = 0;
int d = c;
---------------------
// 可以
// 引用过程中,权限可以平移或者缩小
int x = 0;
int& y = x;
const int& z = x;
++x;
++z;//注意是const,值不能改变,报错
//可以
//这里是权限平移
const int&m=10;
}
说下一般的的权限缩小
double x=1.1;
int a=x;
这里是明显的权限缩小,实际过程是先创造出一个x的int型临时变量,不改变x本身,然后再传给a。
好的,再回来看一下引用
double x=1.1;
int&a=x;//报错
const int&b=x;//编译通过
这里创造出一个x的int型临时变量,而临时变量具有常性,本身就是相当于有个const的属性,如果这里引用不加const修饰就是权限扩大,这是不被允许的,函数返回的时候也是一样,如果是返回值的话先创建一个临时变量再返回,如果是返回引用,比如返回n的引用,那么先创建临时变量int&a=n,再用int&ret=a接收
int func1()
{
static int x = 0;
return x;
}
int& func2()
{
static int x = 0;
return x;
}
int main()
{
//int& ret1 = func1(); // 权限放大
//const int& ret1 = func1(); // 权限平移
//int ret1 = func1(); // 拷贝
//
//int& ret2 = func2(); // 权限平移
//const int& rret2 = func2(); // 权限缩小
return 0;
}
又比如
int i=1;
double j=1.1;
if(j>i)cout<<"666"<<endl;
//这里比较是先创建一个i的double型临时变量,,不改变i本身
下面讲一下auto,它可以自动推导对象类型
int x=0;
auto c=x;
通常在for中使用,c++11中的for多了一种新的写法,如下所示,用引用的话还可以修改值
vector<int>res{1,2,3};
for(auto k:res){
k++;
}
for(auto k:res){
cout<<k<<endl;
}
//输出1,2,3
for(auto&k:res){
k++;
}
for(auto k:res){
cout<<k<<endl;
}//输出2,3,4