C++:引用的简单理解

前言:引用是C++一个很重要的特性,最近看了很多有关引用的资料和博客,故在此对引用的相关知识进行总结

一、什么是引用

引用,顾名思义是某一个变量或对象的别名,对引用的操作与对其所绑定的变量或对象的操作完全等价

语法:类型 &引用名=目标变量名;

特别注意:

1.&不是求地址运算符,而是起标志作用

2.引用的类型必须和其所绑定的变量的类型相同

1 #include<iostream>
2 using namespace std;
3 int main(){
4     double a=10.3;
5     int &b=a; //错误,引用的类型必须和其所绑定的变量的类型相同
6     cout<<b<<endl;
7 }

3.声明引用的同时必须对其初始化,否则系统会报错

1 #include<iostream>
2 using namespace std;
3 int main(){
4     int &a; //错误!声明引用的同时必须对其初始化
5     return 0;
6 }

4.引用相当于变量或对象的别名,因此不能再将已有的引用名作为其他变量或对象的名字或别名

5.引用不是定义一个新的变量或对象,因此内存不会为引用开辟新的空间存储这个引用

1 #include<iostream>
2 using namespace std;
3 int main(){
4     int value=10;
5     int &new_value=value;
6     cout<<"value在内存中的地址为:"<<&value<<endl;
7     cout<<"new_value在内存中的地址为:"<<&new_value<<endl;
8     return 0;
9 }

6.对数组的引用

语法:类型 (&引用名)[数组中元素数量]=数组名;
 1 #include<iostream>
 2 using namespace std;
 3 int main(){
 4     int a[3]={1,2,3};
 5     int (&b)[3]=a;//对数组的引用
 6     cout<<&a[0]<<" "<<&b[0]<<endl;
 7     cout<<&a[1]<<" "<<&b[1]<<endl;
 8     cout<<&a[2]<<" "<<&b[2]<<endl;
 9     return 0;
10 }

7.对指针的引用

语法:类型 *&引用名=指针名;//可以理解为:(类型*) &引用名=指针名,即将指针的类型当成类型*
1 #include<iostream>
2 using namespace std;
3 int main(){
4     int a=10;
5     int *ptr=&a;
6     int *&new_ptr=ptr;
7     cout<<&ptr<<" "<<&new_ptr<<endl;
8     return 0;
9 }

二、引用的应用

A.引用作为函数的参数

 1 #include<iostream>
 2 using namespace std;
 3 void swap(int &a,int &b){//引用作为函数的参数
 4     int temp=a;
 5     a=b;
 6     b=temp;
 7 }
 8 int main(){
 9     int value1=10,value2=20;
10     cout<<"----------------------交换前----------------------------"<<endl;
11     cout<<"value1的值为:"<<value1<<endl;
12     cout<<"value2的值为:"<<value2<<endl;
13     swap(value1,value2);
14     cout<<"----------------------交换后----------------------------"<<endl;
15     cout<<"value1的值为:"<<value1<<endl;
16     cout<<"value2的值为:"<<value2<<endl;
17     return 0;
18 }

特别注意:

1.当用引用作为函数的参数时,其效果和用指针作为函数参数的效果相当。当调用函数时,函数中的形参就会被当成实参变量或对象的一个别名来使用,也就是说此时函数中对形参的各种操作实际上是对实参本身进行操作,而非简单的将实参变量或对象的值拷贝给形参

2.通常函数调用时,系统采用值传递的方式将实参变量的值传递给函数的形参变量。此时,系统会在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量,也就是说形参变量只是实参变量的副本而已;并且如果函数传递的是类的对象,系统还会调用类中的拷贝构造函数来构造形参对象。而使用引用作为函数的形参时,由于此时形参只是要传递给函数的实参变量或对象的别名而非副本,故系统不会耗费时间来在内存中开辟空间来存储形参。因此如果参数传递的数据较大时,建议使用引用作为函数的形参,这样会提高函数的时间效率,并节省内存空间

3.使用指针作为函数的形参虽然达到的效果和使用引用一样,但当调用函数时仍需要为形参指针变量在内存中分配空间,而引用则不需要这样,故在C++中推荐使用引用而非指针作为函数的参数

