C++|为什么有时运算符重载需定义一个函数体完全相同的const版本

我们知道,数据保护很重要,如文件的用户限制,只读限制,密码保护等。

在编程语言中,使用const限定变量来做数据保护,以避免其数据状态的改变。

如有一个类,要访问一个类公共成员,需要通过声明类对象(或类对象指针、引用)来引用类的成员函数。如果声明了这个类的一个常对象呢?常对象是说不能改变类的数据成员,对象是用来调用成员函数的,也就是说调用的成员函数不能更改数据成员。在编译阶段,编译器无法去查询引用的成员函数的定义,所以C++的做法是,设置常成员函数的语法机制,在类定义时,常成员函数的是在末尾添加一个const标识(因为函数还可以返回const类型(放在开头),所以只能放在末尾),常成员函数的函数体只能引用成员数据做右值(不能引用数据成员做左值)。所以,常对象自然只引用常成员函数,编译器会在编译阶段检查常对象引用的是不是常成员函数,会在类定义中去查找对应的常成员函数版本,如果不是,会引发编译错误。另外,常成员函数虽然可以返回引用,并可以用常对象调用返回引用的常成员函数做左值来修改对象状态,但最好是值返回,因为常对象的初衷就是不改变对象状态。

例如:

const Array ib(7);// seven-element Array

ib不能调用非常成员函数。

值返回的常运算符重载也不能被:

ib.operator[](3) = 8; // error,相当于ib[3] = 8;
// 如果定义的是int &operator[]( int ) const; ib.operator[](3)可以做左值,但返回了声明const对象的初衷
cout<<ib[3]<<endl;

如果非常对象,则可以调用非常成员函数,也可以使用非常操作符重载。

Array ia(7); // seven-element Array
ia.operator[](3) = 6; // 相当于ia[3] = 6;
cout<<ia[3]<<endl;

如一个array类的简单demo版:

#include <iostream>
#include <iomanip>
#include <cstdlib> // exit function prototype
using namespace std;
class Array
{
public:
    Array( int = 10 );       // default constructor
    ~Array();                // destructor
    // subscript operator for non-const objects returns modifiable lvalue
    int &operator[]( int );              
    // subscript operator for const objects returns rvalue
    int operator[]( int ) const; 
    //int &operator[]( int ) const; // 也可以返回引用,但会引发常对象也可以[]做左值
private:
    int size; // pointer-based array size
    int *ptr; // pointer to first element of pointer-based array
}; // end class Array

// default constructor for class Array (default size 10)
Array::Array( int arraySize )
{
    size = ( arraySize > 0 ? arraySize : 10 ); // validate arraySize
    ptr = new int[ size ]; // create space for pointer-based array
    
    for ( int i = 0; i < size; i++ )
        ptr[ i ] = 0; // set pointer-based array element
}

Array::~Array()
{
    delete [] ptr;
}
// overloaded subscript operator for non-const Arrays;
// reference return creates a modifiable lvalue
int &Array::operator[]( int subscript )
{
    // check for subscript out-of-range error
    if ( subscript < 0 || subscript >= size )
    {
        cerr << "\nError: Subscript " << subscript 
            << " out of range" << endl;
        exit( 1 ); // terminate program; subscript out of range
    } // end if
    cout<<"non-const version returning reference(lvale)"<<endl;
    return ptr[ subscript ]; // reference return
} // end function operator[]
// overloaded subscript operator for const Arrays
// const reference return creates an rvalue
int Array::operator[]( int subscript ) const
{
    // check for subscript out-of-range error
    if ( subscript < 0 || subscript >= size )
    {
        cerr << "\nError: Subscript " << subscript 
            << " out of range" << endl;
        exit( 1 ); // terminate program; subscript out of range
    }
    cout<<"const version returning rvale"<<endl;
    return ptr[ subscript ]; // returns copy of this element
}

int main()
{
    Array ia(7); // seven-element Array
    ia.operator[](3) = 6; // 相当于ia[3] = 6;
    cout<<ia[3]<<endl;
    const Array ib(7);// seven-element Array
    //ib.operator[](3) = 8; // error,相当于ib[3] = 8;
    // 如果定义的是int &operator[]( int ) const; ib.operator[](3)可以做左值
    cout<<ib[3]<<endl;
    getchar();
    return 0;
}
/*output:
non-const version returning reference(lvale)
non-const version returning reference(lvale)
6
const version returning rvale
0
*/

关于const:

1 因为是const,需要在声明时初始化(常量声明后不能再修改);

2 防止修改,起保护作用,增加程序健壮性

3 const常量相对于#define宏定义,const常量具有类型,编译器可以进行安全检查;#define宏定义以字面量为数据类型,只是简单的字符串替换,并不进行安全检查。若用 const 定义常量(类型为整数或枚举,必须以常量表达式初始化),则这种常量在非 odr 式使用(粗略来说是只使用其值)时不需要依赖其身为变量的身份,一定场合下甚至可以不需要定义(譬如作为类的 static 成员对象)。编译器在作为常量处理它时,不会依赖“一份定义”,而是像是立即数一样使用它,它本身可能在机器码中被“拷贝”到多个地方,和 #define 定义的宏常量的结果相同。另一方面, const 定义的常量由于是整数或枚举,所以直接变成机器码上的立即数往往性能更好。另外,const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 const 限定的变量,不要与常量混淆。

4 const对象默认为文件内链接属性,显式声明extern,可以将const对象改变为外部链接属性。

5 const与指针,指针指向目标对象,变量存在型、址与值,指针本身可以称为己址、己值,目标对象可以称为他值。

int a;
const int* const ptr = &;

以指针声明运算符*为界限,const放在*前面,表示修饰指针的目标对象,不能通过指针解引用*ptr做左值去修改a,const放在*后面,表示修饰指针自身,ptr本身不能再做左值,不能再指向其它存储单元。

6 const修饰函数的返回值(放置在函数声明的开头),并不能做为函数签名的标志,不能用以区分函数重载。但const修饰成员函数是否能够对成员数据做修改(也就是在函数体内使用成员数据做左值),可以做为函数签名的标志,能够区分函数重载。

ref

https://light-city.club/sc/basic_content/const/

-End-

举报
评论 0