Как лучше всего обрабатывать динамические многомерные массивы в C/С++?
Каков приемлемый/наиболее часто используемый способ манипулирования динамическими (со всеми параметрами, неизвестными до выполнения) многомерными массивами в C и/или С++.
Я пытаюсь найти самый чистый способ выполнить то, что делает этот код Java:
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int rows=sc.nextInt();
int cols=sc.nextInt();
int[][] data=new int[rows][cols];
manipulate(data);
}
public static void manipulate(int[][] data){
for(int i=0;i<data.length;i++)
for(int j=0;j<data[0].length.j++){
System.out.print(data[i][j]);
}
}
(читается из std_in, чтобы уточнить, что размеры неизвестны до выполнения).
Изменить: я заметил, что этот вопрос довольно популярен, хотя он довольно старый. На самом деле я не согласен с высшим голосованием. Я считаю, что лучшим выбором для C является использование одномерного массива, как сказал ниже: "Вы можете выделить rowscolssizeof (int) и получить к нему доступ по таблице [row * cols + col].".
Существует ряд вариантов с С++, если вам действительно нравится boost или stl, тогда ответы ниже могут быть предпочтительными, но самый простой и, вероятно, самый быстрый выбор - использовать одномерный массив, как в C.
Другим жизнеспособным выбором в C и С++, если вы хотите, чтобы синтаксис [] [] был линглным ответом внизу, вручную создает массив с большим количеством malloc.
Ответы
Ответ 1
Используйте boost:: multi_array.
Как и в вашем примере, единственное, что вам нужно знать во время компиляции, - это количество измерений. Вот первый пример в документации:
#include "boost/multi_array.hpp"
#include <cassert>
int
main () {
// Create a 3D array that is 3 x 4 x 2
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents[3][4][2]);
// Assign values to the elements
int values = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
A[i][j][k] = values++;
// Verify values
int verify = 0;
for(index i = 0; i != 3; ++i)
for(index j = 0; j != 4; ++j)
for(index k = 0; k != 2; ++k)
assert(A[i][j][k] == verify++);
return 0;
}
Изменить: как было предложено в комментариях, вот пример "простого" примера, который позволяет определить размер многомерного массива во время выполнения, запрашивая у входа в консоль.
Вот пример вывода этого примера приложения (скомпилированного с константой, обозначающей его 3 измерения):
Multi-Array test!
Please enter the size of the dimension 0 : 4
Please enter the size of the dimension 1 : 6
Please enter the size of the dimension 2 : 2
Text matrix with 3 dimensions of size (4,6,2) have been created.
Ready!
Type 'help' for the command list.
>read 0.0.0
Text at (0,0,0) :
""
>write 0.0.0 "This is a nice test!"
Text "This is a nice test!" written at position (0,0,0)
>read 0.0.0
Text at (0,0,0) :
"This is a nice test!"
>write 0,0,1 "What a nice day!"
Text "What a nice day!" written at position (0,0,1)
>read 0.0.0
Text at (0,0,0) :
"This is a nice test!"
>read 0.0.1
Text at (0,0,1) :
"What a nice day!"
>write 3,5,1 "This is the last text!"
Text "This is the last text!" written at position (3,5,1)
>read 3,5,1
Text at (3,5,1) :
"This is the last text!"
>exit
Важными частями кода являются основная функция, где мы получаем размеры от пользователя и создаем массив с помощью:
const unsigned int DIMENSION_COUNT = 3; // dimension count for this test application, change it at will :)
// here is the type of the multi-dimensional (DIMENSION_COUNT dimensions here) array we want to use
// for this example, it own texts
typedef boost::multi_array< std::string , DIMENSION_COUNT > TextMatrix;
// this provide size/index based position for a TextMatrix entry.
typedef std::tr1::array<TextMatrix::index, DIMENSION_COUNT> Position; // note that it can be a boost::array or a simple array
/* This function will allow the user to manipulate the created array
by managing it commands.
Returns true if the exit command have been called.
*/
bool process_command( const std::string& entry, TextMatrix& text_matrix );
/* Print the position values in the standard output. */
void display_position( const Position& position );
int main()
{
std::cout << "Multi-Array test!" << std::endl;
// get the dimension informations from the user
Position dimensions; // this array will hold the size of each dimension
for( int dimension_idx = 0; dimension_idx < DIMENSION_COUNT; ++dimension_idx )
{
std::cout << "Please enter the size of the dimension "<< dimension_idx <<" : ";
// note that here we should check the type of the entry, but it a simple example so lets assume we take good numbers
std::cin >> dimensions[dimension_idx];
std::cout << std::endl;
}
// now create the multi-dimensional array with the previously collected informations
TextMatrix text_matrix( dimensions );
std::cout << "Text matrix with " << DIMENSION_COUNT << " dimensions of size ";
display_position( dimensions );
std::cout << " have been created."<< std::endl;
std::cout << std::endl;
std::cout << "Ready!" << std::endl;
std::cout << "Type 'help' for the command list." << std::endl;
std::cin.sync();
// we can now play with it as long as we want
bool wants_to_exit = false;
while( !wants_to_exit )
{
std::cout << std::endl << ">" ;
std::tr1::array< char, 256 > entry_buffer;
std::cin.getline(entry_buffer.data(), entry_buffer.size());
const std::string entry( entry_buffer.data() );
wants_to_exit = process_command( entry, text_matrix );
}
return 0;
}
И вы можете видеть, что для присоединения элемента в массиве это очень просто: вы просто используете оператор(), как в следующих функциях:
void write_in_text_matrix( TextMatrix& text_matrix, const Position& position, const std::string& text )
{
text_matrix( position ) = text;
std::cout << "Text \"" << text << "\" written at position ";
display_position( position );
std::cout << std::endl;
}
void read_from_text_matrix( const TextMatrix& text_matrix, const Position& position )
{
const std::string& text = text_matrix( position );
std::cout << "Text at ";
display_position(position);
std::cout << " : "<< std::endl;
std::cout << " \"" << text << "\"" << std::endl;
}
Примечание. Я скомпилировал это приложение в VC9 + SP1 - получил только некоторые забываемые предупреждения.
Ответ 2
Существует два способа представления 2-мерного массива в С++. Один из них более гибкий, чем другой.
Массив массивов
Сначала создайте массив указателей, затем инициализируйте каждый указатель другим массивом.
// First dimension
int** array = new int*[3];
for( int i = 0; i < 3; ++i )
{
// Second dimension
array[i] = new int[4];
}
// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
for( int j = 0; j < 4; ++j )
{
std::cout << array[i][j];
}
}
Проблема с этим методом заключается в том, что вашему второму измерению выделяется столько же массивов, что и не облегчает работу распределителя памяти. Вероятно, ваша память будет фрагментирована, что приведет к снижению производительности. Это обеспечивает большую гибкость, поскольку каждый массив во втором измерении может иметь другой размер.
Большой массив для хранения всех значений
Трюк здесь состоит в том, чтобы создать массивный массив для хранения всех необходимых данных. Жесткая часть состоит в том, что вам все еще нужен первый массив указателей, если вы хотите иметь доступ к данным с помощью синтаксиса [i] [j].
int* buffer = new int[3*4];
int** array = new int*[3];
for( int i = 0; i < 3; ++i )
{
array[i] = array + i * 4;
}
Массив int * не является обязательным, так как вы можете получить доступ к своим данным непосредственно в буфере, вычислив индекс в буфере из двухмерных координат значения.
// You can then access your array data with
for( int i = 0; i < 3; ++i )
{
for( int j = 0; j < 4; ++j )
{
const int index = i * 4 + j;
std::cout << buffer[index];
}
}
ПРАВИЛО, чтобы иметь в виду
Память компьютера является линейной и будет продолжаться долгое время. Имейте в виду, что 2-мерные массивы не поддерживаются на компьютере, поэтому единственный способ - "линеаризовать" массив в 1-мерный массив.
Ответ 3
Вы можете выделить rowscolssizeof (int) и получить к нему доступ по таблице [row * cols + col].
Ответ 4
Стандартный способ без использования boost - использовать std::vector:
std::vector< std::vector<int> > v;
v.resize(rows, std::vector<int>(cols, 42)); // init value is 42
v[row][col] = ...;
Это позаботится о новом/удалении необходимой вам памяти. Но он довольно медленный, поскольку std::vector
не предназначен в первую очередь для его использования (вложение std::vector
друг в друга). Например, вся память не выделяется в одном блоке, а отдельна для каждого столбца. Кроме того, строки не должны быть одинаковой ширины. Более быстрый использует обычный вектор, а затем выполняет вычисление индекса, например col_count * row + col
, чтобы получить определенную строку и col:
std::vector<int> v(col_count * row_count, 42);
v[col_count * row + col) = ...;
Но это потеряет способность индексировать вектор, используя [x][y]
. Вы также должны хранить количество строк и столбцов где-то, в то время как с помощью вложенного решения вы можете получить количество строк, используя v.size()
, и количество cols с помощью v[0].size()
.
Используя boost, вы можете использовать boost::multi_array
, который делает именно то, что вы хотите (см. другой ответ).
Существует также исходный путь с использованием собственных С++-массивов. Это обеспечивает некоторую работу и никоим образом не лучше, чем вложенное векторное решение:
int ** rows = new int*[row_count];
for(std::size_t i = 0; i < row_count; i++) {
rows[i] = new int[cols_count];
std::fill(rows[i], rows[i] + cols_count, 42);
}
// use it... rows[row][col] then free it...
for(std::size_t i = 0; i < row_count; i++) {
delete[] rows[i];
}
delete[] rows;
Вам нужно сохранить количество столбцов и строк, которые вы создали где-то, так как вы не можете получить их из указателя.
Ответ 5
Вот простой способ сделать это в C:
void manipulate(int rows, int cols, int (*data)[cols]) {
for(int i=0; i < rows; i++) {
for(int j=0; j < cols; j++) {
printf("%d ", data[i][j]);
}
printf("\n");
}
}
int main() {
int rows = ...;
int cols = ...;
int (*data)[cols] = malloc(rows*sizeof(*data));
manipulate(rows, cols, data);
free(data);
}
Это отлично действует с C99, однако это не С++ любого стандарта: С++ требует, чтобы размеры типов массива были константами компиляции. В этом отношении С++ теперь на пятнадцать лет отстает от C. И эта ситуация не скоро изменится (предложение массива переменной длины для С++ 17 не приближается к функциональности массивов переменной длины C99).
Ответ 6
2D-массивы C-стиля в C и С++ представляют собой блок памяти размером rows * columns * sizeof(datatype)
bytes.
Фактические размеры [row] [column] существуют только статически во время компиляции. Там нет ничего динамически во время выполнения!
Итак, как отмечали другие, вы можете реализовать
int array [ rows ] [ columns ];
Как
int array [ rows * columns ]
Или как:
int * array = malloc ( rows * columns * sizeof(int) );
Далее: Объявление массива с переменным размером. В C это возможно:
int main( int argc, char ** argv )
{
assert( argc > 2 );
int rows = atoi( argv[1] );
int columns = atoi( argv[2] );
assert(rows > 0 && columns > 0);
int data [ rows ] [ columns ]; // Yes, legal!
memset( &data, 0, sizeof(data) );
print( rows, columns, data );
manipulate( rows, columns, data );
print( rows, columns, data );
}
В C вы можете просто передать массив с переменным размером примерно так же, как массив с переменным размером:
void manipulate( int theRows, int theColumns, int theData[theRows][theColumns] )
{
for ( int r = 0; r < theRows; r ++ )
for ( int c = 0; c < theColumns; c ++ )
theData[r][c] = r*10 + c;
}
Однако в С++ это невозможно. Вы должны выделить массив, используя динамическое распределение, например:
int *array = new int[rows * cols]();
или предпочтительно (с автоматическим управлением памятью)
std::vector<int> array(rows * cols);
Затем функции должны быть изменены для принятия одномерных данных:
void manipulate( int theRows, int theColumns, int *theData )
{
for ( int r = 0; r < theRows; r ++ )
for ( int c = 0; c < theColumns; c ++ )
theData[r * theColumns + c] = r*10 + c;
}
Ответ 7
Если вы используете C вместо С++, вы можете посмотреть абстракцию Array_T в библиотеке Dave Hanson C Интерфейсы и реализации. Он исключительно чист и хорошо разработан. У меня мои ученики делают двумерную версию в качестве упражнения. Вы могли бы это сделать или просто написать дополнительную функцию, которая выполняет преобразование индекса, например,
void *Array_get_2d(Array_T a, int width, int height, int i, int j) {
return Array_get(a, j * width, i, j);
}
Немного чище иметь отдельную структуру, в которой вы храните ширину, высоту и указатель на элементы.
Ответ 8
Недавно я столкнулся с подобной проблемой. У меня не было Boost. Векторы векторов оказались довольно медленными по сравнению с обычными массивами. Наличие массива указателей делает инициализацию намного более трудоемкой, потому что вам нужно выполнять итерацию по каждому измерению и инициализировать указатели, возможно, имея некоторые довольно громоздкие, каскадные типы в процессе, возможно, с большим количеством typedef.
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не был уверен, должен ли я опубликовать это как ответ, потому что он отвечает только на часть вашего вопроса. Приношу свои извинения за следующее:
- Я не рассматривал, как читать измерения со стандартного ввода, как отмечали другие комментаторы.
- Это прежде всего для С++.
- Я только закодировал это решение для двух измерений.
Я решил опубликовать это так или иначе, потому что я вижу векторы векторов, которые часто возникают в ответ на вопросы о многомерных массивах на С++, без упоминания об аспектах производительности (если вам это интересно).
Я также истолковал основную проблему этого вопроса как о том, как получить динамические многомерные массивы, которые можно использовать с такой же легкостью, как пример Java из вопроса, т.е. без хлопот при вычислении индексов с псевдо-многомерный одномерный массив.
Я не видел расширений компилятора, упомянутых в других ответах, таких как GCC/g++, чтобы объявлять многомерные массивы с динамическими границами так же, как и со статическими границами. Насколько я понимаю, вопрос не ограничивает ответы на стандартный C/С++. ISO C99, по-видимому, поддерживает их, но в С++ и предыдущих версиях C они, похоже, являются расширениями, специфичными для компилятора. См. Этот вопрос: Динамические массивы в C без malloc?
Я придумал способ, которым люди могут понравиться на С++, потому что это маленький код, имеет простоту использования встроенных статических многомерных массивов и так же быстро.
template <typename T>
class Array2D {
private:
std::unique_ptr<T> managed_array_;
T* array_;
size_t x_, y_;
public:
Array2D(size_t x, size_t y) {
managed_array_.reset(new T[x * y]);
array_ = managed_array_.get();
y_ = y;
}
T* operator[](size_t x) const {
return &array_[x * y_];
}
};
Вы можете использовать его так. Размеры не
auto a = Array2D<int>(x, y);
a[xi][yi] = 42;
Вы можете добавить утверждение, по крайней мере, ко всем, кроме последнего измерения, и расширить идею до более чем двух измерений. Я сделал сообщение в своем блоге об альтернативных способах получения многомерных массивов. Я также более конкретно отношусь к относительной производительности и кодированию там.
Производительность динамических многомерных массивов на С++
Ответ 9
Невозможно определить длину заданного массива в С++. Лучшим способом, вероятно, было бы передать длину каждого измерения массива и использовать это вместо свойства .length самого массива.
Ответ 10
Вы можете использовать malloc для выполнения этого и до сих пор иметь доступ к нему с помощью обычного массива [] [], значит, стихи метода массива [rows * cols + cols].
main()
{
int i;
int rows;
int cols;
int **array = NULL;
array = malloc(sizeof(int*) * rows);
if (array == NULL)
return 0; // check for malloc fail
for (i = 0; i < rows; i++)
{
array[i] = malloc(sizeof(int) * cols)
if (array[i] == NULL)
return 0; // check for malloc fail
}
// and now you have a dynamically sized array
}