【问题标题】:Operator overloading for built in types in C++ to access elements in a 2D arrayC++ 中内置类型的运算符重载以访问二维数组中的元素
【发布时间】:2016-02-06 02:04:02
【问题描述】:

我正在寻找二维数组指针的重载 [] 运算符 访问单元格元素。

二维数组作为int *arr 传递给我的函数。

我们可以通过*(arr+i*N+j) 访问单元格元素,其中N 是列数 i 是行索引,j 是列索引。

但是我们可以这样写arr[i,j]arr(i,j) 以获得更好的可读性 使用一些宏或运算符重载?

有什么建议吗?

【问题讨论】:

标签: c++ arrays multidimensional-array operator-overloading


【解决方案1】:

无法更改构建类型的运算符的行为。如果要重载运算符,则至少有一个操作数必须是用户定义的类型。

【讨论】:

    【解决方案2】:

    没有。

    运算符重载需要至少一种参数类型是类/结构/联合。而宏不能做到这一点。

    如果可能,您可以获得的最接近的方法是通过引用传递数组。例如:

    template<std::size_t width, std::size_t height>
    void doThings(int(& array)[width][height])
    {
        // access array[i][j];
    }
    

    或者,如果这不可能,一个辅助非运算符函数来隐藏丑陋的部分:

    int& access(int* array, std::size_t i, std::size_t j)
    {
        return *(arr + i*N + j);
    }
    

    或者您可能需要解决这里的潜在问题。为什么int*首先传递了一个二维数组?

    这里要记住的重要一点是不要使事情过于复杂。

    【讨论】:

    • 我认为写 arr[i,j] 比写 (arr+iN+j) 更容易出错和不可读。
    【解决方案3】:
    • 您可以编写一个索引函数来隐藏公式。

      如果您的N 是全局定义的,请写

      int index(int i, int j) {
          return i * N + j;
      }
      

      并与它一起使用

      arr[index(i, j)]
      
    • 或者,围绕指针编写一个包装类。可以在绝对没有运行时开销的情况下编写此类。它可以有一个运算符来允许语法

      arr[i][j]
      

      其中arr 是包装类的一个实例。这样的包装器可以这样定义:

      class Array2DWrapper {
          int *ptr;
      public:
          Array2DWrapper(int *ptr) : ptr(ptr) {}
      
          int * operator[](int i) {
              return ptr + i*N;
          }
      };
      
      // USAGE:
      void exampleFunction(int *arrPtr) {
          Array2DWrapper arr { arrPtr };
          ...
          arr[i][j];
          ...
      }
      

      如你所见,这个想法是为外部维度重载operator[],它返回一个指向内部维度的指针。当这个类的用户写arr[i]时,它会调用自定义运算符重载,返回一个int*,然后下一个[j]使用内置的operator[]指针访问元素。

      请注意,上面的类可以用作函数参数,但调用者可以使用指向某个二维数组的原始指针来调用它。这将自动调用此类的构造函数(“隐式转换”)。

      // USAGE with implicit conversion on the call site:
      void exampleFunction(Array2DWrapper arr) {
          ...
          arr[i][j];
          ...
      }
      
      // Note how the caller doesn't need to wrap it explicitly:
      int * myArrPtr = ...;
      exampleFunction(myArrPtr);
      

      如果N 未在全局范围内定义,则应将其作为成员添加到类以及构造函数中。

      class Array2DWrapperDynamicN {
          int *ptr;
          int N;
      public:
          Array2DWrapper(int *ptr, int N) : ptr(ptr), N(N) {}
      
          int * operator[](int i) {
              return ptr + i*N;
          }
      };
      

      但是现在,隐式转换不再起作用了。

      // USAGE:
      void exampleFunction(int *arrPtr, int N) {
          Array2DWrapperDynamicN arr { arrPtr, N };
          ...
          arr[i][j];
          ...
      }
      

    【讨论】:

    • 您的第二个选项很好。但是我们还必须在类的某个地方添加 N ?
    • @vicky 我稍微扩展了答案。我不知道在您的情况下 N 是否是全局定义的,或者是否需要将其放入包装器中。这取决于您的其余代码...N 来自哪里?是函数参数吗?还是在函数之外?
    • 你的隐式转换真不错。但是如果 N 可以参与隐式转换,那就太好了,因为 N 不是全局定义的。很高兴看到普通指针转换为具有二维索引功能的智能指针。
    • @vicky 但是为此,它需要以 some 方式知道内部维度的大小......好吧,在 C++11 中,您可以使用初始化列表来对多个参数构造函数使用隐式转换。这有点复杂。基本上你这样调用函数:exampleFunction({arr,N}),看看{arr,N}exampleFunction 的单个参数,如果函数具有签名exampleFunction(Array2DWrapperDynamicN arr),则隐式转换为Array2DWrapperDynamicN
    • 通常行数和列数作为参数列表在 2D 数组之后传递,例如 exampleFunction(arr,m,n) .... 在这种情况下我们可以做点什么吗?
    【解决方案4】:

    你不能直接这样做。
    我建议编写一个可以方便地包装二维数组的小类/结构。这里我使用std::vector 而不是int *arr,你不必关心内存管理。以下代码提供了 3 种可能的方法:

    方法一(推荐):通过mat(i,j)访问

    operator()(size_t i, size_t j) 被称为函数调用运算符

    template<typename T>
    struct Matrix
    {
        Matrix(const std::vector<T>& d, size_t s) : Matrix(d, s, s) {}
        Matrix(const std::vector<T>& d, size_t c, size_t r) : data_(d), cols_(c), rows_(r) {}
        std::vector<T> data_;
        size_t    cols_;
        size_t    rows_;
        const T& operator()(size_t i, size_t j) const { return data_[i * cols_ + j]; }
              T& operator()(size_t i, size_t j)       { return data_[i * cols_ + j]; }
        // and you can add other convenient methods
    };
    

    像这样使用它:

       Matrix<int> mat({1, 2, 3, 4, 5, 6, 7, 8, 9}, 3, 3); // a 3x3 matrix
       std::cout << mat(1,2) << std::endl;
    

    Live code

    如果您在编译时知道大小,那么您可以使用std::array 并相应地更改您的结构:

    template<typename T, size_t Cols, size_t Rows> struct Matrix 
    { 
      std::array<T, Cols * Rows> data_; 
      // etc...
    

    方法二:通过mat[{i,j}]访问

    使用数组下标运算符只需要一个参数,因此您可以更改/添加以下运算符到您的类:

    const T& operator[](const std::array<size_t,2>& a) const { return data_[a[0] * cols_ + a[1]]; }
          T& operator[](const std::array<size_t,2>& a)       { return data_[a[0] * cols_ + a[1]]; }
    

    可以这样调用:

    std::cout &lt;&lt; mat[{1,2}] &lt;&lt; std::endl;

    请注意,当您处理多个维度时,此方法很有用(您不必写多个operator()(size_t i, size_t j, size_t k, etc...)

    方法3(不推荐):通过mat[Idx(i),Idx(j)]访问

    您可以使用 数组下标运算符 获取 两个参数,但您必须重载逗号运算符,这在两个内置类型之间是不可能的...所以直接访问通过mat[i,j] 是不可能的(感谢leemes 指出这一点的评论)。

    但是,您可以创建自己的类型并重载它。这里有一个例子(把它放在你的 Matrix 类定义之前):

    struct Idx 
    {
        Idx(size_t ii) : i(ii) {}
        size_t i;
        operator size_t() const { return i; } // implicit user-defined conversion
    };
    
    std::array<size_t, 2> operator , (Idx i1, Idx i2)
    {
        return { i1, i2 };
    }
    
    // etc...
    // and we do not have to add Matrix operators since we reused the one from Method 2
    

    像这样使用它:

    std::cout << mat[Idx(1),Idx(2)] << std::endl;
    

    这不是那么优雅......

    Complete and final live code

    【讨论】:

    • 这是什么运算符? (我猜你希望第二个是非常量)
    • 这是操作员:mat(i,j).
    • 是的,很抱歉,这是您编写代码而不进行测试时发生的情况;p
    • :) 关于带有两个参数的数组下标运算符:这确实只有在重载逗号运算符时才有可能,但对于两个内置类型的参数来说,这又是不可能的。通常的“解决方法”是使用语法 [i][j] 代替(就像我的回答一样)。它还以更好的方式类似于原始 2D 数组的经典语法。但这当然是主观偏好。
    • 哦,对了,感谢逗号运算符重载也是不可能的。我会更准确地看看你的答案!
    猜你喜欢
    • 1970-01-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 2013-05-15
    • 2013-12-23
    相关资源
    最近更新 更多