- •Тема: Массивы. Двумерные массивы Краткий обзор языка
- •Одномерные массивы
- •Действия над массивами
- •Двумерные массивы Паскаля – матрицы
- •Описание двумерного массива Паскаля.
- •Пример описания двумерного массива Паскаля
- •Основные действия с двумерными массивами Паскаля
- •Ввод двумерного массива Паскаля
- •Вывод двумерного массива Паскаля на экран
- •Представление двумерного массива Паскаля в памяти
- •I, j: integer; { индексы массива }
- •Примеры решения задач с двумерными массивами Паскаля
- •Методы доступа к элементам массивов
- •Динамическое размещение массивов
- •Индексный массив
- •Специфические типы массивов Динамические массивы
- •Гетерогенные массивы
- •Массивы массивов
- •Примеры задач "Переворот" массива
- •Сдвиг массива на k элементов влево или вправо
- •Поиск минимума/максимума и их индексов
- •Поиск минимума/максимума и их индексов в двумерном массиве (матрице)
- •Работа с элементами выше/ниже/на главной/побочной диагоналях
- •Транспонирование матрицы относительно главной и побочной диагоналей
- •Поворот двумерного массива на 90° по часовой стрелке
- •Поворот на 90 градусов по часовой стрелке и против без использования дополнительного массива
- •Удаление элемента в одномерном массиве
- •Вставка элемента в одномерный массив
- •Удаление всех строк и столбцов, содержащих хоть 1 ноль.
- •Цифровая сортировка (DigidalSort)
- •Двоичный (бинарный) поиск
- •Работа с матрицей одним циклом
- •Заполнение массива случайными неповторяющимися значениями
- •Удалить все элементы, которые встречаются больше 1 раза
- •Удаление элементов в одномерном несортированном массиве по условию
- •Заполнение массива по спирали
- •Построение синусоиды на двумерном массиве(матрице) из точек(.)
- •Вставка нового столбца в матрицу со сдвигом исходных элементов
- •Переворот матрицы на 90° по часовой стрелке
- •Заполнение массива неповторяющимися значениями
Динамическое размещение массивов
При динамическом распределении памяти для массивов следует описать соответствующий указатель и присваивать ему значение при помощи функции calloc. Одномерный массив a[10] из элементов типа float можно создать следующим образом
float *a;
a=(float*)(calloc(10,sizeof(float));
Для создания двумерного массива вначале нужно распределить память для массива указателей на одномерные массивы, а затем распределять память для одномерных массивов. Пусть, например, требуется создать массив a[n][m], это можно сделать при помощи следующего фрагмента программы:
#include
main ()
{ double **a;
int n,m,i;
scanf("%d %d",&n,&m);
a=(double **)calloc(m,sizeof(double *));
for (i=0; i<=m; i++)
a[i]=(double *)calloc(n,sizeof(double));
. . . . . . . . . . . .
}
Аналогичным образом можно распределить память и для трехмерного массива размером n,m,l. Следует только помнить, что ненужную для дальнейшего выполнения программы память следует освобождать при помощи функции free.
#include
main ()
{ long ***a;
int n,m,l,i,j;
scanf("%d %d %d",&n,&m,&l);
/* -------- распределение памяти -------- */
a=(long ***)calloc(m,sizeof(long **));
for (i=0; i<=m; i++)
{ a[i]=(long **)calloc(n,sizeof(long *));
for (j=0; i<=l; j++)
a[i][j]=(long *)calloc(l,sizeof(long));
}
. . . . . . . . . . . .
/* --------- освобождение памяти ----------*/
for (i=0; i<=m; i++)
{ for (j=0; j<=l; j++)
free (a[i][j]);
free (a[i]);
}
free (a);
}
Рассмотрим еще один интересный пример, в котором память для массивов распределяется в вызываемой функции, а используется в вызывающей. В таком случае в вызываемую функцию требуется передавать указатели, которым будут присвоены адреса выделяемой для массивов памяти.
Пример:
#include
main()
{ int vvod(double ***, long **);
double **a; /* указатель для массива a[n][m] */
long *b; /* указатель для массива b[n] */
vvod (&a,&b);
.. /* в функцию vvod передаются адреса указателей, */
.. /* а не их значения */
..
}
int vvod(double ***a, long **b)
{ int n,m,i,j;
scanf (" %d %d ",&n,&m);
*a=(double **)calloc(n,sizeof(double *));
*b=(long *)calloc(n,sizeof(long));
for (i=0; i<=n; i++)
*a[i]=(double *)calloc(m,sizeof(double));
.....
}
Отметим также то обстоятельство, что указатель на массив не обязательно должен показывать на начальный элемент некоторого массива. Он может быть сдвинут так, что начальный элемент будет иметь индекс отличный от нуля, причем он может быть как положительным так и отрицательным.
Пример:
#include
int main()
{ float *q, **b;
int i, j, k, n, m;
scanf("%d %d",&n,&m);
q=(float *)calloc(m,sizeof(float));
/* сейчас указатель q показывает на начало массива */
q[0]=22.3;
q-=5;
/* теперь начальный элемент массива имеет индекс 5, */
/* а конечный элемент индекс n-5 */
q[5]=1.5;
/* сдвиг индекса не приводит к перераспределению */
/* массива в памяти и изменится начальный элемент */
q[6]=2.5; /* - это второй элемент */
q[7]=3.5; /* - это третий элемент */
q+=5;
/* теперь начальный элемент вновь имеет индекс 0, */
/* а значения элементов q[0], q[1], q[2] равны */
/* соответственно 1.5, 2.5, 3.5 */
q+=2;
/* теперь начальный элемент имеет индекс -2, */
/* следующий -1, затем 0 и т.д. по порядку */
q[-2]=8.2;
q[-1]=4.5;
q-=2;
/* возвращаем начальную индексацию, три первых */
/* элемента массива q[0], q[1], q[2], имеют */
/* значения 8.2, 4.5, 3.5 */
q--;
/* вновь изменим индексацию . */
/* Для освобождения области памяти в которой размещен */
/* массив q используется функция free(q), но поскольку */
/* значение указателя q смещено, то выполнение */
/* функции free(q) приведет к непредсказуемым последствиям. */
/* Для правильного выполнения этой функции */
/* указатель q должен быть возвращен в первоначальное */
/* положение */
free(++q);
/* Рассмотрим возможность изменения индексации и */
/* освобождения памяти для двумерного массива */
b=(float **)calloc(m,sizeof(float *));
for (i=0; i < m; i++)
b[i]=(float *)calloc(n,sizeof(float));
/* После распределения памяти начальным элементом */
/* массива будет элемент b[0][0] */
/* Выполним сдвиг индексов так, чтобы начальным */
/* элементом стал элемент b[1][1] */
for (i=0; i < m ; i++) --b[i];
b--;
/* Теперь присвоим каждому элементу массива сумму его */
/* индексов */
for (i=1; i<=m; i++)
for (j=1; j<=n; j++)
b[i][j]=(float)(i+j);
/* Обратите внимание на начальные значения счетчиков */
/* циклов i и j, он начинаются с 1 а не с 0 */
/* Возвратимся к прежней индексации */
for (i=1; i<=m; i++) ++b[i];
b++;
/* Выполним освобождение памяти */
for (i=0; i < m; i++) free(b[i]);
free(b);
...
...
return 0;
}
В качестве последнего примера рассмотрим динамическое распределение памяти для массива указателей на функции, имеющие один входной параметр типа double и возвращающие значение типа double.
Пример:
#include
#include
double cos(double);
double sin(double);
double tan(double);
int main()
{ double (*(*masfun))(double);
double x=0.5, y;
int i;
masfun=(double(*(*))(double))
calloc(3,sizeof(double(*(*))(double)));
masfun[0]=cos;
masfun[1]=sin;
masfun[2]=tan;
for (i=0; i<3; i++);
{ y=masfun[i](x);
printf("\n x=%g y=%g",x,y);
}
return 0;
}
Элементом массива может быть в свою очередь тоже массив. Таким образом, мы приходим к понятию двумерного массива или матрицы. Описание двумерного массива строится из описания одномерного путем добавления второй размерности, например:
int a[4][3];
Анализ подобного описания необходимо проводить в направлении выполнения операций [], то есть слева направо. Таким образом, переменная a является массивом из четырех элементов, что следует из первой части описания a[4]. Каждый элемент a[i] этого массива в свою очередь является массивом из трех элементов типа int, что следует из второй части описания.
Для наглядности двумерный массив можно представить в виде таблицы с числом строк, равным первому размеру массива, и числом столбцов, равным второму размеру массива, например:
-
Массива
Столбец 0
Столбец 1
Столбец 2
Строка 0
18
21
5
Строка 1
6
7
11
Строка 2
30
52
34
Строка 3
24
4
67
Имя двумерного массива без квадратных скобок за ним имеет значение адреса первого элемента этого массива, то есть значение адреса первой строки - одномерного массива из трех элементов. При использовании в выражениях тип имени двумерного массива преобразуется к типу адреса строки этого массива. В нашем примере тип имени массива a в выражениях будет приведен к типу адреса массива из трех элементов типа int и может использоваться во всех выражениях, где допускается использование соответствующего адреса.
Имя двумерного массива с одним индексным выражением в квадратных скобках за ним обозначает соответствующую строку двумерного массива и имеет значение адреса первого элемента этой строки. Например, в нашем случае a[2] является адресом величины типа int, а именно ячейки, в которой находится число 30, и может использоваться везде, где допускается использование адреса величины типа int.
Имя двумерного массива с двумя индексными выражениями в квадратных скобках за ним обозначает соответствующий элемент двумерного массива и имеет тот же тип. Например, в нашем примере a[2][1] является величиной типа int, а именно ячейкой, в которой находится число 52, и может использоваться везде, где допускается использование величины типа int.
В соответствии с интерпретацией описания двумерного массива (слева-направо) элементы последнего располагаются в памяти ЭВМ по строкам.
Инициализация двумерного массива также проводится по строкам, например, для того чтобы получить вышеописанный массив a, можно было бы провести следующую инициализацию
int a[][3] = {
{ 18, 21, 5 },
{ 6, 7, 11 },
{ 30, 52, 34 },
{ 24, 4, 67 }
};
Здесь первый размер массива будет определен компилятором. Следует отметить, что второй размер массива должен быть всегда указан. Это необходимо для того, чтобы сообщить компилятору размер строки массива, без которого компилятор не может правильно разместить двумерный массив в памяти ЭВМ.
Для инициализации двумерного массива символов можно использовать упрощенный синтаксис инициализации строк:
char s[][17] = {
"Строка 1",
"Длинная строка 2",
"Строка 3"
}
Размер памяти заказанный под каждую строку в этом случае должен быть равным длине самой длинной строки с учетом нуль-символа. При этом, для части строк (строка 1 и строка 3) будет выделено излишнее количество памяти. Таким образом, хранение строк различной длины в двумерном массиве символов недостаточно эффективно с точки зрения использования памяти.
Ввод двумерного массива осуществляется поэлементно с помощью двух вложенных циклов. Следующий фрагмент программы предназначен для ввода по строкам двумерного массива элементов типа double размером n строк на m столбцов
for (i=0; i<n; i++)
for (j=0; j<m; j++)
{
printf("a[%d][%d] = ", i, j);
scanf ("%lf", &a[i][j]);
}
Для ввода массива по столбцам достаточно поменять местами строки программы, являющиеся заголовками циклов.
Вывод такого же двумерного массива иллюстрирует следующий фрагмент:
for (i=0; i<n; i++)
{
for (j=0; j<m; j++) printf ("%9.3lf ", a[i][j]);
printf("\n");
}
В данном фрагменте после вывода очередной строки массива осуществляется переход на следующую строку дисплея.
В языке Си допускается использовать не только двумерные, но и трехмерные, четырехмерные и т. д. массивы. Их использование ничем принципиально не отличается от использования двумерных массивов, однако на практике они применяются значительно реже.
