c++引用详解

目录

1.引用的概念

2.引用的特性

3.常引用

4.使用场景

        4.1做参数

        4.2做返回值 

5.传值传引用效率比较

        5.1传值和传引用性能比较

        5.2值和引用做返回值性能比较        

6.引用和指针的区别



1.引用的概念

        引用不是新定义了一个变量,而是给已经存在的变量取一个别名,编译器不会为引用变量开辟新的内存空间,它和它的引用共用一块内存空间。

        比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。      

        类型+&引用变量名(对象名)=   引用实体

如下:

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

注意引用类型和引用实体必须是同种类型。  

2.引用的特性

1.引用必须初始化

2.一个变量可以有多个引用实体

3.引用一旦引用一个实体,再不能引用其他实体 

例如: 

#include<iostream>
int main()
{
	int a = 12;
	int& b = a;
	int& c = b;
	int& d = c;
	std::cout << a << b << c << d;
	return 0;
}

上面这个代码说明 一个变量可以有多个引用实体,那么它们在内存中是怎么分布的呢?让我们一起看看!

打开监视我们可以看到变量a,b,c,d,的地址是一样的,变量a有多个实体。可以这样理解这几个引用都操控着同一块内存空间,如图: 

引用必须初始化,如果引用不初始化的话,程序就会报错,因为引用不初始化的话,就不知道这个引用是谁的引用了。例如: 

#include<iostream>
int main()
{
	int a = 12;
	int& b ;//引用未初始化
	int& c ;
	int& d ;
	std::cout << a << b << c << d;
	return 0;
}

引用一旦引用一个实体就不能引用其他实体,看代码: 

#include<iostream>
int main()
{
	int a = 12;
	int& b =a;//
	int c = 20;
	b = c;
	std::cout << a << b << c;
	return 0;
}

        这里的b = c是将b重新换成c的引用吗?不是的,只是将变量c的值赋给a。如图: 

我们可以看见b和c的地址不是同一块空间。 

3.常引用

#include<iostream>
#include<typeinfo>
using namespace std;
int main()
{
	double a = 2.0;
	const int& b = a;
	const int c = 0;
	//int& d = c;//权限的扩大
	int f = 3;
	const int g = f;//权限的缩小
	const int* p = &f;
	//int* p1 = p;//权限的扩大
	int l = 20;
	int* p3 = &l;
	const int* p4 = p3;//权限的缩小
	int num1 = 20;
	const int num2 = num1;
	const int num3 = 30;
	int num4 = num3;
	//权限的扩大和缩小只适合于指针和引用。
	return 0;
}

        如果引用的实体有const修饰,那么引用也要加上const否则程序就会报错,这是因为const修饰的实体具有常属性,这个实体的可读不可写,所以引用的时候不能比实体的权限大。例如: 

    const int c = 0;
    int& d = c;//权限的扩大 

        但是如果反过来,引用的实体是没有const修饰的,但是引用是被const修饰的,是可以的,这是权限的缩小。例如:

    int f = 3;
    const int g = f;//权限的缩小 

        上面的规则也适用于指针。例如:

    const int g = f;//权限的缩小
    const int* p = &f;
    int* p1 = p;//权限的扩大
    int l = 20;
    int* p3 = &l;
    const int* p4 = p3;//权限的缩小 

        那么想想引用变量名的类型可以和引用实体可以不同吗?来看看这段代码:

#include<iostream>
#include<typeinfo>

int main()
{
	double a = 2.0;
	 int& b = a;
	return 0;
}

        事实上这段代码会报错,因为引用对象的类型和引用实体之间的类型是不同的,但是只要在引用对象的前面,加上const就不会报错了如下: 

#include<iostream>
#include<typeinfo>

int main()
{
	double a = 2.0;
	const int& b = a;
	return 0;
}

        这是为什么呢?让我们一起来分析一下!

        引用不是直接发生的而是在引用的时候在系统中会创建一个临时变量,然后是通过临时变量完成引用的,因为临时变量具有常属性,所以在b的前面加上const修饰,编译器就不会报错了。这里实际上是对临时变量的引用,不是对变量a的引用。 如图:

4.使用场景

        4.1做参数

