Обозначение элементов многомерного массива

Описание многомерного массива позволяет использовать в программе любой из его элементов как индексированную переменную.

Индексированная переменная (индексное выражение) – обозначение ячейки для хранения конкретного элемента массивауказанием идентификатора массива и индексов элемента по каждому измерению.

В массивах Си/Си++ индексы элементов на единицу меньше заданных математически. Это обстоятельство должно учитываться в программе, особенно при формировании условия повторения (выхода из) цикла.

Особенность работы с массивами в Си/Си++ – любой двумерный массив можно представить в виде одномерного при условии укрупнения единицы хранения (элемента).

Например, если в качестве элементов выбрать строки (столбцы), то двумерный массив превратится в одномерный массив строк (столбцов).

Хранение двумерного массива, например X(m n), реализуется схемой распределения оперативной памяти (рис. 9.4).

Для хранения трехмерного массива, например S(k m n), схема распределения оперативной памяти представлена на рис 9.5 (первая и последняя страницы).

x00 x01 . . . x0j . . . x0 n-1

 

  x10 x11 . . . x1j . . . x1 n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  xi0 xi1 . . . xij . . . xi n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  xm-1 0 x m-1 1 . . . x m-1 j . . . x m-1 n-1  

Рис. 9.4. Хранение элементов двумерного массива

s000 s001 . . . s00j . . . s00 n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  s0i0 s0i1 . . . s0ij . . . s0i n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  s0m-1 0 s0 m-1 1 . . . s0 m-1 j . . . s0 m-1 n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  sk-100 sk-101 . . . sk-10j . . . sk-10 n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  sk-1i0 sk-1i1 . . . sk-1ij . . . sk-1i n-1

 

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

 

  sk-1m-10 sk-1 m-1 1 . . . sk-1 m-1 j . . . sk-1 m-1 n-1  

Рис. 9.5. Хранение элементов трехмерного массива

Анализ схем показывает, что все элементы многомерного массива располагаются в оперативной памяти линейно (непрерывно и последовательно). При этом двумерный массив представляется последовательностью строк, трехмерный – последовательностью страниц, каждая из которых в свою очередь является последовательностью строк.

Изменение индексов происходит последовательно – справа налево. Например, для трёхмерного массива вначале полностью перебирается крайний правый индекс (столбцов), затем средний (строк) и последним – левый (страниц).

Длина ячейки хранения каждого элемента определяется типом массива.

Таким образом, любой трехмерный массив представляется в виде одномерного массива двумерных матриц (страниц), каждая из которых, в свою очередь, рассматривается как одномерный массив строк (столбцов).

Следовательно, n-мерный массив в Си/Си++ интерпретируется как совокупность массивов (n-1) размерности, которые также могут быть представлены совокупностью массивов еще меньшей размерности.

Структура обозначения индексированной переменной многомерного массива:

имя[индекс_1][индекс_2]...[индекс_i]...[индекс_n]

где имя – идентификатор массива;

индекс_i – целая константа, задающая номер элемента по i-му измерению;

[ ] – ограничители индекса элемента по каждому измерению.

Так, в описанном ранее массиве D(20 30) элемент, расположенный в первом столбце первой строки, обозначается индексным выражением d[0][0], во втором столбце той же строки – d[0][1], в первом столбце второй строки – d[1][0], текущий – d[i][j], элемент последнего столбца, последней строки – d[19][29].

Рассмотренный пример идентификации элементов массива D применим к любому из двумерных массивов, указанных в соответствующем описателе.

Для трехмерных массивов обозначение элементов выполняется аналогично. Например, в массиве S(10 5 15) (описан ранее) элемент первой страницы на пересечении первой строки и первого столбца обозначается индексным выражением s[0][0][0], элемент второго столбца первой строки той же страницы – s[0][0][1], второго столбца второй строки первой страницы – s[0][1][1], текущий – s[k][i][j], а элемент последнего столбца, последней строки, последней страницы – s[9][4][14].

Индекс, при необходимости, может задаваться арифметическим выражением. Например, d[i+2][5], d[i][j-1], s[3][i-1][j+2], s[k-3][i][2*j].

ü Внимание! Индекс на момент использования переменной должен быть определен (рассчитан) и укладываться в заданный описателем диапазон.

Рассмотренные формы представления индексированных переменных позволяют осуществить программную реализацию элементов алгоритма с использованием многомерных массивов.

Непрерывное и последовательное расположение элементов многомерного массива в оперативной памяти позволяет адрес каждого элемента представить зависимостью:

а = а1 + смещение,

где а – адрес некоторого текущего элемента массива;

а1 – адрес первого элемента массива;

смещение – номер текущего элемента относительно первого.

Смещение рассчитывается для массивов различной размерности по аналогичным методикам.

Так для двумерного массива

смещение = индекс_1*разм_2+индекс_2

т.е. произведение номера текущей строки на ее размер (число столбцов) плюс номер текущего столбца. Первое слагаемое определяет число элементов в вышерасположенных строках, второе – число элементов в текущей строке от ее начала до искомого включительно.

Для трехмерного массива

смещение = индекс_1*(разм_2* разм_3) +

+ индекс_2 * разм_3 + индекс_3

Первое слагаемое определяет число элементов в ранее расположенных страницах, второе – в предыдущих строках текущей страницы, третье – число элементов в текущей строке текущей страницы.

ü Внимание! Для любого массива размер первого измерения (разм_1) в расчете смещения не используется.

В качестве сомножителей (разм_i) используются значения, указанные в описателях массивов.

Описатели массивов определяют максимально возможные значения (размеры) каждого измерения. Это позволяет использовать зарезервированное пространство оперативной памяти полностью, либо частично, обеспечивая универсальность размеров в сторону уменьшения. Для двумерного массива это утверждение поясняется схемой (рис. 9.6):

Размер, заданный в описателе (максимальное число столбцов nmax)  
Размер, используемый в расчетах (n)    
                    Размер, использу-емый в расчетах (m) Размер, заданный в описателе (максимальное число строк mmax)
                   
                   
                   
                     
                   
                   
                         

Рис. 9.6. Соответствие реальных размеров описанным

Размеры, указанные в описателе определяют количество зарезервированных в оперативной памяти ячеек. При этом ячейки создаваемого двумерного массива располагаются последовательно и линейно (построчно). Если в расчетах зарезервированное пространство используется частично (с меньшим числом строк и/или столбцов), то участки с хранимыми значениями будут чередоваться с неиспользуемыми, количество которых должно быть учтено при указании длины каждой строки в индексном выражении. Суммарное количество элементов каждой строки задано в описателе массива. Поэтому адреса любой ячейки определяется индексным выражением, использующим в качестве одного из параметров указанный в описателе размер.

Так, если двумерный массив z описан как z[10][20], а в задаче используется с размерами m=7, n=12, то адрес текущего элемента &z[i][j] = z[0] + i * 20 + j, а не &z[i][j] = z[0] + i * n + j.

Исходя из изложенного, адрес i-го, j-го элемента массива D(20х30) вычисляется по формуле

&(d[i][j]) = d[0] + i * 30 + j,

а адрес k-го, i-го, j-го элемента массива S(10х5х15) вычисляется как

&(s[k][i][j]) = s[0][0] + k * (5 * 15) + i * 15 + j

Таким образом, индексное выражение полностью определяет адрес конкретной ячейки для хранения соответствующего элемента через параметры описателя, а не укороченные, реально заданные.

С учетом изложенного идентификация переменных алгоритма и создаваемой программы представлена в табл. 9.1.

Таблица 9.1

Обозначение в алгоритме m n i j xпр xij yij
Обозначение в программе m n i j xp x[i][j] y[i][j]

На основании схемы алгоритма и таблицы идентификации составим программу решения задачи.

Классический вариант программирования задачи

#include <stdio.h> /* директивы */

#include <conio.h> /* препроцессора */

#include <windows.h>

#define M 10 /* увеличенные */

#define N 12 /* размеры массивов */

main( ) /* заголовок головной функции */