4.如果在编程过程中既希望通过让引用作为函数的参数来提高函数的编程效率,又希望保护传递的参数使其在函数中不被改变,则此时应当使用对常量的引用作为函数的参数。

5.数组的引用作为函数的参数:C++的数组类型是带有长度信息的,引用传递时如果指明的是数组则必须指定数组的长度

 1 #include<iostream>
 2 using namespace std;
 3 void func(int(&a)[5]){//数组引用作为函数的参数,必须指明数组的长度
 4 //函数体
 5 }
 6 int main(){
 7     int number[5]={0,1,2,3,4};
 8     func(number);
 9     return 0;
10  }

B.常引用

语法:const 类型 &引用名=目标变量名;

常引用不允许通过该引用对其所绑定的变量或对象进行修改

1 #include<iostream>
2 using namespace std;
3 int main(){
4     int a=10;
5     const int &new_a=a;
6     new_a=11;//错误!不允许通过常引用对其所绑定的变量或对象进行修改
7     return 0;
8 }

特别注意:

先看下面的例子

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 string func1(){
 5     string temp="This is func1";
 6     return temp;
 7 }
 8 void func2(string &str){
 9     cout<<str<<endl;
10 }
11 int main(){
12     func2(func1());
13     func2("Tomwenxing");
14     return 0;
15 }

运行上面的程序编译器会报错

这是由于func1()和“Tomwenxing”都会在系统中产生一个临时对象(string对象)来存储它们,而在C++中所有的临时对象都是const类型的,而上面的程序试图将const对象赋值给非const对象,这必然会使程序报错。如果在函数func2的参数前添加const,则程序便可正常运行了

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 string func1(){
 5     string temp="This is func1";
 6     return temp;
 7 }
 8 void func2(const string &str){
 9     cout<<str<<endl;
10 }
11 int main(){
12     func2(func1());
13     func2("Tomwenxing");
14     return 0;
15 }

C.引用作为函数的返回值

语法:类型 &函数名(形参列表){ 函数体 }

特别注意:

1.引用作为函数的返回值时,必须在定义函数时在函数名前将&

2.用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本

 1 //代码来源:RUNOOB
 2 #include<iostream>
 3 using namespace std;
 4 float temp;
 5 float fn1(float r){
 6     temp=r*r*3.14;
 7     return temp;
 8 }
 9 float &fn2(float r){ //&说明返回的是temp的引用,换句话说就是返回temp本身
10     temp=r*r*3.14;
11     return temp;
12 }
13 int main(){
14     float a=fn1(5.0); //case 1:返回值
15     //float &b=fn1(5.0); //case 2:用函数的返回值作为引用的初始化值 [Error] invalid initialization of non-const reference of type ‘float&‘ from an rvalue of type ‘float‘                           //(有些编译器可以成功编译该语句,但会给出一个warning) 
16     float c=fn2(5.0);//case 3:返回引用
17     float &d=fn2(5.0);//case 4:用函数返回的引用作为新引用的初始化值18     cout<<a<<endl;//78.5
19     //cout<<b<<endl;//78.5
20     cout<<c<<endl;//78.5
21     cout<<d<<endl;//78.5
22     return 0;
23 }

 上例中4个case的说明解释:

case 1:用返回值方式调用函数(如下图,图片来源:伯乐在线):

返回全局变量temp的值时,C++会在内存中创建临时变量并将temp的值拷贝给该临时变量。当返回到主函数main后,赋值语句a=fn1(5.0)会把临时变量的值再拷贝给变量a

case 2:用函数的返回值初始化引用的方式调用函数(如下图,图片来源:伯乐在线)

这种情况下,函数fn1()是以值方式返回到,返回时,首先拷贝temp的值给临时变量。返回到主函数后,用临时变量来初始化引用变量b,使得b成为该临时变量到的别名。由于临时变量的作用域短暂(在C++标准中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束,也就是在语句float &b=fn1(5.0);之后) ,所以b面临无效的危险,很有可能以后的值是个无法确定的值。

 如果真的希望用函数的返回值来初始化一个引用,应当先创建一个变量,将函数的返回值赋给这个变量,然后再用该变量来初始化引用:

