int[n][m],其中n和m在运行时已知

6

我经常需要创建一个宽度和高度(分别称为n和m)在编译时未知的二维数组,通常我会写成:

vector<int> arr(n * m);

我手动访问元素的方式是:

arr[j * m + i] 

我最近听说可以使用以下写法:

int arr[n][m] // n and m still only known at runtime.

所以这里有两个问题:

  1. 这种行为是否符合C++标准?
  2. 我应该如何传递这样的数组给函数?g++报告arr的类型为int (*)[n],但是n是动态的,在声明它的函数(main)之外是未知的。

2
  1. 不。
  2. 如果问题1的答案是“不”,这会有影响吗?
- juanchopanza
7
这是非标准行为(你可以更改编译器选项以禁止非标准行为)。按照您使用向量的方式操作,但将其包装在一个类中。 - chris
1
或者使用 vector<vector<int>> - matsjoyce
“vector”解决方案似乎是最好的选择... - ikh
1
@Quest 你确定吗?你有报价吗? - juanchopanza
显示剩余7条评论
3个回答

17
你所询问的特性(在运行时才知道维度)是C++的一个非标准扩展,但是在C.99中是标准扩展(在C.11中成为可选特性)。该特性被称为变长数组(VLA),链接是GCC的文档。
如果你使用的是GCC,则需要将数组长度作为参数传递给函数。
void foo (int m, int arr[][m]) {
    //...
}

然而,无论是编译器还是文档中似乎存在一个 bug,因为上述函数原型语法只适用于编译 C 代码,而不适用于 C++(截至 gcc 版本 4.8.2)。我找到的唯一解决方法是使用 void * 参数,并在函数体中进行强制转换:
int foo_workaround (int m, void *x)
{
    int (*arr)[m] = static_cast<int (*)[m]>(x);
    //...
}

如果您不想依赖编译器扩展,还有其他的解决方案。如果您不介意为每一行单独分配内存,您可以使用向量的向量,例如:
std::vector<std::vector<int> > arr(n, std::vector<int>(m));

然而,如果你想要像你自己的示例中展示的那样一个单一分配块,那么最好创建一个包装器类来围绕vector给你类似2D的语法。

template <typename T>
class vector2d {

    int n_;
    int m_;
    std::vector<T> vec_;

    template <typename I>
    class vector2d_ref {
        typedef std::iterator_traits<I> TRAITS;
        typedef typename TRAITS::value_type R_TYPE;
        template <typename> friend class vector2d;
        I p_;
        vector2d_ref (I p) : p_(p) {}
    public:
        R_TYPE & operator [] (int j) { return *(p_+j); }
    };

    typedef std::vector<T> VEC;
    typedef vector2d_ref<typename VEC::iterator> REF;
    typedef vector2d_ref<typename VEC::const_iterator> CREF;

    template <typename I> 
    vector2d_ref<I> ref (I p, int i) { return p + (i * m_); }

public:

    vector2d (int n, int m) : n_(n), m_(m), vec_(n*m) {}
    REF operator [] (int i) { return ref(vec_.begin(), i); }
    CREF operator [] (int i) const { return ref(vec_.begin(), i); }

};

包装器的operator[]返回一个中间对象,该对象还重载了operator[],以在使用包装器时允许二维数组语法。
    vector2d<int> v(n, m);
    v[i][j] = 7;
    std::cout << v[i][j] << '\n';

1
我正在使用g++编译器,但是在以下函数(这里只有原型)中,使用-std=c++11编译失败:int sum(int n, int[][n] m); int sum(int n, int[n][n] m); 原因是:“在]标记之前在函数体外使用参数”。出了什么问题? - cdkrot
请尝试使用-std=gnu++11 - jxh
我不确定我是否正确地阅读了你的代码。在 ')' 前面多了一个 'm' 是什么意思? - jxh
@cdkrot:非常感谢。这可能是因为对于VLA参数没有C++名称修饰约定,所以C++前端拒绝接受语法。将函数包装在“extern"C"”周围也没有帮助,因此可能有改进的空间。 - jxh
关于VLA和C++的正确用法,请参考这篇相关帖子 - jxh
显示剩余4条评论

2

为什么不使用std::vector来嵌套另一个std::vector呢?

std::vector<std::vector<int> > arr(n, std::vector<int>(m));

访问项目的方法如下:

std::cout << "(2,1) = " << arr[2][1] << std::endl;

这不是简单的二维数组,而是一个数组的数组。但这个解决方案也是可接受的。 - cdkrot
@Jost,如果你声明int a [10] [10],它将在内部成为int a [100]。 - cdkrot
是的,这就是我所说的,元素存储在连续的数组中。关于函数中的传递,这个问题上的一个答案应该可以解决,但我还没有测试过。 - cdkrot
2
我没有给任何人投反对票,std::vector将使用动态内存分配,它不会像这样操作,它将像数组一样,包含指向其他数组的指针(可能在其他位置)。 - cdkrot
@cdkrot,你说得完全正确,我说错了。好提醒。另外,第二部分不是针对你的 :) - scohe001
显示剩余2条评论

0

一个由#include <vector>引入的std::vector嵌套std::vector可以实现与二维数组相同的功能:

int n = 10, m = 10; //vector dimensions
std::vector<std::vector<int>> arr(n, std::vector<int>(m)); //Create 2D vector (vector will be stored as "std::vector<int> arr(n * m);

//you can get values from 2D vector the same way that you can for arrays
int a = 5, b = 5, value = 12345;
arr[a][b] = 12345;
std::cout << "The element at position (" << a << ", " << b << ") is " << arr[a][b] << "." << std::endl;

输出:

位置为(5,5)的元素为12345。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接