Ответы на вопросы создания многомерных массивов есть в просторах интернета. Ответы на вопросы о создании трёхмерных, четырёхмерных и более многомерных массивов выступают в качестве притесняемого меньшинства. На самом деле трёхмерные и более мерные массивы не так уж и нужны. Они не очень удобны, их сложно обрабатывать.
Можно использовать или чистый трёхмерный массив, или эмулировать трёхмерный массив с помощью указательных переменных. Алгоритм эмуляции указателями многомерных массивов в принципе один и тот же: сначала выделяется память основной указательной переменной, указывающей на массив. Для каждой внутренней указательной переменной выделяется собственная память. И пока указатели указывают на указатели — так и выделяется память каждому указателю.
Любой многомерный массив — это массив массивов.
Создавать трёхмерный массив можно несколькими способами:
Использовать чистый массив: [10][20][30] — массив, хранящий 20 массивов, где каждый из 20 массивов хранит 30 массивов
Использовать указатель на чистый двумерный массив: (*)[20][30] — указатель на начало массива, хранящего 20 массивов, где каждый из 20 массивов хранит 30 массивов. В этом случае указателю нужно выделить память или направить указатель на уже зарезервированную программой память, т. е. сделать так, чтобы указатель указывал на доступную область памяти, а не не пойми куда.
Использовать указатель на указатель, указывающий на одномерный массив: (**)[30] — эту штуку можно называть [массивом] [указателей, указывающих на указатели]. Всем указателям нужно выделять память или направлять их на уже выделенное место в памяти.
Использовать указатель на указатель на указатель: *** — В этом случае всё как и в предыдущих, указатели нужно делать валидными.
Для любой большей мерности массивам — всё происходит по похожей схеме.
В случае чистого трёхмерного массива много проблем не возникает:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
usingnamespacestd;
intmain(){
//все количества ячеек должны быть заданы константами
constintN=10;
constintM=20;
constintK=30;
intarr[N][M][K];//Трёхмерный массив
/*Заполняем трёхмерный массив значениями*/
intcount=1;//число для заполнения
for(inti=0;i<N;i++){
for(intj=0;j<M;j++){
for(intk=0;k<K;k++){
arr[i][j][k]=count++;
}
}
}
/*Выводим массив на экран*/
for(inti=0;i<N;i++){
for(intj=0;j<M;j++){
for(intk=0;k<K;k++){
cout<<arr[i][j][k]<<'\t';
}cout<<'\n';
}cout<<'\n';
}
cin.get();
}
Поскольку трёхмерный массив — это массив двумерных массивов, а любой двумерный массив легко представлять в виде таблицы, вы видите на экране массив таблиц. Если у вас консольное окно узкое, то вывод произойдёт не очень красиво, нужно будет изменить ширину в свойствах окна консоли. Если у вас Borland C++3.1 или что-то похожее, то вы не сможете в полной мере насладиться красивым видом массива таблиц. При первых попытках запусков программ, создаваемых из показанных кодов, не задавайте слишком большие числа, иначе в третьем, например, варианте, у вас вывод на экран может происходить больше суток, если так получится, что суммарно ячеек будет нужно слишком много.
В случае добавления указателей, нужно выделять и чистить память. В первом случае, когда добавляется один указатель, у вас появляется возможность сказать программе, сколько ячеек для таблиц вы хотите создать, сказать об этом прямо в ходе работы программы:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
usingnamespacestd;
intmain(){
intN=10;//Из-за добавления указательной переменной можно не const
constintM=20;
constintK=30;
cout<<"input N: ";
cin>>N;
int(*arr)[M][K];//Указатель на двухмерный массив как трёхмерный массив
arr=newint[N][M][K];
/*Заполняем трёхмерный массив значениями*/
intcount=1;//число для заполнения
for(inti=0;i<N;i++){
for(intj=0;j<M;j++){
for(intk=0;k<K;k++){
arr[i][j][k]=count++;
}
}
}
/*Выводим массив на экран*/
for(inti=0;i<N;i++){
for(intj=0;j<M;j++){
for(intk=0;k<K;k++){
cout<<arr[i][j][k]<<'\t';
}cout<<'\n';
}cout<<'\n';
}
cin.get();
delete[]arr;
}
Когда вы делаете указатель на двухмерный массив, обязательно оборачивать указательную часть в скобки, иначе вы получите не указатель не массив, а двухмерный массив указателей.
Благодаря использованию свойств указателей мы получили возможность задавать, сколько таблиц должен хранить наш массив. Для этого мы использовали добавление одной звёздочки.
То, что написано ниже — это для саморазвития. Будете делать на практике, оторвут уши и руки.
В случае добавления ещё одной звёздочки можно будет и задавать количество таблиц, и хранить таблицы с разным числом строк:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
usingnamespacestd;
intmain(){
intX=2;
intY=3;
constintZ=4;
cout<<"how many table? ";//Сколько таблиц создать?
int*temp=newint[X];//массив для хранения количеств ячеек
intcount=1;//переменная для заполнения массива значениями
for(inti=0;i<X;i++){//Заполняем трёхмерный массив
cout<<"col_row: ";//Сколько строк дать таблице, хранимой в ячейке?
cin>>temp[i];//Вводим число строк
arr[i]=newint[temp[i]][Z];//Выделяем память
for(intj=0;j<temp[i];j++){//Заполняем таблицу
for(intk=0;k<Z;k++){
arr[i][j][k]=count++;//Записываем значение в ячейку
}
}
}
/*обходим трёхмерный массив*/
for(inti=0;i<X;i++){
for(intj=0;j<temp[i];j++){
for(intk=0;k<Z;k++){
cout<<arr[i][j][k]<<'\t';
}cout<<'\n';
}cout<<'\n';
}
/*Обязательно чистим память*/
for(inti=0;i<X;i++){
delete[]arr[i];
}delete[]arr;
(cin.ignore()).get();//чтобы окно сразу не закрылось
}
Операция new должна разобраться, какая из размерностей динамическая и что здесь есть указатель. Из-за синтаксической особенности типа массива приходится выделывать такие страшные штуки, которые были проделаны при выделении памяти. Тут сложности с тем, что могут быть массивы указателей, указатели на массивы указателей, массивы указателей на указатели, массивы указателей, указывающих на массивы и много ещё чего, а компилятор должен точно знать, чего мы от него хотим.
В том случае, если массив не смешивается с указателями, такие страшные штуки не нужны. Если мы добавим третью звёздочку к нашей сущности, выполняющей роль массива, то тогда мы получим массив, способный хранить таблицы и с разным числом строк, и с разным числом колонок.
Если использовать все три звёздочки, то можно задавать и количество строк для каждой таблицы, и количество колонок для каждой строки таблицы. Но я немного в упрощённом варианте покажу. В моём случае для каждой конкретной таблицы будет задаваться определённое число строк и определённое для конкретной таблицы число колонок:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
usingnamespacestd;
intmain(){
intX=2;
intY=3;
intZ=4;
cout<<"how many table? ";//Сколько таблиц создать?
cin>>X;//Вводим число
int***arr=newint**[X];//Выделяем память для основного трёхмерного массива
int(**temp)=0;//Хранение числа строк и числа колонок в конкретной таблице
temp=newint*[X];//Создаём базу массива хранения
for(inti=0;i<X;i++){//Цикл для обхода таблиц
cout<<i+1<<"table\n";
cout<<"how many string for current table? ";//Сколько ячеек выделить под таблицы?
cin>>Y;
arr[i]=newint*[Y];//Выделяем память под строки конкретной таблицы
temp[i]=newint[2];//Выделяем память массиву хранения числа ячеек
temp[i][0]=Y;//Сохраняем текущее значение во временный массив
cout<<"how many cols for current table? ";//как много колонок дать этой таблице?
cin>>temp[i][1];//Сохраняем число колонок конкретной таблицы в массив хранения
for(intj=0;j<temp[i][0];j++){
arr[i][j]=newint[temp[i][1]];//Выделяем память под колонки для строки
}
cout<<'\n';
}
/*Заполняем массив*/
intcount=0;
for(inti=0;i<X;i++){
for(intj=0;j<temp[i][0];j++){//обходим по массиву, хранящему число строк
for(intk=0;k<temp[i][1];k++){//обходим по массиву, хранящему число колонок
arr[i][j][k]=count++;//наращиваем значение
}
}
}
for(inti=0;i<X;i++){
for(intj=0;j<temp[i][0];j++){//обходим по массиву, хранящему число строк
for(intk=0;k<temp[i][1];k++){//обходим по массиву, хранящему число колонок
cout<<arr[i][j][k]<<'\t';//выводим
}cout<<'\n';
}cout<<'\n';
}
/*ОБЯЗАТЕЛЬНАЯ ЗАЧИСТКА*/
for(inti=0;i<X;i++){
for(intj=0;j<temp[i][0];j++){
delete[]arr[i][j];
}delete[]arr[i];
delete[]temp[i];//чистим память от вспомогательного массива
}delete[]arr;//чистим память от основного массива
delete[]temp;//чистим память основного трехмерного массива
//Уничтожаем основной массив
(cin.ignore()).get();//чтобы окно сразу не закрылось
}
Основная возможная сложность, которая идёт за осознанием природы указателей и массивов — может быть в том, что нужно дополнительно отслеживать, сколько в таблице вообще строк и сколько колонок в каждой отдельной строке таблицы. Поскольку у меня немного упрощённый вариант, я отслеживаю количество колонок не для каждой отдельной строки, а только для конкретной таблицы. Но развить можно таким образом, чтобы у каждой строки было своё число колонок, тогда таблицы мало будут похожи на таблицы.
Но ещё раз напоминаю, что делать таких вещей совсем не нужно (если только для спортивного интереса в самом глубоком бомбоубежище, чтобы никто вас не застал за этим грязным делишком). Эти примеры даны вам больше для того, чтобы вы получили некоторое представление о работе указателей и массивов, получили некоторое представление о трёхмерном массиве, который задаётся с помощью указателей, чтобы поняли в чём сложности работы с указательными переменными и поняли некоторые другие вещи. Даже когда вы поймёте, как эти примеры работают, почему они работают, и когда сможете сами писать такие же коды, писать таким образом ни в коем случае не нужно: если есть большое желание решить задачу, требующую похожих поведений, и хочется использовать именно показанный в этой статье способ, вам нужно переосмыслить вариант решения, наверняка найдётся что-то проще, читабельней и понятнее всем, чем то, что показывал я сейчас.
Эта статья не затрагивает массивы двухуровневых указателей и двумерные массивы указателей, но эти массивы тоже возможно трактовать как трёхмерные и использовать их при определённых нуждах, но как и ранее, указателей имеет смысл избегать, особенно многоуровневых указательных сущностей. В подавляющем большинстве случаев вам может хватить или обычного трёхмерного массива, размерности которому задаются сразу в исходном коде программы, или одноуровневого указателя на двухмерный массив, чтобы можно было сообщать в ходе работы программы, какой нужна первая (слева) мерность трёхмерному массиву. Всё остальное от лукавого.
2 комментария на «“С++ для начинающих. Выделение памяти для трехмерного массива. Многомерные массивы.”»
Я в книге нашел два способа выделения памяти для динамических массивов и использовал вот этот int (*a)[6][5]= new int [3][6][5];
Правда там был пример для двухмерного дин. массива, но методом тыка я переделал под трехмерный.
Так вот по условию задачи мне нужно увеличить индексация начального элемента на 1 (из [0][0][0] сделать [1][1][1]).
Для одномерного массива я писал бы а+=1, но тут эта строка меняет лишь номер матрицы (из [0][0][0] делает [1][0][0]).
Кто знаком с такой конструкцией и принято ли ею пользоваться ?
Это указатель на начало массива двумерных массивов [6][5], т.е. это как массив из массивов [6][5].(только оно не массив, а указатель на набор массивов [6][5]. Это важно).
int (*a)[6][5] = new int[3] -> здесь а становится массивом с тремя значениями, каждое из которых массив фиксированного размера [6][5].
a[0] — массив[6][5], a[1] — массив[6][5], a[2] — массив [6][5]
Я в книге нашел два способа выделения памяти для динамических массивов и использовал вот этот int (*a)[6][5]= new int [3][6][5];
Правда там был пример для двухмерного дин. массива, но методом тыка я переделал под трехмерный.
Так вот по условию задачи мне нужно увеличить индексация начального элемента на 1 (из [0][0][0] сделать [1][1][1]).
Для одномерного массива я писал бы а+=1, но тут эта строка меняет лишь номер матрицы (из [0][0][0] делает [1][0][0]).
Кто знаком с такой конструкцией и принято ли ею пользоваться ?
Это указатель на начало массива двумерных массивов [6][5], т.е. это как массив из массивов [6][5].(только оно не массив, а указатель на набор массивов [6][5]. Это важно).
int (*a)[6][5] = new int[3] -> здесь а становится массивом с тремя значениями, каждое из которых массив фиксированного размера [6][5].
a[0] — массив[6][5], a[1] — массив[6][5], a[2] — массив [6][5]
Вот пример кода.