c++笔记_引用


前言

复合类型(compound type)是指基于其他类型定义的类型。


一、引用是什么?

引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:

代码如下(示例):

int ival = 1024;
int &refVal = ival;			//refVal指向ival(refVal是ival的另一个名字)
int &refVal2;				//错误:引用必须被初始化

在初始化变量中时,初始值会被拷贝到新建的对象中。然而定义时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。所以一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。

ps:C++11中新增了一种引用:所谓的“右值引用”,这种引用主要用于内置类。严格来说当我们使用术语“引用”时,指的其实是“左值引用”。

1.引用即别名

定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的:

#include<iostream>
using namespace std;
int main(void)
{
	int ival = 1024;
	int &refVal = ival;	
	refVal = 2;				//把2赋给refVal指向的对象,此处是给了ival
	int li = refVal;		//与li = ival执行结果一样(li = 2)
	
	cout<<"ival="<<ival<<endl;
    cout<<"refVal="<<refVal<<endl;
    cout<<"li="<<li<<endl;
    
    cout<<"&ival="<<&ival<<endl;		//打印变量ival的内存地址
	cout<<"&refVal="<<&refVal<<endl;	//打印引用refVal的内存地址
	
	return 0;
}

代码输出:

ival=2
refVal=2
li=2
&ival=0x7ffeefbff3f4		//ival和refVal是同一块内存地址,所以可以理解ival完全等价与refVal
&refVal=0x7ffeefbff3f4

所以可以理解成引用就是为一个已经存在的对象起的另外一个名字。

二、引用的定义

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头:

int i = 1024, i2 = 2048;		//i和i2都是int
int &r = i, r2 = i2;			//r是一个引用,与i绑定在一起,r2是int
int i3 = 1024, &ri = i3;		//i3是int,ri是一个引用,与i3绑定在一起
int &r3 = i3, &r4 = i2;			//r3和r4都是引用

大部分情况下引用都类型都要和与之绑定的对象严格匹配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

int &refVal4 = 10;			//错误:引用类型的初始值必须是一个对象
double dval = 3.14;			
int &refVal5 = dval;		//错误:引用的类型的初始值必须是int对象

三、用引用给数组取个名字

当然也可以用引用给数组取个别名,具体方法如下:

#include<iostream>
using namespace std;
int main(void)
{
	int arr[5] = {1,2,3,4,5};
	//目的:给数组arr取个别名
	int (&my_arr)[5] = arr;		//my_arr就是数组arr的别名
	
	//打印数组arr元素
	for(int i=0;i<5;i++)
		cout<<arr[i]<<endl;
	
	cout<<"-------"<<endl;
	
	//打印数组my_arr元素
	for(int b=0;b<5;b++)
		cout<<my_arr[b]<<endl;
	
	cout<<"数组arr的内存地址:"<<&arr<<endl;
	cout<<"数组my_arr的内存地址:"<<&my_arr<<endl;	

	return 0;
}

代码输出结果:

1
2
3
4
5
-------
1
2
3
4
5
数组arr的内存地址:0x7ffeefbff480
数组my_arr的内存地址:0x7ffeefbff480

四、配合typedef使用引用

首先简单介绍下typedef是什么?就是为类型创建个新的名字。

某些场景下,我们也可以配合typedef来使用引用,具体方法如下:

#include<iostream>
using namespace std;
int main(void)
{
	int arr[5] = {1,2,3,4,5};
	
	typedef int TYPE_ARR[5];	//TYPE_ARR就是一个数组类型(有5个int类型元素)
	
	//用typedef给数组类型取个别名
	TYPE_ARR &myarr = arr;		//myarr就是数组arr的别名
	
	for(int i=0;i<5;++i)
		cout<<myarr[i]<<endl;
		
	return 0;
}

四、引用作为函数参数

引用也可以用作函数参数。假设,我们需要交换a和b两个数的位置,在不用引用的情况下会输出什么呢?