{

float x[M][N], y[M][N]; /* описатели массивов */

int i, j, n, m;

char buf[50]; /*описание символьного массива*/

clrscr( );

CharToOem(" Введите m (m<= ",buf); /* запрос */

printf("\n %s %d):",buf,M); /* и */

scanf("%d", &m); /* ввод */

CharToOem(" Введите n (n<= ",buf); /* фактических */

printf("\n %s %d):",buf,N); /* размеров */

scanf("%d", &n); /* массивов */

printf("\n n=%d m=%d ", n, m); /*вывод размеров массивов*/

for( i = 0; i < m; i++ ) /*заголовок внешнего цикла ввода x[i][j]*/

for( j = 0; j < n; j++ ) /*заголовок внутр. цикла ввода x[i][j]*/

{

CharToOem(" Введите значение ",buf); /* ввод */

printf("\n %s x[%d][%d]:",buf,i+1, j+1); /* элементов */

scanf("%f", & x[i][j]); /*массива Х*/

}

CharToOem(" Массив X",buf); /* вывод */

printf("\n %s \n",buf); /*заголовка*/

for(i = 0; i < m; i++)/* заголовок внешн. цикла вывода x[i][j]*/

{

for(j=0; j < n; j++)/*заголовок внутр. цикла вывода x[i][j]*/

printf(" %5.2f", x[i][j]);

printf("\n");

}

for(i = 0; i < m ; i++ /*заголовок внешн. цикла расчета y[i][j]*/

for( j = 0; j < n; j++)/*заголовок внутр. цикла расчета y[i][j]*/

y[ i ][ j ] = x[ i ][ j ] / 2.;

CharToOem(" Массив Y",buf); /* вывод */

printf("\n %s \n",buf); /*заголовка*/

for( i = 0 ; i < m ; i++ )/*заголовок внешн. цикла вывода y[i][j]*/

{

for(j = 0; j < n; j++) /*заголовок внутр. цикла вывода y[i][j]*/

printf(" %5.2f", y[i][j]);

printf("\n");

}

getch( );

}

2 3 – размеры массива;

10. 20. 30. – элементы первой строки;

100. 200. 300. – элементы второй строки.

Под закрывающей скобкой приведены исходные данные для решения задачи.

Результаты решения представлены в приложении 9.1.

Программирование задачи с графическим интерфейсом

Программирование задачи при использовании графического интерфейса предварим его разработкой.

ListBoxХi
ListBoxYi

Для ввода количества столбцов и строк массива планируем однострочные поля редактирования (EditN, EditМ). Для ввода элементов массива Х – многострочное поле редактирования (EditХ). Вывод элементов массивов X и Y реализуем в поля-списки (ListBoxXi, ListBoxYi).

Управление процессом решения реализуем двумя командными кнопками, расположенными в нижней части окна. Назначение каждой определяется ее названием.

Использование графического интерфейса для вывода числовых элементов двумерных массивов в виде таблицы требует:

· представления каждого числового данного соответствующей символьной строкой;

· формирования общей символьной строки (из полученных для каждого элемента строк), соответствующей числовой строке массива;

· размещение сформированной общей символьной строки в окне вывода.

Представление числовых данных символьными строками комментариев не требует.

Формирование элементов выводимой строки в единое целое выполняется функцией «склеивания» строк strcat.

Функция «склеивания» символьных строк strcat( )

Функция предназначена для получения результирующей строки из двух исходных строк. Структура функции:

strcat( buf1, buf2 )

где strcat – обозначение функции;

buf1 – имя исходной (результирующей) символьной строки;

buf2 – имя добавляемой символьной строки;

( ) – ограничители аргумента.

Функция располагается в библиотеке string.h.

Правила записи и использования

1. Операнды buf1 и buf2 – символьные строки. Строка buf1 увеличивает свое значение после выполнения функции на величину buf2.

2. Обязательное условие формирования строк buf1 и buf2 – окончание каждой символом «\0».

3. Пробелы, при необходимости, формируются структурой соответствующей строки (включением в нее).

4. Однократное использование функции – чтение строки buf1, добавление к ней строки buf2 и занесение результата в buf1. Поэтому размер buf1 в описателе создается увеличенным (на величину добавляемых компонентов).

5. Многократное использование функции – последовательное добавление второго операнда (buf2) к предварительно полученной строке buf1.

6. Повторное использование функции для создания новой результирующей строки требует предварительной очистки первого аргумента функции. Один из вариантов – присваивание строке buf1 пустой строки: sprintf(buf1,"%s","");

7. Проверка результирующей строки на переполнение не выполняется.

8. Функция используется как операнд арифметического выражения (присваивания) или самостоятельный оператор.

Общий вид фрагмента программы «склеивания» символьных строк str и buf:

#include <string.h> /* директива препроцессора*/

char str[25], buf[10]; /*описатель символьных строк*/

EditStr->GetText(str, 10); /*ввод строки str из поля EditStr*/

EditBuf->GetText(buf, 10); /*ввод buf из поля EditBuf*/

strcat(str, buf); /*формирование результирующей строки str «склеиванием» исходных строк str и buf*/

Описатель типа определяет массивы str и buf как символьные максимальной длины 25 и 10 символов. Пятая и шестая строки предписывает ввод строк str и buf из полей EditStr и EditBuf соответственно. Оператор strcat(str, buf); формирует «склеенную» строку и хранит ее под именем str .

Многократное использование функции позволяет создать результирующую строку с любым количеством компонентов в пределах, предусмотренных размером buf1.

Вариант 1: последовательное соединение нескольких строк

#include <string.h> /* директива препроцессора*/

char str[25], buf1[10], buf2[5];/*описатель символьных строк*/

EditStr->GetText(str, 10); /*ввод строки str из поля EditStr*/

EditBuf1->GetText(buf1, 10); /*ввод buf1 из поля EditBuf1*/

EditBuf2->GetText(buf2, 10); /*ввод buf2 из поля EditBuf2*/

strcat(str, buf1); /*формирование результирующей строки str «склеиванием» исходных строк str и buf1*/

strcat(str, buf2); /*формирование результирующей строки str «склеиванием» полученных str и buf2*/

Описатель типа определяет массивы str, buf1 и buf2 как символьные, максимальной длины 25, 10 и 5 символов, соответственно. Пятая, шестая и седьмая строки предписывает ввод str, buf1 и buf2 из полей EditStr, EditBuf1 и EditBuf2 соответственно. Операторы strcat(str, buf1); и strcat(str, buf2); последовательно формируют «склеенную» строку из str, buf1 и buf2. Полученная строка имеет имя str .

Вариант 2: использование функции в теле цикла.

#include <string.h> /* директива препроцессора*/

char str[50] = “ ”, buf[10];/*описание и инициализация

символьных строк*/

for( j = 0 ; j < 5 ; j++ ) /* заголовок цикла ввода buf и формирования str*/

{

EditBuf->GetLine( buf, 10, j); /* ввод buf */

strcat(str, buf); /*формирование результирующей строки str «склеиванием» исходных строк str и buf*/

}

Описатель типа определяет массивы str и buf как символьные максимальной длины 50 и 10 символов соответственно и инициализирует str пустой строкой. Оператор EditBuf->GetLine (buf, 10, j); предписывает ввод buf из j-й строки многострочного поля EditBuf. Оператор strcat(str, buf); формирует в теле цикла, из последовательно вводимых строк buf, «склеенную» строку и хранит ее под именем str .

С учетом планируемого интерфейса выполним программирование задачи.

#include<stdio.h>

#include<stdlib.h>

#include <string.h>

void TSumprDlgClient::Ok()

{

// INSERT>> Your code here.

float x[M][N], y[M][N]; /* описатели массивов */

int i, j, n, m;

char buf[30],buf1[90]=" "; /*описание символьного массива*/

ListBoxYi->ClearList(); /*очистка поля вывода*/

ListBoxXi->ClearList(); /*очистка поля вывода*/

EditN->GetText(buf, 10); /*ввод количества*/

n = atoi(buf); /* столбцов массива*/

EditM->GetText(buf, 10); /*ввод количества*/

m = atoi(buf); /* строк массива*/

for( i = 0 ; i < m ; i++ ) /* заголовок внешн. цикла ввода x[i][j] */

for( j = 0 ; j < n ; j++ ) /* заголовок внутр. цикла ввода x[i][j] */

{

EditX->GetLine(buf, 30, i*n+j); /* ввод элементов */

x[i][j]=atof(buf); /* массива Х*/

}

for(i = 0; i < m; i++) /*заголовок внешн. цикла вывода x[i][j]*/

{

for(j = 0; j < n; j++)/*заголовок внутр. цикла вывода x[i][j]*/

{

sprintf(buf,"%11.3f",x[i][j]); /* вывод текущих*/

strcat(buf1, buf); /*склеенных*/

}

ListBoxXi->AddString(buf1); /*значений xi*/

sprintf(buf1,"%s","");

}

for( i = 0; i < m; i++)/*заголовок внешн. цикла расчета y[i][j]*/

for(j = 0; j < n; j++) /*заголовок внутр. цикла расчета y[i][j]*/

y[ i ][ j ] = x[ i ][ j ] / 2.;

for( i = 0 ; i < m ; i++ )/*заголовок внешн. цикла вывода y[i][j]*/

{

for(j = 0; j < n; j++)/*заголовок внутр. цикла вывода y[i][j]*/

{

sprintf(buf,"%11.6f",y[i][j]); /* вывод текущих*/

strcat(buf1, buf); /*склеенных*/

}

ListBoxYi->AddString(buf1); /*значений yi*/

sprintf(buf1,"%s","");

}

}

3 2 – размеры массива;

10. 20. 30. – элементы первой строки;

100. 200. 300. – элементы второй строки.

Под закрывающей скобкой приведены исходные данные для решения задачи.

Результаты решения представлены в приложении 9.2.