1 int x=fn1(5.0);
2 int &b=x;

 case 3:用返回引用的方式调用函数(如下图,图片来源:伯乐在线)

这种情况下,函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数,即主函数的赋值语句中的左值是直接从变量temp中拷贝而来(也就是说c只是变量temp的一个拷贝而非别名) ,这样就避免了临时变量的产生。尤其当变量temp是一个用户自定义的类的对象时,这样还避免了调用类中的拷贝构造函数在内存中创建临时对象的过程,提高了程序的时间和空间的使用效率。

case 4:用函数返回的引用作为新引用的初始化值的方式来调用函数(如下图,图片来源:伯乐在线)

这种情况下,函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数。在主函数中,一个引用声明d用该返回值初始化,也就是说此时d成为变量temp的别名。由于temp是全局变量,所以在d的有效期内temp始终保持有效,故这种做法是安全的。

3.不能返回局部变量的引用。如上面的例子,如果temp是局部变量,那么它会在函数返回后被销毁,此时对temp的引用就会成为“无所指”的引用,程序会进入未知状态。

4.不能返回函数内部通过new分配的内存的引用。虽然不存在局部变量的被动销毁问题,但如果被返回的函数的引用只是作为一个临时变量出现,而没有将其赋值给一个实际的变量,那么就可能造成这个引用所指向的空间(有new分配)无法释放的情况(由于没有具体的变量名,故无法用delete手动释放该内存),从而造成内存泄漏。因此应当避免这种情况的发生

5当返回类成员的引用时,最好是const引用。这样可以避免在无意的情况下破坏该类的成员。

6.可以用函数返回的引用作为赋值表达式中的左值

 1 #include<iostream>
 2 using namespace std;
 3 int value[10];
 4 int error=-1;
 5 int &func(int n){
 6     if(n>=0&&n<=9)
 7         return value[n];//返回的引用所绑定的变量一定是全局变量,不能是函数中定义的局部变量
 8     else
 9         return error;
10 }
11
12 int main(){
13     func(0)=10;
14     func(4)=12;
15     cout<<value[0]<<endl;
16     cout<<value[4]<<endl;
17     return 0;
18 }

D.用引用实现多态

在C++中,引用是除了指针外另一个可以产生多态效果的手段。也就是说一个基类的引用可以用来绑定其派生类的实例

class Father;//基类(父类)
class Son:public Father{.....}//Son是Father的派生类
Son son;//son是类Son的一个实例
Father &ptr=son;//用派生类的对象初始化基类对象的使用

特别注意:

ptr只能用来访问派生类对象中从基类继承下来的成员如果基类(类Father)中定义的有虚函数,那么就可以通过在派生类(类Son)中重写这个虚函数来实现类的多态。

 

三、总结

1.在引用的使用中,单纯给某个变量去别名是毫无意义的,引用的目的主要用于在函数参数的传递中,解决大块数据或对象的传递效率和空间不如意的问题

2.用引用传递函数的参数,能保证参数在传递的过程中不产生副本,从而提高传递效率,同时通过const的使用,还可以保证参数在传递过程中的安全性

3.引用本身是目标变量或对象的别名,对引用的操作本质上就是对目标变量或对象的操作。因此能使用引用时尽量使用引用而非指针

时间: 2024-10-12 13:37:19

C++:引用的简单理解的相关文章

php对引用的简单理解

背景 php语言的高度封装和五花八门的库使这门语言很容易上手,而且开发效率比C/C++高出许多.但是也正是由于php封装度很高,一些在c语言中很简单的概念,让php这么一封装,就变得难以琢磨.比如引用,在c语言中的概念很简答, 就是两个变量名指向同一块内存.而且引用必须要你手动操作,哪个变量引用的哪块内存在编写代码的时候心里是一清二楚的.但是在php中,有很多地方使用了隐式的引用,而写代码的时候并不知道这是引用.这就很容易造成问题,而且难以发现.就比如下面的代码我没有使用引用啊?但是name的值

