【问题标题】:Change array dimensions at runtime在运行时更改数组维度
【发布时间】:2017-05-13 05:27:39
【问题描述】:

我有这个矩阵类,它有一个二维双数组。在构造函数中,您可以指定宽度和高度。当宽度为 1 时,我想创建一个 1d 数组而不是 2d。因为我重载了 [] 运算符并返回了指针。如果只有 1 行/列,我不想总是写 [i][0]。相反,我只想写 [i]。 有谁知道如何解决这个问题?

编辑: 为了澄清这一点,我需要这个类来进行矩阵计算,而不仅仅是数组。

【问题讨论】:

  • 如果你的矩阵大小是在运行时确定的,那么编译器就无法知道宽度是否为1。一种可能性是为这种情况设置不同的类。
  • 您需要创建第二个代表一维数组的类。更好的是,使用std::vector<double 来表示一维数组,使用std::vector<std::vector<double > > 来表示二维数组——不要搞乱管理数组维度、内存分配或尝试以奇怪的方式重载operator[]() .
  • 您需要能够动态调整矩阵的大小吗?如果没有,您可以将行和列作为模板参数,这样您就可以专注于 1 的情况。
  • @JustinTime 号

标签: c++ arrays pointers c++14


【解决方案1】:

您可以将这两种替代类型包装成一个变体类型(一个标记的联合)。

但是,您不能使用operator[] 访问这两个变体,因为每个变体的返回类型都不同。一个将返回对子数组的引用,而另一个将返回一个双精度数。不能有两个仅根据返回类型不同的重载。

相反,您可以使用重载函数。例如,double at(size_type x)double at(size_type x, size_type y)


但是,由于您要表示一个矩阵,因此使用一维数组来表示任意秩的矩阵可能会更简单,方法是将较高维度连续放置,就像多维数组存储在内存中一样(内存是一维的) , 毕竟)。这允许您在运行时指定每个维度的宽度,并避免变体类型的复杂性。

