【C++】【2】忍受不了指针就换引用

目录

1 引用的基本使用

2 引用的注意事项

2.1 引用必须初始化

2.2 引用在初始化之后,不可以改变

2.3 引用做函数参数 

2.4 引用做函数的返回值

2.5 引用的本质 是一个指针常量

2.6 常量引用 修饰形参防止误操作

2.7 奇怪的引用

2.8 引用做函数形参和返回值的效率

2.8.1 引用做函数形参的效率

 2.8.2 引用作为函数的返回值


1 引用的基本使用

引用不是定义一个变量,而是给一个变量取别名

数据类型& 别名  =  实体

int main()
{
	int a = 10;
	int& b = a;
	int& c = a;
	int& d = a;
	int& e = a;
	e = 100;
	cout << "a的值为" << a << endl;
	return 0;
}

调试结果:

 输出结果:

  • 可以看到定义的a的引用别名b,c,d,e后,他们的地址都与a相同,也与a的指针所存储的a的地址相同,说明引用并不是定义了新的变量,而是给已存在的变量取了一个别名。
  • 可以看到我们通过e修改了a的值,所以他们都是同一块内存空间,可以使用a操作这个空间,也可以使用b,c,d,e。

2 引用的注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

2.1 引用必须初始化

可以创建一个别名,但是不为他分配一个实体吗?答案是:不行。

代码测一下,发现编译器报错了,所以引用在创建的时候必须分配实体进行初始化

2.2 引用在初始化之后,不可以改变

int main()
{
	int a = 10;
	int b = 20;
	int& c = a;

	c = b;
	cout << c << endl;
	c = 15;
	cout << c << endl;
	cout << b << endl;
	return 0;
}

我们定义了两个整型变量a,b我们为a起一个别名c,我们能不能让b的别名是c,我们试一下,程序运行起来发现c=20,是因为c是b的别名吗,我们修改一下c的值,看看a和b谁会修改,结果我们发现修改的是a,那么c=b就是将b的值赋给c,并不是改变引用的对象。从上面的调试过程中的地址,我们也能看出来c是a的别名,而b不是。

2.3 引用做函数参数 

函数传参的时候,可以利用传引用的方式修改实参的值,我们就可以暂时抛弃指针

void refSwap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	refSwap(a, b);
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;
	return 0;
}

输出结果:

a和b的值发生了交换,相对于指针是不是还是挺简单那,这是因为引用的底层就是通过指针实现的,后面会介绍。

2.4 引用做函数的返回值

int& refFunc(int a, int b)
{
	int c = a + b;
	int& ref = c;
	return ref;
}

int main()
{
	int& a = refFunc(10, 20);
	cout << "打印a的值:" << endl;
	cout << a << endl;
	return 0;
}

输出结果:

诶,怎么输出结果是个随机值呢?这是因为在上面的代码中我们返回了局部变量的引用,之前介绍过,函数调用的时候会在栈上创建函数栈帧,这时候ref和c是创建在refDunc函数的函数栈帧上,但是函数调用完之后,函数栈帧销毁了,里面的ref引用的c也没了,c是一块不属于我们的空间,所以我们访问了不属于我们的空间,出错啦!

int& refFunc(int a, int b)
{
	static int c = a + b;//让c存储在静态区,函数调用完不释放,加长c的生命周期
	int& ref = c;
	return ref;
}

int main()
{
	int& a = refFunc(10, 20);
	cout << "打印a的值:" << endl;
	cout << a << endl;
	cout << a << endl;
	cout << a << endl;
	cout << a << endl;
	return 0;
}

输出结果:

因为c在函数栈帧销毁的时候释放掉了,我们只需要让函数结束的时候这块空间不释放,所以我们在c前面加一个static让c存储在静态区,函数栈帧销毁的时候c还存在,这样返回引用是没问题的。

2.5 引用的本质 是一个指针常量

引用的本质是一个指针常量。(指针常量)指针的指向不可以修改

int main()
{
	int a = 10;
	int& b = a;
    b = 20;
	return 0;
}

我们可以发现定义a的别名b的时候,是使用了一个指针,所以引用的本质就是指针,只不过C++编译器帮我们实现了。那编译器是如何实现的呢?其实int&b=a,实现的时候使用了一个指针常量,int* const b = a,这样不可以改变b的指向,所以引用是不可以改变实体的,我们需要修改的时候就是对指针b进行解引用。

int main()
{
	int a = 10;
	int& b = a;
	//int* const b = &a;
	b = 20;
	//*b = 20;
	return 0;
}

2.6 常量引用 修饰形参防止误操作

常量引用一般是用来修饰形参,防止修改形参影响实参

void Test01(const int& a)
{
	int b = a;
	//a = 10;
}

上面的代码,如果只是想通过引用进行赋值,而不想改变实参的值,我们就需要在形参前面加上const防止我们不小心修改引用,影响实参,如果我们想修改引用的值,那就不需要const

2.7 奇怪的引用

这个标题为什么叫奇怪的引用呢?我们一起来看一下代码。

int main()
{
	int a = 10;
	double b = 1.1;
	b = a;
	cout << b << endl;
	double& d = a;
	return 0;
}

 为什么int类型的a可以赋值给double类型的b,可以引用就不行呢?奇怪了!

其实是,a在赋值给b的时候并不是将a直接赋值给b,编译器先生成一个目标类型的临时变量存着a的值,然后把这个临时变量再赋给b,因为编译器给的这个临时变量具有常量性,所以a没法直接赋给double类型的引用,常量只有读取的权限,而double有可读可写的权限,这里属于权限放大,权限放大是不行的,权限缩小就可以,所以这里只需要将double的权限缩小到只读就可以,于是我们在double&前面加上一个const修饰一下,这样就没问题了。

int main()
{
	int a = 10;
	double b = 1.1;
	b = a;
	cout << b << endl;
	const double& d = a;
	return 0;
}

2.8 引用做函数形参和返回值的效率

2.8.1 引用做函数形参的效率

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
 
int main()
{
	TestRefAndValue();
}

输出结果:

我们发现,传值会产生临时拷贝,效率是远远不如传引用的,而且引用传参的时候不需要取地址,建议使用引用作为函数的形参。

 2.8.2 引用作为函数的返回值

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "返回值   time:" << end1 - begin1 << endl;
	cout << "返回引用 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();

}

输出结果: 

为什么他们的效率会相差这么大呢,因为如果函数是值返回,一般是将这个数据先存到一个临时的区域里(比如寄存器),然后将寄存器的值再赋给函数调用后接收返回值的那个变量,寄存器的读和写都需要时间,所以效率较低;而引用作为返回值,就没有拷贝的这个过程了,而是直接将返回的值取一个别名赋给函数调用后接收返回值的那个变量。
 

注意!如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值