方括号 [] 运算符重载 C++

Square bracket [] operator overloading c++

本文关键字:重载 C++ 运算符 方括号      更新时间:2023-10-16

我有一个项目,希望我用 c++ 制作一个 BigNum 类(大学项目) 它说过载操作员支架以进行获取和设置 但问题是,如果集合无效,我们应该抛出一个无效的异常,就像

BigNum a;
a[i]=11;//it is invalid because its >9

在搜索中,我发现了如何使设置工作

C++:重载支架运算符 [] 获取和设置

但是我没有发现如何在 C# 中管理设置操作,您可以轻松管理设置值,相当于 C++ 中的设置值

为了在 C# 中说清楚,我们可以说

public int this[int key]
{
set
{
if(value<0||value>9)throw new Exception();
SetValue(key,value);
}
}

新答案

我必须重写我的答案,我的旧答案是一场灾难。

检查应在作业期间进行,当右侧(11)可用时。因此,您需要重载的运算符是operator=。对于重载operator=,其至少一个操作数必须是用户定义的类型。在这种情况下,唯一的选择是左侧。

我们这里的左手边是表达式a[i].此表达式的类型,也称为返回类型operator[],必须是用户定义的类型,例如BigNumberElement。然后我们可以声明一个BigNumberElementoperator=,并在operator=体内进行范围检查。

class BigNum {
public:
class BigNumberElement {
public:
BigNumberElement &operator=(int rhs) {
// TODO : range check
val_ = rhs;
return *this;
}
private:
int val_ = 0;
};
BigNumberElement &operator[](size_t index) {
return element_[index];
}
BigNumberElement element_[10];
};

旧答案

你可以定义一个wapper,比如NumWapper,它包装了BigNum元素的引用。BigNum 的operator=按值返回包装器。

a[i]=11;

然后是类似于NumWrapper x(...); x = 11.现在,您可以在operator=中进行这些检查NumWrapper.

<小时 />
class BigNum {
public:
NumWrapper operator[](size_t index) {
return NumWrapper(array_[index]);
}
int operator[](size_t index) const {
return array_[index];
}
};

在 NumWrapper 中,重载一些运算符,例如:

class NumWrapper {
public:
NumWrapper(int &x) : ref_(x) {}
NumWrapper(const NumWrapper &other) : ref_(other.ref_) {}
NumWrapper &operator=(const NumWrapper &other);
int operator=(int x);
operator int();
private:
int &ref_;
};

您还可以将 NumWrapper 的副本和移动构造函数声明为私有,并使 BigNum 成为他的朋友,以防止用户代码复制您的包装器。如果这样做,此类代码auto x = a[i]将不会编译,而用户代码仍然可以按auto x = static_cast<T>(a[i])复制包装的值(尽管有点冗长)。

auto &x = a[i]; // not compiling
const auto &x = a[i]; // dangerous anyway, can't prevent.

看来我们很好。


这些也是另一种方法:将元素存储为用户定义的类,例如BigNumberElement。我们现在将类 BigNum 定义为:

class BigNum {
// some code
private:
BigNumberElement array_[10];
}

我们需要为 BigNumberElement 声明一个完整的集合运算符,例如 comparison(也可以通过转换来完成)、赋值、构造函数等,以使其易于使用。

auto x = a[i]现在将获得BigNumberElement的副本,这在大多数情况下都很好。仅分配给它有时会引发异常并引入一些运行时开销。但是我们仍然可以写auto x = static_cast<T>(a[i])(虽然仍然很冗长...据我所知,意外的编译时错误消息比意外的运行时异常要好。

我们还可以使 BigNumberElement 不可复制/可移动...但是,它将与第一种方法相同。(如果任何成员函数返回BigNumberElement &,则意外的运行时异常将返回。

下面定义了一个类型foo::setter,该类型从operator[]返回并重载其operator=以分配值,但如果值不在允许的范围内,则抛出。

class foo
{
int data[10];
public:
void set(int index, int value)
{
if(value<0 || value>9)
throw std::runtime_error("foo::set(): value "+std::to_string(value)+" is not valid");
if(index<0 || index>9)
throw std::runtime_error("foo::set(): index "+std::to_string(index)+" is not valid");
data[index] = value;
}
struct setter {
foo &obj;
size_t index;
setter&operator=(int value)
{
obj.set(index,value);
return*this;
}
setter(foo&o, int i)
: obj(o), index(i) {}
};
int operator[](int index) const // getter
{ return data[index]; }
setter operator[](int index) // setter
{ return {*this,index}; }
};

如果你试图做的是重载[],你可以在其中输入像字典这样的信息或像dict[key] = val这样的映射。 答案其实很简单:

假设你想加载一个 std::string 作为键,并将 std::vector 作为值。 假设您有一个unordered_map作为您的底层结构,您正在尝试向其传递信息。

std::unordered_map<std::string, std::vector<double>> myMap;

在你自己的类中,你有这个定义:

class MyClass{
private:
std::unordered_map<std::string, std::vector<double>> myMap;
public:
std::vector<double>& operator [] (std::string key) {
return myMap[key];
}
}

现在,当您想要加载对象时,只需执行以下操作:

int main() {
std::vector<double> x;
x.push_back(10.0);
x.push_back(20.0);
x.push_back(30.0);
x.push_back(40.0);

MyClass myClass;
myClass["hello world"] = x;
double x = myClass["hello world"][0]; //returns 10.0
}

重载的 [] 返回对该向量的存储位置的引用。 因此,当您第一次调用它时,它会返回在为 = x 分配向量后将存储向量的地址。 第二个调用返回相同的地址,现在返回您输入的向量。