Смекни!
smekni.com

Массивы. Двумерные массивы (стр. 5 из 7)

Пример:

int *ptr1, *ptr2, a[10];

ptr1=a+5;

ptr2=a+7;

if (prt1>ptr2) a[3]=4;

В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3]=4 не будет выполнен.

Массивы указателей

В языке СИ элементы массивов могут иметь любой тип, и, в частности, могут быть указателями на любой тип. Рассмотрим несколько примеров с использованием указателей.

Следующие объявления переменных

int a[]={10,11,12,13,14,};

int *p[]={a, a+1, a+2, a+2, a+3, a+4};

int **pp=p;

порождают программные объекты, представленные на схеме

pp pа . . . . .

в в в в в

18

aа 11 12 13 14 15

Схема размещения переменных при объявлении.

При выполнении операции pp-p получим нулевое значение, так как ссылки pp и p равны и указывают на начальный элемент массива указателей, связанного с указателем p ( на элемент p[0]).

После выполнения операции pp+=2 схема изменится и примет вид, изображенный

pp pа . . . .

в в в в в

aа 10 11 12 13 14

Схема размещения переменных после выполнения операции pp+=2.

Результатом выполнения вычитания pp-p будет 2, так как значение pp есть адрес третьего элемента массива p. Ссылка *pp-a тоже дает значение 2, так как обращение *pp есть адрес третьего элемента массива a, а обращение a есть адрес начального элемента массива a. При обращении с помощью ссылки **pp получим 12 - это значение третьего элемента массива a. Ссылка *pp++ даст значение четвертого элемента массива p т.е. адрес четвертого элемента массива.

Если считать, что pp=p, то обращение *++pp это значение первого элемента массива a (т.е. значение 11), операция ++*pp изменит содержимое указателя p[0], таким образом, что он станет равным значению адреса элемента a[1].

Сложные обращения раскрываются изнутри. Например обращение *(++(*pp)) можно разбить на следующие действия: *pp дает значение начального элемента массива p[0], далее это значение инкременируется ++(*p) в результате чего указатель p[0] станет равен значению адреса элемента a[1], и последнее действие это выборка значения по полученному адресу, т.е. значение 11.

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

int a[3][3]={ { 11,12,13 },

{ 21,22,23 },

{ 31,32,33 } };

int *pa[3]={ a,a[1],a[2] };

int *p=a[0];

Схема размещения указателей на двумерный массив.

Согласно этой схеме доступ к элементу a[0][0] получить по указателям a, p, pa при помощи следующих ссылок: a[0][0], *a, **a[0], *p, **pa, *p[0].

Рассмотрим теперь пример с использованием строк символов. Объявления переменных можно изобразить схемой представленной:

char *c[]={ "abs", "d", "yes", "no" };

char **cp[]={ c+3, c+2 , c+1 , c };

char ***cpp=cp;

Схема размещения указателей на строки.

Динамическое размещение массивов

При динамическом распределении памяти для массивов следует описать соответствующий указатель и присваивать ему значение при помощи функции 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("&bsol;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 и может использоваться во всех выражениях, где допускается использование соответствующего адреса.