【C++---const引用】数组进行指针引用传递给函数error: non-const lvalue reference of type ‘int*&‘ to an rvalue


复习回顾一下:对于指针类型的参数传递一般就是两种方式,一种是通过指针形参,一种是通过对指针的引用。由于数组名本身可看作是一个指针,于是借助数组来进行测试。指针形参比较常见,一般也运用地比较熟练。但在测试通过引用来传递的时候出现了一个编译器错误:error: cannot bind non-const lvalue reference of type 'int*&' to an rvalue of type 'int*'|,提示无法将非const的引用绑定到指针指向的对象。 根据错误提示可以知道可能是引用没有用上const引用出错了,加上const之后确实不报错了,但内部是什么原因呢?

原因

在传递数组时,实际上传递的是指针。而数组转为指针时内部需要一个自动转换,会产生一个临时量。这个临时量是个右值,不能直接赋值给非常量引用,但是可以赋值给常量引用或者右值引用(&&)。

此时需要在传递参数时指定传递的指针是const指针,让这个指针的const引用绑定到自动转换产生的临时量上。因此需要在形参上加上const。

【形参中加上const变为const引用不仅局限于传递数组,其他类型也是同样的道理,比如拷贝构造函数中的形参就是const引用类型的,为何需要引用且为const引用?

除了在形参上加上const解决这个问题之外,还可以选择自己手动转换类型,把数组赋给一个自定义的指针,再将这个自定义的指针传给函数参数中指针的非常量引用。

案例如下:

类型转换

一般我们认为数组名本身就是一个指针,指向数组第一个元素的地址。

之所以可以认为是一个指针,是因为内部总是会自动转换(把数组转为指向首元素地址的指针)。

在了解自动转换之前,先看看手动转换替代自动转换实现的函数传参的例子。

手动转换

//手动转换
int p[5]={2,1,2,3,4};
int *x=p;
#include <iostream>
using namespace std;

void reset(int * &i){
    i[1]=1200;
}

int main()
{
    int p[5]={2,1,2,3,4};
    int *x = p;
    reset(x);
    for(int i=0;i<5;++i){
        cout<<x[i]<<endl;
    }
    return 0;
}

在这里插入图片描述
经过将p数组手动转换成x指针,再将指针的引用传递给reset函数,可以成功的对reset函数外的p数组对象进行修改。

手动转换没问题,再看自动转换:

自动转换

#include <iostream>
using namespace std;

void reset(int * &i){
    i[1]=1200;
}

int main()
{
    int p[5]={2,1,2,3,4};
    reset(p);
    for(int i=0;i<5;++i){
        cout<<p[i]<<endl;
    }
    return 0;
}

发现编译出错
在这里插入图片描述
编译器报错:因为我们直接传递数组给reset,内部自动把数组转换成指针,然后对自动转换的指针进行非常量指针引用,提示需要改成常量引用来引用右值(int *)。

问题的关键在于内部自动转换的时候产生了一个临时量对象


关于临时量

在《C++ PRIMER》中对const引用介绍时举了一个例子:

double dval = 3.14;
const int &ri = dval;

这里ri被要求绑定一个double型的数,因此编译器进行了自动转换生成了临时量对象temp

const int temp = dval;
const int &ri = temp;

这样ri绑定了一个临时量对象,由于这个临时量对象(临时量的建立需要消耗资源,这也是右值引用&&出现的原因。)是常量且ri并没有实际绑定dval,所以ri不会随着dval的改变而改变。如果把int改成double则ri就实际绑定了dval,且由于是const引用,所以不能通过ri来改变dval,但是可以通过其他方式来修改dval,比如直接修改。

那如果ri不是const引用:
在这里插入图片描述

(题外话)当然,由于这里声明ri的引用的const是底层const (《C++> Primer》:“顶层const可以表示任意对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。”“用于声明引用的const都是底层const”)> ,
理论上ri是可以绑定到别的对象上的。但是C++语言不允许随意改变引用所绑定的对象,引用在内部的本质是一个常量指针!

关于常量引用(const的引用)

实际上,常量引用绑定到内部生成的临时量对象上非常常见,通常只有两种情况常量引用会绑定到实际的对象上:

  1. 常量引用的初始值为const对象,且该对象类型与常量引用类型相同(如const int i ; const int &r = i; )
  2. 常量引用的初始值为非const对象,且该对象也与常量引用类型(除去const的类型)相同(如int i; const int &r = i;)。这种情况下,绑定的非const对象内容可以用其他普通引用改变。

