Урок 7.2.3. Доступ к элементам массива

7.2.3. Доступ к элементам массива

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

1. Использование индексированной переменной. Это наиболее естественный, простой и понятный способ доступа к элементам массива любой размерности. О доступе к элементам массива путем использования индексированной переменной уже говорилось в разделе 7.2.1. и поэтому повторяться не будем, а рассмотрим пример:

int i,j; // индексные переменные
int N, m[3][6] = { { 1, 2, 3, 4, 5, 6 },
                   { 7, 8, 9, 10, 11, 12 },
                   { 13, 14, 15, 16, 17, 18 }
               };
 
i = 2; j = 4;
N = m[i][j];     // N получит значение элемента, стоящего на 
                 // пересечении 2-ой строки и 4-го столбца
 
N = m[i-2][2*j-3]; // N получит значение элемента нулевой строки
                   // и 5-го столбца 
N = m[0][0];       // m[0][0] и m[2][5] -первый и последний элементы
m[0][0] = m[2][5]; // матрицы обменялись значениями 
m[2][5] = N;    
 
// печать матрицы в один столбец: 
for(i = 0; i< = 2; i++)
  for(j = 0; j< = 5; j++)
    printf("%d\n", m[i][j]);
 
// печать матрицы по строкам: 
 
for(i = 0; i< = 2; i++)
    printf("%3d%3d%3d%3d%3d%3d\n", 
            m[i][0],m[i][1],m[i][2],m[i][3],m[i][4],m[i][5]);


2. Использование имени одномерного массива. Этот способ доступа к элементам массива основан на том, что в Си имя одномерного массива трактуется как константа-указатель на данные, тип которых определен типом элементов массива, а значением этой константы является адрес нулевого элемента.

Следовательно, применив к имени массива операции адресной арифметики, можно получить доступ к любому элементу массива. Поэтому, например, для одномеpного массива, описанного как int mas[15];, выражения Си mas и &mas[0], mas+1 и &mas[1], mas+2 и &mas[2] являются равносильными.

Чтобы получить значение первого элемента массива, можно записать *mas, используя операцию косвенной адресации. Правила адресной арифметики и тот факт, что имя массива является константой типа указатель, дают возможность получить значение любого элемента одномерного массива, прибавляя соответствующее число к имени массива:

*(mas+0) - значение элемента mas[0],
*(mas+1) - значение элемента mas[1],
*(mas+2) - значение элемента mas[2],
......................................
*(mas+10) - значение элемента mas[10],
*(mas+11) - значение элемента mas[11] и т.д.

Таким способом мы получаем доступ к элементам массива путем использования адреса элемента. Здесь необходимо обратить внимание на роль скобок, например, *(mas+1) – это второй элемент массива mas, а *mas+1 – это прибавление единицы к первому элементу массива mas.

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

3. Доступ к элементам многомерных массивов. Как уже известно, многомерные массивы трактуются как одномерные, элементами которых могут быть любые объекты, в том числе и массивы. Когда в разделе 7.2.1. мы рассматривали распределение памяти под трехмерный массив int m[2][3][2], то употребляли имя массива с одним индексом, например, m[0] и m[1], а также с двумя индексами, например, m[1][0], и с тремя индексами, например m[0][2][1].

Кроме того, мы установили, что доступ к элементу n-мерного массива может быть осуществлен путем использования индексированной переменной с n индексами. Таким образом, индексированная переменная m[0][2][1] представляет собой значение соответствующего элемента массива m.

Что же тогда представляют собой имя массива – m или, допустим, конструкции вида m[1] и m[1][0]? Си дает на это однозначный ответ – это константы-указатели. А раз так, то теперь нам должно быть понятно следующее: то, что ранее мы назвали индексированной переменной, есть не что иное, как применение операции индексации к указателю.

Приводимое далее описание трехмерного массива с инициализацией элементов и комментарии к нему дают представление о возможностях доступа к элементам через константы-указатели:

int m[2][3][2] =    /* Имя массива m – указатель на начало массива. */
/* Индексация имени массива m дает m[0] – указатель на эти массивы ----> */   {
/* Индексация указателя m[0] дает: */
/* m[0][0] – указатель на int ---> */           { 10, 11 },
/* m[0][1] – указатель на int ---> */           { 12, 13 },
/* m[0][2] – указатель на int ---> */           { 14, 15 }
},


/* Индексация имени массива m дает m[1] – указатель на эти массивы ----> */ {
/* Индексация указателя m[1] дает: */
/* m[1][0] – указатель на int --> */            { 16, 17 },
/* m[1][1] – указатель на int --> */            { 18, 19 },
/* m[1][2] – указатель на int --> */            { 20, 21 }
};