#include<iostream>
using namespace std;
//不使用引用进行交换
void swap(int a,int b)
{
	int tmp;		//定义变量tmp用于存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
int main(void)
{
	int a=1;
	int b=2;
	swap(a,b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

代码输出:

a=1 b=2

从代码输出结果来看,a和b还是原来的参数,并没有交换到。那么我们换做引用的方式处理看看结果会发生什么?

#include<iostream>
using namespace std;
//函数参数不使用引用
void swap(int a,int b)
{
	int tmp;		//定义变量tmp用于存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函数参数使用引用
void refSwap(int &a,int &b)
{
	int tmp;		//定义变量tmp用于存放a的值
	tmp = a;
	a = b;
	b=tmp;
}

int main(void)
{
	int a=1;
	int b=2;
	refSwap(a,b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

代码输出:

a=2 b=1

使用引用的方法进行交换,看到代码结果a和b已经互换的位置,因此可以总结出,如果需要操作(改变)对象本身,那么需用引用处理,当然也可以用指针的方式进行处理,但是书写对比引用会相对麻烦,所以一般都是引用。不信可以对比看看。

#include<iostream>
using namespace std;
//函数参数不使用引用
void swap(int a,int b)
{
	int tmp;		//定义变量tmp用于存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函数参数使用引用
void refSwap(int &a,int &b)
{
	int tmp;		//定义变量tmp用于存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函数参数使用指针
void ptrSwap(int *a,int *b)
{
	int tmp=*a;
	*a = *b;
	*b=tmp;
}
int main(void)
{
	int a=1;
	int b=2;
	ptrSwap(&a,&b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

代码输出:

a=2 b=1

从上述的代码看出,引用和指针传参的方式都能使a和b数据进行交换,但是从书写上来看指针会相对复杂且代码难理解,因此建议使用引用的方式对对象本身进行处理。


五、引用作为函数返回值

如上所说引用可以用作函数参数,那么引用也可以作为函数返回值。

不过注意的是引用作为函数返回值,不能返回局部变量的引用,比如:

#include<iostream>
using namespace std;
//引用作为函数返回值类型
int &data(void)
{
	int num = 100;		//num数局部变量
	return num;			//函数返回什么变量,那么引用的就是该变量的别名
}
int main(void)
{
	int &ret = data();	//ret是函数data()中变量num的别名
	cout<<"ret="<<ret<<endl;
}

代码输出:

ret=32767

从代码输出结果来看返回的是值跟预期结果的值不一致,因此可以知道如果返回的是局部变量的引用,那么编译器会把返回的结果处理成一个随机值,主要原因是因为局部变量会在返回的过程中进行销毁,从而导致局部变量丢失。

那么如何解决这个问题?通常我们可以使用静态变量,在变量前加上关键字static。

#include<iostream>
using namespace std;
//引用作为函数返回值类型
int &data(void)
{
	static int num = 100;		//变量num前加上关键字statci,使num变成静态变量
	return num;			
}
int main(void)
{
	int &ret = data();	//ret是函数data()中变量num的别名
	cout<<"ret="<<ret<<endl;
}

代码输出:

ret=100

这时候代码输出结果就跟预期一致啦,当然我们也可以把变量num定义成全局变量,从而达到正确结果。

ps:补充如果函数返回值作为左值,那么函数到返回类型必须是引用。

#include<iostream>
using namespace std;
//函数到返回值作为左值
int &data(void)
{
	static int num = 100;	
	return num;			
}
int main(void)
{	
	int num1=data();
	int num2=data()=200;
	cout<<"num1="<<num1<<endl;
	cout<<"num2="<<num2<<endl;
	return 0;
}

代码输出:

num1=100
num2=200

那么如果函数返回值用作左值,但是函数返回类型不是引用的话,函数返回值将无法用作左值,编译器会报错。

六、引用的本质

引用的本质在C++内部实现是一个指针常量,C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占的空间大小和指针相同,只是这个过程编译器内部实现,用户不可见。

int num = 10;
int &a = num;		//a就是num的别名
//编译器内存转换:int* const a = &num;

a=100;				//等价与num=100

总结

1:引用是什么?->引用(reference)为对象起了另外一个名字。
2:引用怎么定义?->使用操作符&。
3:引用定义的注意事项:引用必须进行初始化;引用一旦初始化就不能修改别名。
4:引用作为函数返回值需要注意什么?->不能返回局部变量的引用。
5:使用什么方法可以解决返回变量的引用?->使用静态变量在数据类型前加上关键字static或定义全局变量。
6:引用用作函数参数有什么效果?->操作对象本身而不会产生副本,从而提升程序的时间效率。
7:还有个常引用后面复习到有关类的知识点时在补充(const tt &b)。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页