需要注意的是:非常量引用是不允许绑定到临时量上的,更进一步地说,非常量引用不允许绑定到右值上(临时量、字面值常量等)。这便是上面自动转换的例子中编译器报错的原因。

换个角度想,如果将非常量引用绑定临时量,那么意味着你可能会对临时量做改变(因为是引用)而且你可能还会将修改后的临时量拿来继续使用,但是临时量在系统内部,根本没法拿出来继续使用,所以用一个非常量引用绑定临时量是没有任何意义的。

C++规定参考https://en.cppreference.com/w/cpp/language/reference_initialization

左值右值参考https://blog.csdn.net/z_yu_yun/article/details/58149590


回到上面给的例子中,

手动转换时,将数组变成了int类型的指针,然后传入函数中,这时不管函数的形参中的引用是常量引用还是非常量引用,都可以绑定到实际的对象上,也就是绑定到了x指针上,所以可以正常地运行,也可以对数组进行实际的修改。

自动转换时,函数的形参中的引用为非常量引用时无法正常运行。如【临时量】中所述,没法绑定到临时量中,所以编译器直接报错。如果将形参的引用改成常量引用(顶层const引用)。常量引用绑定了自动转换产生的临时量对象,这意味着常量引用绑定的这个指针指向了数组首元素地址,且不会改变,故后续可以正常运行。

如下所示:

在这里插入图片描述

加上const引用之后不再报错,因为此时const引用可以直接绑定到内部自动转换产生的临时指针上。

但是reset()直接修改了数组内的内容,似乎改变了我们对const引用表明传递的内容不会被修改的认知?

const引用的对象不是不能被修改了吗,这里为什么被修改了?

其实我们的认知没有错,对于普通的类型,const引用的形参表示在函数内部不会对传递的对象进行修改。

但是这里是指针类型的const引用,我们看到的const是引用所绑定的指针的【顶层const】,针对的是引用所绑定的指针对象,不可以指向其他地址,而不是针对所存放的int类型数据的【底层const】,所以我们可以对数组修改并作用到main中的p数组上。

此时如果加上指针的底层const,则会与我们的认知相符:
在这里插入图片描述

编译器报错,const生效,不能修改read-only的对象,所以我们企图修改 i[1] 是错的。

这里的两个const、 * 和 & 放在一起好像很复杂。
其实还是从右往左读:
首先有一个参数i,
然后对这个参数有一个引用,
接着的const表示对这个参数的引用是const引用,
接着的int *表明这个参数i的类型是一个int型的指针,
到这里表明有一个int*型的指针参数i,对这个参数有一个const的引用,所以这个const是表明这个指针所指是不可修改的,只能指向这个固定的地址。
最后的const表示这个指针指向的数据是不可修改的。

问题又来了:

不是说是const引用吗?为什么又变成了引用所绑定的指针的【顶层const】?

一般来说,顶层const和底层const都是针对指针而言的,但是也是针对复合类型而言的。

对于引用来说,也有顶层const和底层const,只不过c++规定了所有的引用默认都存在顶层const,这意味着引用的对象不能改变,因为引用本身就不是一个对象,只是他所绑定的对象的别名。

所以我们通常所说的const引用都默认了是引用的底层const。

在这里的形参中int * & i,分别从指针和引用的角度来看,各有两层const:

引用:

  • 引用的顶层const,是c++默认规定好的,表示引用不可被重新绑定到另一个对象上,如果非要显示地写出来就是把const加到&的后面,但是这是错误的语法;
  • 引用的底层const,也就是我们一直说的const引用,表示引用的对象的内容不能通过该引用被修改,写出来就是int * const & i

指针:

  • 指针的顶层const,表示指针的指向不能被修改,实际上就是引用的底层const,这两层重叠在一起了,因为int * const & i也表达了这里的const就是指针的顶层const。
  • 指针的底层const,表示指针指向的底层的普通类型数据不能被修改,写出来就是const int * & i

因此上面的四层有两层重叠了,变成了实际的三层,有一层是c++默认的,变成了我们看到的两层,两个const。

最后,引用《C++ PRIMER》的一句话:

事实上:不使用常量引用会极大限制函数所能接受的实参类型,(因为我们)不能把const对象、字面值或者需要类型转换的对象传递给非常量引用。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值