2 C++中的引用

C++中的引用

上节说到,变量名实际上是一段连续存储空间的别名。很显然我们可以将其命名为其它名字,就像我们有乳名、小名一样。
C++引入了引用的概念。

  • 引用可以看作一个已定义变量的别名
  • 引用的语法 type& name = variate;
  • 普通引用在声明时必须用其它的变量进行初始化。用值进行初始化也不行(即int&a = 1; 会报错)
  • 引用作为函数参数时,声明时候不需要进行初始化
#include <stdio.h>
int main(int argc, char *argv[])
{
	int original = 1;
	int& new_name = original;//c语言无引用的概念,用gcc会编译报错。
	new_name = 2;
	printf("original = %d, new_name = %d\n", original, new_name);
	printf("&original = %p, &new_name =%p\n", &original, &new_name);
	return 0;
}

上面程序中,int& new_name = original; 用original别名叫new_name,当new_name赋值后,对于的存储空间的值也会变化,故original 的值和new_name值一样。相应的,他们的内存地址打印也相同,如下图:
在这里插入图片描述

引用的意义

  • 引用作为其它变量的别名而存在,因此在一些情况下可以代替指针
  • 引用相对于指针来说,具有更好的可读性和实用性
#include <stdio.h>

//指针实现交换函数
void swap1(int* pa, int* pb)
{
	int c = *pa;
	*pa = *pb;
	*pb = c;
}

//引用实现交换函数
void swap2(int& a, int& b)
{
	int c = a;
	a = b;
	b = c;
}

int main(int argc, char *argv[])
{
	int var_1 = 8;
	int var_2 = 10;
	swap1(&var_1, &var_2);
	printf("after swap1, var_1 = %d, var_2 = %d\n", var_1, var_2);
	swap2(var_1, var_2);
	printf("after swap2, var_1 = %d, var_2 = %d\n", var_1, var_2);
	return 0;
}

上面程序中,引用实现的交换函数可读性更好。
swap1(&var_1, &var_2); 如果不看函数实现,像是交换两个变量的地址。
swap2(var_1, var_2); 从函数写法,像是交换两个变量。不像指针可读性差。
在这里插入图片描述

const 引用

  • 在C++中可以声明const 引用,语法 const Type& name = var;
  • const 引用让变量拥有只读属性,注意,只有定义的别名拥有只读属性,不会影响到正名。
#include <stdio.h>

int main(int argc, char *argv[])
{
	int a = 8;
	const int& b = a;//变量a没有只读属性,当前只有变量b才有只读属性。
	//b = 10; //打开这里,报错 error: assignment of read-only reference ‘b’
	a = 20; //原来的变量还是可以赋值,不拥有只读属性
	printf("a = %d, b = %d\n", a, b);
	int *p = (int*)&b;
	*p = 10;	
	printf("a = %d, b = %d\n", a, b);	
	return 0;
}

在这里插入图片描述

  • 当使用常量对const引用进行初始化时(const int & a = 1; 我们知道直接使用常量赋值普通引用,编译器会报错, int& a = 1 会报错。),C++编译其会为常量分配空间,并将引用名作为这段空间的别名
  • 使用常量对const引用初始化后,将生成一个只读变量,而不是真正的常量
#include <stdio.h>

int main(int argc, char *argv[])
{
	const int& a = 8; //这里编译器会为这个常量分配存储空间。去掉const 编译器会报错 cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’
	int *p = (int *)&a;
	*p = 10;//改变常量空间的地址内容
	printf("a = %d\n", a);	
	return 0;
}

在这里插入图片描述

引用的实现方式

引用有自己的存储空间吗?下面通过一个实例来验证。

#include <stdio.h>

struct C
{
	int& a;//等价于 int* const a;
	int& b;//等价于 int* const b;
};

int main(int argc, char *argv[])
{
	printf("sizeof(C) = %ld\n", sizeof(C));	
	return 0;
}

在这里插入图片描述
两个int型引用组成的结构体,打印的正好是2个int型变量的长度,从编译器行为来看,引用有自己的大小。

  • 引用在C++中的内部实现是一个常指针 type& name 等价于 type* const name。 type 指的是变量的类型,也可以指定义的新class类型, 例如:char& a 等价于 char* const a .
  • C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用占用的空间大小与指针占用空间大小相同。
  • 从使用者的角度,引用会让人误以为只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏。

下面再用一个例子来说明引用有自己的存储空间:

#include <stdio.h>

struct T
{
	int& a;
	int& b;
	int& c;
};

int main(int argc, char *argv[])
{
	int a = 1;
	int b = 2;
	int c = 3;
	T va = {a, b, c};
	printf("&a = %p\n", &a);	
	printf("&b = %p\n", &b);
	printf("&c = %p\n", &c);
	printf("&va = %p\n", &va);
	printf("sizeof(va) = %ld\n", sizeof(va));	
	return 0;
}

在这里插入图片描述
从打印结果来看,引用a,b,c都有自己的独立存储空间,va变量的空间为24字节,变量与变量内部的引用在内存上也是独立的。