Нам уже известно, что применение операции индексации к указателю на данное дает значение данного. Следовательно, например, m[1][0][1] – то, что мы назвали индексированной переменной, обеспечивает доступ к элементу массива и дает значение 17, так как m[1][0] является указателем на данное int. Из всего вышесказанного ясно, что доступ к элементам многомерных массивов можно обеспечить не только через индексированную переменную, но и через любую константу-указатель в массиве.

Например, если объявлен массив:

int m[2][3]={
{1,2,3},
{4,5,6}
};

то:

*(m[0]+0) – значение элемента m[0][0], равное 1 ,
*(m[0]+1) – значение элемента m[0][1], равное 2 ,
*(m[0]+2) – значение элемента m[0][2], равное 3 ,
*(m[1]+0) – значение элемента m[1][0], равное 4 ,
*(m[1]+1) – значение элемента m[1][1], равное 5 ,
*(m[1]+2) – значение элемента m[1][2], равное 6 ,

Рассмотрим пример программы, распечатывающей элементы массива различными способами:

#include <stdio.h>
main()
{ int m[3][6] = {   { 1, 2, 3, 4, 5, 6},
                    { 7, 8, 9,10,11,12},
                    {13,14,15,16,17,18}
                };
int i,j;
//Доступ к элементам через указатели m[0], m[1], m[2]
for(i = 0; i< = 2; i++)
    for(j = 0; j< = 5; j++)
                printf("%d \n",*(m[i]+j));
 
//Доступ к элементам массива через указатель m[0]
for(i = 0; i< = 17; i++) 
                printf("%d \n",*(m[0]+i));
 
//Доступ к элементам через указатель m
for(i = 0; i< = 17; i++) 
                printf("%d \n",*(*m+i));
}


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

int arr[2][3] = {1,2,3,4,5,6};
int *uk;
int i;
uk =arr;      //uk указывает на начало массива arr  
              //такое присваивание возможно только в C
              //в С++ uk надо объявлять int (*uk)[3];
              //как это сделано в примере ниже
uk++;         //теперь uk указывает на arr[0][1] 
i = *uk;      //i получило значение 2  
uk = uk + 2;  //uk указывает на arr[1][2] 
i = *uk;      //i получило значение 4  
uk = arr[0];  //uk снова указывает на начало массива  
uk = uk+1;
i = *uk;      //i приняло значение 2  
i = uk[1];    //i приняло значение 3 
i = uk[3];    //i приняло значение 5 
i = uk[0];    //i приняло значение 2


При доступе к элементам массива при помощи указателя надо учитывать , что указатель "передвигается" по данным в той последовательности, в какой они расположены в памяти. Если, увеличивая указатель, мы выйдем за пределы данных массива, то он будет указывать на неопределенные данные. Для того, чтобы обращаться к массиву при помощи указателя точно так же, как и при помощи имени массива, необходимо соответствующим образом описать указатель. Например, объявлен массив:

int arr[2][3]={1,2,3,4,5,6}; ,

который можно рассматривать как одномерный массив, элементами которого являются одномерные массивы содержащие по 3 элемента, т.е. arr[0] и arr[1] являются указателями на соответствующие трехэлементные массивы.

//Теперь, если описать указатель на трехэлементные массивы:
int (*ukt)[3]; //то, используя ukt, можно обращаться
               //к элементам массива arr по индексам:
ukt = arr;     //ukt[0] - указывает на 1 строку матрицы 
               //ukt[1] - указывает на 2 строку матрицы
i = ukt[0][1]; //i приняло значение 2, ибо arr[0][1] равносильно 
               //ukt[0][1], что равносильно  *(ukt[0]+1), 
               //которое дает значение 2
i = ukt[0][0]; //i приняло значение равное 1
ukt = ukt + 1; //для arr присваивание было бы недопустимо
               //ukt[0] сейчас указывает на 1 строку 
i = ukt[0][0]; //i приняло значение, равное 4


Таким образом, любое действие, которое достигается с помощью индексации массива, достигается и с помощью указателей на него. Тесная взаимосвязь указателей, массивов и адресной арифметики представляет собой одно из достоинств языка Си.

рассказать друзьям и получить подарок

Оставить комментарий

Ваш email не будет опубликован. Обязательные поля отмечены *

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Translate Переводчик

Подписка на новости

SmartResponder.ru
Ваш e-mail: *
Ваше имя: *

Хостинг для Wordpress сайтов