c++函数返回引用的问题

这两天写Vec时遇见返回引用的问题,觉得之前对这个问题的理解不够深入。所以,本文进一步讨论下我对这个问题的理解。
本文参考了[c++函数返回引用]一文,此文写的较为清楚,大家可以直接参看此文。

问题引入

重载Vec的[]运算符时的接口如下:

T& operator[]( size_type i ) { return data[i]; }
const T& operator[]( size_type i ) const { return data[i]; }

首先,这两个接口标签“看起来”一样,为什么能重载?
其次,第一个函数并没有该表对象的状态,为什么不写成如下形式:

T& operator[]( size_type i ) const { return data[i]; }//实际上,这个接口编译过不去,原因后面说。

分析

先回答第一个问题,这两个接口函数的参数列表看起来一样,实际不一样,因为成员函数的第一个参数是默认的,并且第二个函数有const的限制,所以,还原后的接口如下:

T& operator[]( T* this, size_type i ) { return this->data[i]; }
const T& operator[]( const T* this, size_type i ) const { return this->data[i]; }

这么看,这两个函数的标签就不一样了。

现在来回答第二个问题。首先需要明白一点的是,为什么要重载?如果不重载其实是不存在这个问题的。重载的原因是因为,存在常对象,常对象不能调用非const方法,所以,需要一个const版本的[]运算符。所以,这个版本必须得有。那么问题来了,如果第一个方法写成const,那么它和第二个方法的参数列表又变成一样的,此时,重载失败。并且,再仔细观察下面的这个接口:

T& operator[]( size_type i ) const { return data[i]; }//实际上,这个接口编译过不去,原因后面说。

这个编译为什么过不去,因为,这个接口还原之后如下:

T& operator[]( const T* this, size_type i ) const { return this->data[i]; }//实际上,这个接口编译过不去,原因后面说。

先补充下返回引用的概念:本质是返回地址

当函数返回引用类型的时候,没有复制返回值,而是返回对象的引用(即对象本身)。函数返回引用:实际上是一个变量的内存地址,既然是内存地址的话,那么肯定可以读写该地址所对应的内存区域的值,即就是“左值”,可以出现在赋值语句的左边。

所以,上面的接口,可以这么看:

T& operator[] = this->data[i]; // 先假设这么看

左边是一个引用,右边是一个常量,你不能用一个非常引用去绑定一个常量。

所以,从是否能重载以及语法本身来说,都不能那么写。

结论:const成员函数如果返回引用,必须返回常引用。

所以,这个地方不能教条,规则是没有错,但不能迷信规则,错的是用的人不分青红皂白滥用规则就会出现问题。


对于怎么理解函数返回引用的问题

看下面这段代码:

#include <iostream>
#define N 3

int arr[N];

int& f( int* arr, int i ){ //  普通对象接口
    return arr[i];
}

const int& f( const int* arr, int i ){ // 常对象接口
    return arr[i];
}

int main( void ){

    int arr[N];

    f( arr, 0) = 0; // int& f(arr, 0) = arr[0];此时, f(arr, 0)作为变量arr[0]的引用,相当于这个函数接口看做是返回值的引用。这个就是返回引用的函数。
    f( arr, 1) = 1;
    std::cout << arr[0] << std::endl;
    std::cout << arr[1] << std::endl;

    const int arr1[] = {9,8,7};
    int tmp = f( arr1, 0 );


    return 0;
}

函数返回引用:把函数接口看做返回值的引用,本质是返回函数地址,可读可写

进一步讨论

T& operator[]( size_type i ) { return data[i]; }
const T& operator[]( size_type i ) const { return data[i]; }

上面说明了,常成员函数,如果返回引用,必须返回常引用。因为在常成员函数体内,返回的对象是常量,所以,必须得用常引用去绑定。

但是,常指针,常引用都是可以绑定非常量的,此时通过常引用和常指针是不能修改变量,但是非常量本生可以被修改。

那么,我的问题是,第一个函数可不可以省略,只保留第二个,也就是只存在如下的形式。

const T& operator[]( size_type i ) const { return data[i]; }

可以,但是这样会无法无法进行写操作。对于非常量来说,常指针可以绑定非常量对象,然后通过常指针,返回常引用,这都是没问题的。但是,虽然对象是非常量,但是通过常指针和常引用是不能修改的。所以,非常量肯定可以调用这个函数,但是,由于返回常引用,无法进行写操作。这与非常量的语义想违背,所以,第一个重载函数不能省。

#include <iostream>
#define N 3
int& f( int* arr, int i ){
    return arr[i];
}

const int& g( const int* arr, int i ){
    return arr[i];
}

int main( void ){

    int arr[N];

    f( arr, 0) = 0;
    f( arr, 1) = 1;
    f( arr, 2 ) = 2;

    std::cout << g(arr, 0) << std::endl; // 这里没问题,因为常量指针可以指向非常量,这样通过这个指针是不能修改的。但是,由于返回了常引用,所以相当于又通过一个常引用绑定了一个非常量,那么你是不能通过这个常引用进行写操作的。
    g( arr, 1 ) = 1; // 常引用绑定arr[1],不能通过常引用g(arr, 1)进行修改,但是arr[1] = 1;这个是没问题的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值