【C++入门】引用(较详细)

发布于:2022-12-02 ⋅ 阅读:(501) ⋅ 点赞:(0)

         我们都知道一个人不仅有自己的名字,还会有一个甚至多个外号。就像张三,不仅“张三”可以表示他,“法外狂徒”这个外号也可以表示他。

4a956da0c2b54d119f06170ab0188173.png

 

目录

 

 

1.概念: 

2.引用示例:

 3.使用引用的注意事项

4.使用场景

(1)做参数

(2)做返回值

(3)值和引用的作为返回值类型的性能比较

5.引用和指针的不同点


 

1.概念: 

         引用(&)就是相同的道理,并不是新定义了一个变量,而是给已存在的变量取了一个外号(别名)。编译器自然也就不会为引用变量开辟空间,所以它和引用的变量共用一块内存空间

2.引用示例:

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

int main()
{
	int a = 0;
	int& aa = a;//引用示例

	a = 10;

	std::cout << a << std::endl;
	std::cout << aa << std::endl;
	return 0;
}

8a9bd0f709224e09972cabaa7f82a58f.png

 3.使用引用的注意事项

  • 引用变量必须与引用实体同类型

 给一个人取外号也是要有来源的,张三之所以叫“法外狂徒”,是因为他杀人放火无所不干,与”法外狂徒“一样是无视法律的。当你管一个乐于助人的三好市民叫“法外狂徒”就显得有些许不合适了。

int main()
{
	int a = 0;
	char& aa = a;//类型不同,编译时会出错

	a = 10;

	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	return 0;
}
  • 引用在定义时必须初始化

 外号是需要有对应的人的,空有一个外号没有人与之对应,也是很奇怪的。

int main()
{
	int a = 0;
	int& aa;//没有初始化,会报错

	a = 10;

	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	return 0;
}
  • 一个变量可以有多个引用

 张三既可以有“法外狂徒”这个外号,也可以有“张狗蛋”这个外号。

int main()
{
	int a = 0;
	int& aa = a;
	int& aaa = a;

	a = 10;

	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	std::cout << "aaa:" << aaa << std::endl;
	return 0;
}
  • 一个引用变量只能引用一个实体 

 我家的狗叫“狗蛋”,张三也叫“狗蛋”。那我喊“狗蛋”,叫的是我家的狗还是张三呢,这是不是对我家的狗不太公平呢?(doge

int main()
{
	int a = 0;
	int b = 1;
	int& aa = a;
	int& aa = b;//重定义,会报错

	a = 10;

	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	return 0;
}
  • 不能对常量进行引用 
int main()
{
	int a = 0;
	//int& aa = 10;//10为常量,不能引用

	a = 10;

	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	return 0;
}
  •  引用变量权限不能大于实体

 外号朋友之间是可行的,但不具有法律效益,所以姓名的权限是大于外号的。

int main()
{
	int a = 0;
	int& aa = a;//权限等于实体,可以引用
	const int& aaa = a;//权限小与实体,可以引用

	const int b = 0;
	int& bb = b;//权限大于实体,不能引用
	
    //输出
	std::cout << "a:" << a << std::endl;
	std::cout << "aa:" << aa << std::endl;
	std::cout << "aa:" << aaa << std::endl;
	std::cout << "b:" << b << std::endl;
	std::cout << "bb:" << bb << std::endl;
	return 0;
}

4.使用场景

        (1)做参数

void swap(int& a, int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

这里的a与b都是实参的别名,改变a,b的值就相当于改变对应实参的值,能很好地进行数据间的交换,就不用再传实参的地址,这样可以有效地减少代码的复杂度了。

        (2)做返回值

int& Count()
{
    int n = 0;
    n++;
    // ...
    return n;
}

 注意:

  • 函数运行时,系统需要给函数开辟独立的栈空间,用来保存该函数的形参,局部变量以及一些寄存器信息等。
  • 函数运行结束后,该函数的栈空间就被系统回收了。
  • 空间被回收指该块栈空间暂时不能使用,但内存还在。

 就像是住旅店时,房间到期退房后,已经不能使用这个房间了,但是房间还在没有被拆除。

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

f641ec3e5e634caa8c0c34e5d7097846.png

 那么,引用做返回值时到底有什么作用呢?

答案是:

1.减少拷贝,提高效率

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。


(3)值和引用的作为返回值类型的性能比较

#include <time.h>
struct A 
{ 
	int a[100000];//40万字节 
};
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();
	return 0;
}

62b7e0e885c14ef2a46b38a67451bc6a.png

 由运行结果可以看出,以引用做返回值比以值作为函数参数快了不是一点点

2.修改返回值

//偶数*2
for (size_t i = 0; i < SLSize(&sl); ++i)
{
    if (SLAt(&sl,i) % 2 == 0)
    {
        SLAt (&sl,i) *= 2;
    }
}

 这一特性在循序表会有较大用处。

5.引用和指针的不同点:

  • 引用概念上定义一个变量的别名,指针存储一个变量地址。
  • 引用在定义时必须初始化,指针没有要求
  • 一个引用变量只能引用一个实体 ,而指针可以在任何时候指向任何一个同类型实体
  •  没有NULL引用,但有NULL指针
  • 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  • 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  • 有多级指针,但是没有多级引用
  • 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  • 引用比指针使用起来相对更安全

 

 

本文含有隐藏内容,请 开通VIP 后查看