Обозначение элементов многомерного массива
Описание многомерного массива позволяет использовать в программе любой из его элементов как индексированную переменную.
Индексированная переменная (индексное выражение) – обозначение ячейки для хранения конкретного элемента массивауказанием идентификатора массива и индексов элемента по каждому измерению.
В массивах Си/Си++ индексы элементов на единицу меньше заданных математически. Это обстоятельство должно учитываться в программе, особенно при формировании условия повторения (выхода из) цикла.
Особенность работы с массивами в Си/Си++ – любой двумерный массив можно представить в виде одномерного при условии укрупнения единицы хранения (элемента).
Например, если в качестве элементов выбрать строки (столбцы), то двумерный массив превратится в одномерный массив строк (столбцов).
Хранение двумерного массива, например 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.
Программирование задачи с графическим интерфейсом
Программирование задачи при использовании графического интерфейса предварим его разработкой.
|
|
Для ввода количества столбцов и строк массива планируем однострочные поля редактирования (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.