关于引用和指针的简单理解

指针和引用一直是C++中难懂的部分,为此我也困惑了很久,下面说说我对引用和指针的简单理解. 在使用函数时我们常常会纠结实参传递到形参后,函数对形参的操作会不会改变实参的值(也就是主函数的相应变量),最典型的例子是swap(int a,int b).我们都知道,函数在调用时会给变量重新开辟一个存储空间,而这个存储空间会暂时存储形参的值,如果函数在调用完毕后,其相应存储空间的值会被释放.例如下面一段代码: void swap1(int a, int b) { int temp = 0; temp =

Javascript闭包简单理解

提到闭包,想必大家都早有耳闻,下面说下我的简单理解.说实话平时工作中实际手动写闭包的场景并不多,但是项目中用到的第三方框架和组件或多或少用到了闭包.所以,了解闭包是非常必要的.呵呵... 一.什么是闭包简而言之,就是能够读取其他函数内部变量的函数.由于JS变量作用域的特性,外部不能访问内部变量,内部可以外部变量. 二.使用场景1. 实现私有成员.2. 保护命名空间,避免污染全局变量.3. 缓存变量. 先看一个封装的例子: var person = function () { // 变量作用域为函

对数据类型封装和数据抽象的简单理解

请特别关注程序设计技术,而不是各种语言特征. --<C++程序设计语言> Bjarne Stroustrup 本文是<C++程序设计语言>(Bjarne Stroustrup )的第二章的读书笔记,例子来源于这本书的第二章. 在程序设计之中,我们倾向于将数据结构(也可以说是数据类型)以及一组对其操作的相关过程组织在一起,在逻辑上可以称将其为模块.此时程序分为一些模块,模块包括一组对数据的操作,数据隐藏于模块之中.以下以栈的设计为例,使用C和C++进行设计,简单理解模块化设计中的数据

对象序列化原因的简单理解

序列化和反序列化我们可能经常会听到,其实通俗一点的解释,序列化就是把一个对象保存到一个文件或数据库字段中去,其最终目的都是将内存中的对象持久化或者是在网络上传输.反序列化就是在适当的时候把这个文件再转化成原来的对象使用. 使用序列化的原因 a. 一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本.我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据.尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂.可

大话设计模式总结(28种设计模式定义+简单理解)

大话设计模式这本书写的非常有创意,非常适合我这种新手.用了大约两个星期的时间看完了这本书,代码全部都敲了一遍,虽然没有一点基础,但是还是领略到了面向对象的威力.看完之后再也不想使用面向过程的语言了,比如VB,想当初我也是VB狂热者,但是现在我几乎不想再使用了.现在只想着写点什么用上它几种设计模式. 可能是第一次接触这些东西,有些感觉看懂了,但是很难应用到实际编程中:有些感觉没看懂,但是还能说出那么点东西来.听七期学长说他们当初看了两遍,要求能背着写出代码,不知道这次我们八期要求怎么这么低,我只看

我们为之奋斗过的C#-----C#的一个简单理解

我们首先来简单叙述一下什么是.NET,以及C#的一个简单理解和他们俩的一个区别. 1 .NET概述 .NET是Microsoft.NET的简称,是基于Windows平台的一种技术.它包含了能在.NET Framework平台运行的所有编程语言. 2 C#概述 他是专门为.NET平台设计的一种语言. 3 .NET与C#的区别 .NET是一种平台,这种平台可以编译多种语言例如:VB,J#,而C#只是一种语言. 4 IDE IDE全称(Itergrated Developer Environment)

Js 职责链模式 简单理解

js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = function () { if (this.successor) { this.successor.handle(); } } } var app = new Handler({ handle: function () { console.log('app handle'); } }); var dialo

简单理解IoC与DI

为了理解Spring的IoC与DI从网上查了很多资料,作为初学者,下面的描述应该是最详细,最易理解的方式了. 首先想说说IoC(Inversion of Control,控制倒转).这是spring的核心,贯穿始终.所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系.这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好.qq号.电话号.ip号.iq号………,想办法认识她们,