#include<iostream>
#include<typeinfo>
//using namespace std;
//引用做参数
void Swap1(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void Swap2(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 3;
	int b = 5;
	Swap2(a,b);
	return 0;
}

        上面两个函数都可以实现两个变量的交换, swap1是通过指针的方式来实现的,而Swap2是通过引用的方式来实现的,如何理解Swap2函数呢?我们知道引用是必须要初始化的,但是这里貌似也没有初始化,是不是有点不太好理解呀?其实,这里函数的形参只有调用的时候才会被创建,当调用Swap2函数时,操作系统为函数创建栈桢,此时形参才创建,然后main函数就会给形参传值,所以此时就发生了引用,如图:

        而Swap1则是函数调用的时候形参创建并且为形参开辟新的内存空间: 

         注意引用就是起别名,传参的时候不会为形参创建空间,形参是实参的引用。

        4.2做返回值 

        当函数的返回值是引用时又是怎么样的呢?

#include<iostream>
#include<typeinfo>
int& Count1()
{
	static int a = 1;
	++a;
	return a;
}
int Count2()
{
	static int a = 1;
	++a;
	return a;
}
int mian()
{
	int a = Count1();
	return 0;
}

        函数Count1就是引用做返回值,那么应该如何理解呢? 

         当返回值是引用的时候可以理解成就是系统创建了一个临时变量的引用然后返回了这个引用,实际上就是返回a,但是引用返回的时候临时变量的引用是没有创建临时空间的(引用不会新开辟空间),如图:

         Count2函数就比较简单了,就是返回变量的值,在函数返回时,系统会创建临时变量,然后将a的值赋给临时变量然后返回临时变量。如图: 

        你是否有疑问这里函数中的变量为什么要加static修饰?我们一起来看看这段代码:

int& Add(int num1,int num2)
{
	int sum = num1 + num2;
	return sum;
}
int main()
{
	int &a = Add(2,3);
	Add(3, 4);
	std::cout << a;
	return 0;
}

         这个程序输出的结果是什么呢?

        为什么会是这个结果呢?让我们一起来分析一下:

        首先main函数调用了int &a = Add(2,3);此时a的值是5,然后   又调用了 Add(3, 4);因为a是函数Add中sum 的引用,所以变量a的开辟的空间就是sum的空间,因为栈是向下增长的,两次调用Add函数又是连续的,所以两次调用函数为局部变量sum开辟的空间是相同的,所以第二次变量sum的改变就会让变量a改变。

        这就是使用static修饰变量的原因,static修饰变量,变量会存放在数据段里面,使变量的生命周期和 程序的生命周期一样长。

        总结:引用返回不会创建临时变量,返回的就是函数里面的a,因此不会额外开辟空间,可以提高程序的效率。 如果想要通过引用返回的话,这个变量要是出了这个作用域还存在就可以通过引用返回

5.传值传引用效率比较

          以值作为函数的参数或者返回值类型,在传参或者返回期间,函数不是直接传递(实参)参数或者直接将变量返回,而是传递一份临时拷贝的变量,因此用值做参数或者返回值类型,效率是十分低下的。尤其当传递的实参很大或者返回值的类型非常大的时候,函数的开销就会非常大,程序的效率就会很低  。

        5.1传值和传引用性能比较

#include<iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void main()
{
	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;
}

        通过实际运行上述代码,我们可以发现指针作为参数传参和传引用效率相差很大

有兴趣的同学可以自己试试。

        5.2值和引用做返回值性能比较        

#include<iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void main()
{
	// 以值作为函数的返回值类型
	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 << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

        同理, 通过运行上述代码,可以发现值返回和引用返回效率上相差很大。 引用返回的效率比传值效率高。

6.引用和指针的区别

        在语法概念上引用就是为已有的变量取别名,不会为引用对象开辟空间。引用对象和引用实体共用一块空间。 我们可以通过代码验证:

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int& b = a;
	cout << &a <<endl<< &b;
	return 0;
}

        在底层实现上其实引用也是有自己的空间的,因为引用是按照指针的实现方式实现的:

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int& b = a;
	int* p = &a;
	return 0;
}

        运行这个代码,然后打开汇编:

你会发现他们的汇编代码是相似的。 

        引用和指针的不同点:

        1.引用在语法上是不会额外开辟空间,指针存储变量的地址会开辟新的空间。

        2.引用在定义的时候必须初始化,指针可以不初始化。

        3.引用在初始化一个实体之后就不能再引用其他实体了,但是指针可以在任何时候指向任何同一类型的实体。

        4.没有空引用,但是有NULL指针。

        5.sizeof的意义不同,引用的结果是引用类型的大小,但是指针始终是4个字节(32位平台上)。

        6.引用自加1是引用的实体加1,而指针加1是指针向后偏移一个类型的大小。

        7.有多级指针但是没有多级引用。

        8.访问实体的方式不同指针需要解引用,引用编译器自己处理。

        9.引用相比指针使用更安全。

        注意上面所介绍的引用,涉及的都是左值引用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值