当函数返回值为引用时

  • 返回栈变量
    1、不能成为其它引用的初始值
    2、不能作为左值使用
  • 返回静态变量或全局变量
    1、可以成为其他引用的初始值
    2、即可作为左值使用,也可作为右值使用
#include <stdio.h>

int& f1()
{
	static int a = 8;
	return a;//返回静态局部变量a的内存空间,f1()函数作为该内存空间别名
}

int& g1()
{
	int b = 6;
	return b;//返回局部变量b的内存空间,f1()函数作为该内存空间别名
}

int main(int argc, char *argv[])
{
	int& c = f1();//返回静态局部变量a的存储空间别名给C
	int& d = g1();//局部变量b内存空间别名,给到引用d. d代表局部变量b的内存空间。
	f1() = 10;//f1() 作为静态局部变量a的别名,然后给内存空间赋值10.相当于给静态局部变量赋值为10.
	printf("c = %d\n", c);	
	printf("d = %d\n", d); //打印被释放的空间别名值,出现段错误。
	printf("f1() = %d\n", f1());//去除掉上一行语句后,这里打印10.	
	return 0;
}

在这里插入图片描述
当引用返回的是局部变量时,局部变量所在的函数运行完成,会释放掉该局部变量,而我们去访问 被释放的引用存储空间时,会出现不可控的情况,这段内存有可能被系统回收给其他变量使用。
在g++上,printf(“d = %d\n”, d); 编译器发生了警告,当运行程序时,访问了被释放的存储空间别名,出现段错误。某些编译器和平台上,会打印出随机值。

回答上一节的问题,C++对三目运算符做了什么?

  • 当三目运算符可能返回值都是变量时,返回的值是变量的引用。
  • 当三目运算符可能返回中有常量时,返回的是值。
    int a = 6;
    int b = 8;
    ( a < b ? a : b) = 9;//语句正确,返回a 或者b的引用,可以作为右值使用。
    ( a < b ? a : b) = 9;//语句错误,返回值中有常量,返回的是值,不能作为右值使用。

小结

  • C++中的引用可以看作变量的别名来使用
  • C++中的常引用可以使得一个变量拥有只读属性
  • C++中的常引用可以用常量初始化而得到一个只读变量
  • C++中引用的本质是一个指针常量

思考

在C++中不允许定义引用数组 Type& array[10],为什么?
如何定义一个数组的应用?如何定义一个函数的引用?
数组引用和数组指针有什么区别?函数引用和函数指针又有什么区别?

双向搜索算法是一种从起点和终点同时进行搜索的算法,可以有效地减少搜索的时间和空间复杂度。在MATLAB中,可以使用双向搜索算法来解决一些图论问题,如最短路径问题等。 双向搜索算法的基本思路是从起点和终点同时开始搜索,每次从两个方向中选择一个距离当前节点最近的节点进行扩展,直到两个搜索方向相遇。在搜索过程中,需要记录每个节点的前驱节点和到起点/终点的距离,以便在搜索结束后回溯出最短路径。 以下是一个简单的MATLAB实现示例: ```matlab function [path, dist] = bidirectional_search(graph, start, goal) % graph: 图的邻接矩阵 % start: 起点 % goal: 终点 n = size(graph, 1); % 节点数 visited1 = false(n,1); % 起点方向已访问的节点 visited2 = false(n, 1); % 终点方向已访问的节点 pred1 = zeros(n, 1); % 起点方向每个节点的前驱节点 pred2 = zeros(n, 1); % 终点方向每个节点的前驱节点 dist1 = inf(n, 1); % 起点方向每个节点到起点的距离 dist2 = inf(n, 1); % 终点方向每个节点到终点的距离 queue1 = start; % 起点方向的队列 queue2 = goal; % 终点方向的队列 visited1(start) = true; visited2(goal) = true; dist1(start) = 0; dist2(goal) = 0; while ~isempty(queue1) && ~isempty(queue2) % 从起点方向扩展节点 curr1 = queue1(1); queue1(1) = []; for i = 1:n if graph(curr1, i) > 0 && ~visited1(i) visited1(i) = true; pred1(i) = curr1; dist1(i) = dist1(curr1) + graph(curr1, i); queue1(end+1) = i; if visited2(i) % 相遇,回溯路径 path = backtrack_path(pred1, pred2, i); dist = dist1(i) + dist2(i); return; end end end % 从终点方向扩展节点 curr2 = queue2(1); queue2(1) = []; for i = 1:n if graph(i, curr2) > 0 && ~visited2(i) visited2(i) = true; pred2(i) = curr2; dist2(i) = dist2(curr2) + graph(i, curr2); queue2(end+1) = i; if visited1(i) % 相遇,回溯路径 path = backtrack_path(pred1, pred2, i); dist = dist1(i) + dist2(i); return; end end end end % 没有找到路径 path = []; dist = inf; function path = backtrack_path(pred1, pred2, i) % 回溯路径 path = [i]; while pred1(i) > 0 i = pred1(i); path = [i, path]; end i = pred2(path(end)); while i > 0 path = [path, i]; i = pred2(i); end end end ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值