【讨论】:

    【解决方案2】:

    我建议使用一维数组,如user2079303 suggested in their answer。但是,如果这不是可取的,也可以使用模板或多态来实现您的想法。


    如果您不需要动态更改其维度,您可以将矩阵设为模板类,使用其维度作为模板参数。这反过来又允许将维度用于 SFINAE 的编译时逻辑,从而允许您根据矩阵的维度重载 operator[]()

    请注意,这实际上Rows == 1Cols == 1 时不会创建一维数组,它只是模拟一个;如果您明确需要该行为,而不是技术上不同但大部分等效的行为,您将需要研究一个或两个参数为1 时的专业化类。这可能会涉及复制至少一些代码,所以会有点混乱。

    // This example uses std::array for the actual array, since it's compile-time anyways.
    // This, in turn, lets it declare a lot of functions constexpr.
    // Const-correctness omitted for brevity.  Remember to restore it for actual code.
    template<size_t Rows, size_t Cols>
    class Matrix {
        std::array<std::array<double, Cols>, Rows> mat;
    
      public:
        // Default constructor. Clang _really_ likes braced initialiser lists.
        constexpr Matrix() : mat{{{{0}}}} {}
    
        // Array constructor.
        constexpr Matrix(const decltype(mat)& arr) : mat(arr) {}
    
        // -----
    
        // Subscript operators.
    
        // Generic operator.  Matrix<x, y>, where x != 1 && y != 1.
        // Does what normal subscript operators do.
        template<bool R = (Rows == 1), bool C = (Cols == 1)>
        auto& operator[](std::enable_if_t<!R && !C, size_t> i) {
            return mat[i];
        }
    
        // Magic operator.  Matrix<1, x>, where x != 1.
        template<bool R = (Rows == 1), bool C = (Cols == 1)>
        auto& operator[](std::enable_if_t<(R && !C), size_t> i) {
            return mat[0][i];
        }
    
        // Magic operator.  Matrix<x, 1>, where x != 1.
        template<bool R = (Rows == 1), bool C = (Cols == 1)>
        auto& operator[](std::enable_if_t<C && !R, size_t> i) {
            return mat[i][0];
        }
    
        // Scalar matrix operator.  Matrix<1, 1>.
        // Just returns mat[0][0], for simplicity's sake.  Might want to make it do something
        //  more complex in your actual class.
        template<bool R = (Rows == 1), bool C = (Cols == 1)>
        auto& operator[](std::enable_if_t<R && C, size_t> i) {
            return mat[0][0];
        }
    
        // -----
    
        // A few interface helpers.
    
        // Simple begin() & end(), for example's sake.
        // A better version would begin at mat[0][0] and end at mat[Rows - 1][Cols - 1].
        constexpr auto begin() const { return mat.begin(); }
        constexpr auto   end() const { return mat.end(); }
    
        // Generic helpers.
        constexpr size_t    size() const { return mat.size() * mat[0].size(); }
        constexpr size_t    rows() const { return mat.size(); }
        constexpr size_t    cols() const { return mat[0].size(); }
    
        // 1D Matrix helpers.
        constexpr bool    is_one_d() const { return (Rows == 1) || (Cols == 1); }
        constexpr bool     one_row() const { return Rows == 1; }
        constexpr size_t dimension() const { return (one_row() ? cols() : rows()); }
    
        // -----
    
        // Output.
        // Would need modification if better begin() & end() are implemented.
        friend std::ostream& operator<<(std::ostream& str, const Matrix<Rows, Cols>& m) {
            for (auto& row : m) {
                for (auto& elem : row) {
                    str << std::setw(6) << elem << ' ';
                }
                str << '\n';
            }
            str << std::endl;
            return str;
        }
    };
    

    它可以用作...

    // Get rid of any "Waah, you didn't use that!" warnings.
    // See https://stackoverflow.com/a/31654792/5386374
    #define UNUSED(x) [&x]{}()
    
    // This should really use if constexpr, but online compilers don't really support it yet.
    // Instead, have an SFINAE dummy.
    template<size_t Rows, size_t Cols>
    void fill(Matrix<Rows, Cols>& m, std::enable_if_t<(Rows == 1) || (Cols == 1), int> dummy = 0) {
        UNUSED(dummy);
    
        //for (size_t i = 0; i < (m.one_row() ? m.cols() : m.rows()); i++) {
        for (size_t i = 0; i < m.dimension(); i++) {
            m[i] = (i ? i : 0.5) * (i ? i : 0.5);
        }
    }
    
    template<size_t Rows, size_t Cols>
    void fill(Matrix<Rows, Cols>& m, std::enable_if_t<!((Rows == 1) || (Cols == 1)), int> dummy = 0) {
        UNUSED(dummy);
    
        for (size_t i = 0; i < m.rows(); i++) {
            for (size_t j = 0; j < m.cols(); j++) {
                m[i][j] = (i ? i : 0.5) * (j ? j : 0.5) + (i >= j ? 0.1 : -0.2);
            }
        }
    }
    

    看到它在行动here


    如果您确实需要能够即时更改其尺寸,这将变得更加复杂。最简单的解决方案可能是使用多态性,并让operator[] 返回一个代理。

    class Matrix {
      protected:
        // Out proxy class.
        // Visible to children, for implementing.
        struct SubscriptProxy {
            virtual operator double&() = 0;
            virtual operator double*() = 0;
            virtual double& operator=(double) = 0;
            virtual double& operator[](size_t) = 0;
            virtual ~SubscriptProxy() = default;
        };
    
      public:
        virtual SubscriptProxy& operator[](size_t i) = 0;
        virtual ~Matrix() = default;
    
        virtual void out(std::ostream& str) const = 0;
        friend std::ostream& operator<<(std::ostream& str, const Matrix& m) {
            m.out(str);
            return str;
        }
    };
    std::ostream& operator<<(std::ostream& str, const Matrix& m);
    

    然后您可以让每个类创建它需要的代理成员public,以及不需要的代理成员privateprivate 得到了一个非功能性的虚拟实现。

    // Resizing omitted for brevity.
    class OneDMatrix : public Matrix {
        double arr[5];
    
        // Proxy for single element.
        class OneDProxy : public SubscriptProxy {
            double& elem;
    
            operator double*() override { return &elem; }
            double& operator[](size_t) override { return elem; }
          public:
            OneDProxy(double& e) : elem(e) {}
    
            operator double&() override { return elem; }
            double& operator=(double d) override {
                elem = d;
                return elem;
            }
        };
    
      public:
        OneDMatrix() : arr{0} {}
    
        // operator[] maintains a static pointer, to keep the return value alive and guarantee
        //  proper cleanup.
        SubscriptProxy& operator[](size_t i) override {
            static OneDProxy* ret = nullptr;
    
            if (ret) { delete ret; }
            ret = new OneDProxy(arr[i]);
            return *ret;
        }
    
        void out(std::ostream& str) const override {
            for (size_t i = 0; i < 5; i++) {
                str << std::setw(4) << arr[i] << ' ';
            }
            str << std::endl;
        }
    };
    
    // Resizing omitted for brevity.
    class TwoDMatrix : public Matrix {
        double arr[3][4];
    
        // Proxy for array.
        class TwoDProxy : public SubscriptProxy {
            double* elem;
    
            operator double&() override { return elem[0]; }
            double& operator=(double) override { return elem[0]; }
          public:
            TwoDProxy(double* e) : elem(e) {}
            operator double*() override { return elem; }
            double& operator[](size_t i) override { return elem[i]; }
        };
    
      public:
        TwoDMatrix() : arr{{0}} {}
    
        // operator[] maintains a static pointer, to keep the return value alive and guarantee
        //  proper cleanup.
        SubscriptProxy& operator[](size_t i) override {
            static TwoDProxy* ret = nullptr;
    
            if (ret) { delete ret; }
            ret = new TwoDProxy(arr[i]);
            return *ret;
        }
    
        void out(std::ostream& str) const override {
            for (size_t i = 0; i < 3; i++) {
                for (size_t j = 0; j < 4; j++) {
                    str << std::setw(4) << arr[i][j] << ' ';
                }
                str << '\n';
            }
        }
    };
    

    由于引用可用于多态性,Matrix 可以用作接口,允许任何不特别需要实际类之一的东西采用Matrix&amp;

    看到它在行动here

    通过这样的设置,通过提供辅助函数,动态创建正确类型的矩阵变得很容易。

    // Assume OneDMatrix is expanded for dynamic size specification.
      // It now has constructor OneDMatrix(size_t sz), and owns a dynamic double[sz].
    // Assume TwoDMatrix is expanded for dynamic size specification.
      // It now has constructor TwoDMatrix(size_t r, size_t c), and owns a dynamic double[r][c].
    Matrix* createMatrix(size_t rows, size_t cols) {
        if (rows == 1)      { return new OneDMatrix(cols);       }
        else if (cols == 1) { return new OneDMatrix(rows);       }
        else                { return new TwoDMatrix(rows, cols); }
    }
    

    像往常一样,模板版本更冗长,但更安全(因为不必使用指针)并且可能更有效(因为不需要做那么多的动态分配和释放;我还没有测试过这个,但是)。

    【讨论】:

      【解决方案3】:

      如果您没有 C++17 编译器,一个不错的解决方案是使用 std::variantboost::variant。我会创建一个易于使用的容器。

      template<typename T>
      struct DynamicDimension {
          std::variant<T, std::vector<T>> element;
      
          // accessors, is_vector and is_element function.
          // Maybe operator[] and push_back and a get function.
          // Add begin and end to make your class useable with range for loops.
      };
      

      【讨论】:

        【解决方案4】:

        这个问题真的让我很感兴趣,在我原来的回答中,我说所需的操作是不可能的,因为在 C++ 中你不能重载仅在返回类型上有所不同的函数。

        但如果可以呢?

        我变得好奇,所以我花了很多时间研究和摆弄 C++,看看我是否可以让一个函数返回多种类型……答案是“是的”,但它是不常用的方式,因为它使用void *,这让生活变得困难。

        然而,它确实具有编写更少代码行的额外好处,即使更难理解。

        也就是说,我从经验中学到了一些东西,所以我想我应该分享一下。

        因此,有不同的方法可以用更少的代码解决这个问题。在矩阵类中,有两个指针,一个是T * matrix,另一个是T ** matrix2d,并使用void * 作为重载[] 运算符的返回类型。

        #ifndef matrix_h
        #define matrix_h
        
        template <typename T>
        class Matrix {
        private:
            int width;
            int height;
            int size;
            T * matrix;
            T ** matrix2d;
        
        public:
            Matrix(const int w, const int h): width(w), height(h){
                if(w==1){
                    matrix = new T[h];
                    size = h;
                } else if (h==1){
                    matrix = new T[w];
                    size = w;
                } else {
                    matrix2d = new T*[h];
                    for(int i=0;i<h;++i){
                        matrix2d[i] = new T[w];
                    }
                    size = w*h;
                }
            }
        
            ~Matrix() {
                if(width==1 || height==1){
                    delete [] matrix;
                } else {
                    for(int i=0;i<height;++i){
                        T * _r = matrix2d[i];
                        delete [] _r;
                    }
                    delete [] matrix2d;
                }
            }
        
            void * operator[](const int i){
                if(width==1 || height==1){
                    return & matrix[i];
                } else {
                    return & (*matrix2d[i]);
                }
            }
        
            const int getSize(){
                return size;
            }
        };
        
        #endif /* matrix_h */
        

        main,做这个演示:

        #include <iostream>
        #include "Matrix.h"
        
        int main() {
            //Give the type so the correct size_t is allocated in memory.
            Matrix <double> matrix1(1, 3);
            Matrix <double> matrix2(2,2);
        
            //Now have an array of void pointers.
            std::cout << "1 dimensional" << std::endl;
            for(int i=0;i<matrix1.getSize();++i){
                std::cout << matrix1[i] << std::endl;
            }
        
            //Cast the void *, then dereference it to store values.
            *((double*)matrix1[0]) = 77;
            *((double*)matrix1[1]) = 31;
            *((double*)matrix1[2]) = 24.1;
        
            for(int i=0;i<matrix1.getSize();++i){
                std::cout << *((double *)matrix1[i]) << std::endl;
            }
        
            std::cout << "2 dimensional addresses." << std::endl;
            for(int i=0;i<2;++i){
                double * _row = (double*)matrix2[i];
                for(int j=0;j<2;++j){
                    std::cout << &_row[j] << " ";
                }
                std::cout << std::endl;
            }
        
            std::cout << "2 dimensional assignment and display." << std::endl;
            double num = 13.1;
            for(int i=0;i<2;++i){
                double * _row = (double*)matrix2[i];
                for(int j=0;j<2;++j){
                    _row[j] = num;
                    num += 0.13;
                    std::cout << _row[j] << " ";
                }
                std::cout << std::endl;
            }
        
            return 0;
        }
        

        【讨论】:

          【解决方案5】:

          要制作 2D 动态数组,您需要使用一些数据结构概念,我认为您不熟悉它们。在此之前,让我向您展示如何制作一维动态数组。

          int main()
          {
            int size;
          
            std::cin >> size;
          
            int *array = new int[size];
          
            delete [] array;
          
            return 0;
          }
          

          不要忘记删除使用 new 分配的每个数组。

          让我们回到二维动态数组。这些数组可以概括为哈希表,它是一种常见的数据结构类型。现在这个人对二维数组的解释比我好得多How to create dynamic 2D array点击链接。

          【讨论】:

          • 你可以至少使用unique_ptr。而且它不回答 OP 问题。
          猜你喜欢
          • 2023-03-13
          • 2016-05-21
          • 2016-08-31
          • 1970-01-01
          • 2011-07-21
          • 2023-03-11
          • 2022-11-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多