首页 > 代码库 > 详解C++引用——带你走进引用的世界
详解C++引用——带你走进引用的世界
一、介绍引用
首先说引用是什么,大家可以记住,引用就是一个别名,比如小王有个外号叫小狗,他的妈妈喊小狗回家吃饭,那就是在喊小王回家吃饭。
接下来我们用两行代码来声明一个引用(就拿小王和小狗来说吧):
int xiaoW;
int &xiaoG=xiaoW;
上面就是一个引用,说明几点要注意的地方:
1.&不是取地址符,而是引用运算符;
2.xiaoG是xiaoW的别名,所以这两个变量的值和地址都是一样的;
3.引用只能初始化,而不能先声明再赋值,因为引用就相当于一个常量;
4.在声明一个引用时必须同时对其进行初始化。
引用是非常忠心的,如果你定义了一个变量的别名,那么这个别名永远属于这个变量,它的地址始终和变量的地址相同,当改变别名的值也会改变这个变量的值。
同样,对对象的引用也与对变量的引用相同,但需要注意的是不能对类引用,因为类是一种类型,没有内存地址。
关于引用的介绍就到这里,下面我们来学习一下引用的使用。
二、三种传参方式的比较
函数的传参方式有三种:按值传递、按地址传递及按别名传递(按别名传递也是按地址传递的一种,这里我为了方便讲解把它单列出来)。接下来我们用三个程序来对三种方式作下比较,程序的功能是交换两个变量的值。
1.按值传递。
#include<iostream>
using namespace std;
void swap(int i,int j)
{
cout<<"交换前的i:"<<i<<" "<<"j:"<<j<<endl;
int temp;
temp=i;
i=j;
j=temp;
cout<<"交换后的i:"<<i<<" "<<"j:"<<j<<endl;
}
int main()
{
int a=1,b=2;
cout<<"交换前的a:"<<a<<" "<<"b:"<<b<<endl;
swap(a,b);
cout<<"交换后的a:"<<a<<" "<<"b:"<<b<<endl;
return 0;
}
程序的运行截图如下:
从结果我们可以看出,变量a和b的值分别传给i和j,函数内的i和j的值是交换了的,而a和b的值并未交换,这是怎么回事呢。
这是因为,当把a和b的值传递到函数中时,这种传递方式就是按值传递,编译器会自动在栈中创建a和b的副本,然后再将a和b的副本传递给函数,这样在函数中交换的是a和b的副本,而不是a和b本身。解决这样的问题就要引入指针了。
2.按地址传递。
#include<iostream>
using namespace std;
void swap(int *i,int *j)
{
cout<<"交换前的i:"<<*i<<" "<<"j:"<<*j<<endl;
int temp;
temp=*i;
*i=*j;
*j=temp;
cout<<"交换后的i:"<<*i<<" "<<"j:"<<*j<<endl;
}
int main()
{
int a=1,b=2;
cout<<"交换前的a:"<<a<<" "<<"b:"<<b<<endl;
swap(&a,&b);
cout<<"交换后的a:"<<a<<" "<<"b:"<<b<<endl;
return 0;
}
程序的运行截图如下:
可以看到,这样就可以交换a和b的值了,因为程序传递的是a和b的地址,所以在函数中可以通过地址直接访问a和b,交换它们的值。
指针虽然可以实现交换的功能,但是使用起来比较麻烦,又不易阅读,使用引用看起来就会直观得多。
3.按别名传递
#include<iostream>
using namespace std;
void swap(int &i,int &j)
{
cout<<"交换前的i:"<<i<<" "<<"j:"<<j<<endl;
int temp;
temp=i;
i=j;
j=temp;
cout<<"交换后的i:"<<i<<" "<<"j:"<<j<<endl;
}
int main()
{
int a=1,b=2;
cout<<"交换前的a:"<<a<<" "<<"b:"<<b<<endl;
swap(a,b);
cout<<"交换后的a:"<<a<<" "<<"b:"<<b<<endl;
return 0;
}
程序的运行截图如下:
结果也是成功交换,程序看起来易懂,而且操作也方便不少。
三、传递对象
对象和变量一样可以作为参数传递,而不一样的是,对象可能包含有大量的数据,那么用上述的三种方式分别传递对象是什么样子呢。
1.按值传递对象
当按值传递的参数是一个对象时,编译器同样会建立一个对象的副本,函数中返回对象时,同样会建立对象的副本,当该对象的数据很多时,那对内存的消耗就非常大了,我们先来看按值传递的程序。
#include<iostream>
using namespace std;
class A
{
public:
A(){cout<<"构造函数被调用\n"<<endl;}
A(A&a){cout<<"拷贝构造函数被调用\n"<<endl;}
~A(){cout<<"析构函数被调用\n"<<endl;}
private:
int x;
};
A func(A a)
{
return a;
}
int main()
{
A a;
func(a);
return 0;
}
程序运行结果截图如下:
由程序运行结果可以看出,执行了一次构造函数,两次复制构造函数,三次析构函数,接下来我们对照程序结果和代码分析一下(此处知识对新手十分重要,大家务必牢记!),main函数中创建了一个对象a,所以调用了构造函数,再将a传递到func函数中,这时会自动调用复制构造函数来创建对象a的副本,也就是说传进func函数的是a的副本而不是a本身,func函数的返回值是对象,返回的方式也是按值返回,此时又自动调用复制构造函数创建返回值的副本,所以我们看到调用一次构造函数和两次复制构造函数,相应也会调用三次析构函数释放内存。
2.按地址传递对象
从按值传递对象的程序结果可以看出,这种方式的系统开销是非常大的,而按地址传递则解决了这一问题。
#include<iostream>
using namespace std;
class A
{
public:
A(){cout<<"构造函数被调用\n"<<endl;}
A(A&a){cout<<"拷贝构造函数被调用\n"<<endl;}
~A(){cout<<"析构函数被调用\n"<<endl;}
private:
int x;
};
A func(A *a)
{
return *a;
}
int main()
{
A a;
func(&a);
return 0;
}
程序运行结果截图如下:
可以看出,上面的程序避免了两次复制构造函数的调用,因为在将对象a传递给func函数时传进的是地址,func函数返回的同样也是个地址。
3.按别名传递对象
#include<iostream>
using namespace std;
class A
{
public:
A(){cout<<"构造函数被调用\n"<<endl;}
A(A&a){cout<<"拷贝构造函数被调用\n"<<endl;}
~A(){cout<<"析构函数被调用\n"<<endl;}
private:
int x;
};
A &func(A &a)
{
return a;
}
int main()
{
A a;
func(a);
return 0;
}
程序运行结果截图如下:
可以看出,按别名传递对象与按指针传递有同样的效果,而实现起来又简单的多,这里给新手朋友们做出个小提示,就是func函数返回的类型是别名,如果想在main函数中接收这个别名,同样也要用引用类型的数据接收,比如A &aa=func(a),而不能用一个A类型的对象接收,这点大家要注意。
四、到底该用指针还是引用
既然引用可以实现指针的功能,又比指针使用方便,那是不是只用引用就可以了呢,答案是否定的,因为指针的某些功能引用是无法实现的,先说一下引用和指针的区别。
1.指针可以为空,而引用不能为空;
2.指针可以被赋值,而引用不可改变;
3.在堆中创建一块内存区域时,必须使用指针来指向这块区域,否则是无法访问的,而不能用引用来指向
五、使用引用需谨慎
引用是不能为空的,否则是得不到正确的结果,我们通过下面的程序来感受一下。
#include<iostream>
using namespace std;
class A
{
public:
A(int i){x=i;}
int get(){return j;}
private:
int j;
};
A &func()
{
A a(100);
return a;
}
int main()
{
A&aa=func();
cout<<aa.get()<<endl;
return 0;
}
程序运行结果截图如下:
从运行结果可以看出,我们并没有得到正确的j值(100),这是为什么呢,原因是,func函数中的a是一个局部对象,在func函数运行后,a就没有了,所以func函数返回的是一个并不存在的对象的别名,因此无法正确得到j的值,解决这个问题很容易,只要将func函数的引用类型返回值改为A类型,就可以的到正确的结果,因为这样会创建一个对象a的副本。
六、寄语
我刚开始学习C++时对引用就特别头疼,但只要清晰的了解了它的工作方式,学习起来还是比较简单的,所以我以我的亲身体验写出这篇博文,以供初学者朋友参考。当然,引用的知识比较混乱而且极容易与指针混淆,需要在应用的过程中才能逐渐掌握,所以建议朋友们还是去多读多写代码。祝朋友们都能学到足够的知识,在实践中进步,成为合格的互联网人才。
由于我也是刚入门不久,所以纰漏之处在所难免,请各位多包涵,如果对本文有任何意见或建议,欢迎与我qq交流,谢谢!