пользователей: 30398
предметов: 12406
вопросов: 234839
Конспект-online
РЕГИСТРАЦИЯ ЭКСКУРСИЯ

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

методичка белая страница 22

Массивы языков Си и Си++ обладают очень интересным свойством, отличающим их от массивов в других алгоритмических языках. При объявлении массивов, как и в других алгоритмических языках, им выделяются участки оперативной памяти. Но как только память для массива выделена,  имя массива воспринимается как константный (т. е. неизменный) указатель того типа, к которому отнесены элементы массива. Однако  для этого указателя  две операции выполняются особенным образом:

  • Операция sizeof, примененная к имени массива, определяет размер выделенного участка памяти для всего массива, а не участка, выделенного для указателя. Например, значение выражения sizeof(x) для последнего примера равно 12.
  • Операция получения адреса, примененная к имени массива, определяет адрес начального (с нулевым индексом) элемента массива.

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

 

Имя_массива == &имя_массива == имя_массива [0]

 

Если применить это правило к рассмотренному ранее массиву х, то получим:

 

x == &x == &x[0]

 

Да, но что такое  x[0]? Ведь массив х объявлен как двумерный! Дело в том, что в соответствии с синтаксисом языка Си, в языке существуют только одномерные массивы. Однако, элементами одномерного массива, в свою очередь, могут быть массивы. Поэтому двумерный массив определяется как массив массивов. Таким образом, х[0] – это нулевой элемент массива х (нулевая строка матрицы х), который в свою очередь является одномерным массивом элементов типа  int, x[1] – первый элемент (первая строка матрицы)  и т. д.  Аналогично строятся трехмерные и n – мерные массивы. Но, если x[0] – это нулевая строка матрицы х, то как обозначится ее нулевой элемент?  Очевидно, как x[0][0]. Поэтому:

 

x == &x == x[0] = &x[0] == &x[0][0].

 

Итак, запомним, что имя массива – это константный указатель на тип данных входящих в него элементов, значение которого – адрес нулевого элемента. Например, int a[10] означает, что а – константный указатель на тип int и a == &a[0], float y[5][8] – означает, что у - константный указатель на тип float и y == y[0] = &y[0][0]. Но в таком случае, что может означать выражение х + 1 и y[0] +1? Очевидно, a + 1 означает увеличение адреса на одно поле типа int (вспомним операции над указателями), т. е. на два байта, что предполагает перемещение к элементу x[1].  Выражение y[0] +1 означает увеличение адреса на одно поле типа float, т. е. на 4 байта, что предполагает перемещение к элементу y[0][1]. Таким образом,  справедливо:

a[1] == *(a + 1)

y[0][1] == *(y[0] +1) .

Или в  общем виде:

a[i] == *(a + i)

y[0][j] == *(y[0] +j) .

Следует отметить, что хотя  y == y[0], выражение *( y + j) не воспринимается транслятором как перемещение к j –ому элементу нулевой строки. Дело в том, что в этом выражении слева от знака ‘+’ должен размещаться объект, который может восприниматься как одномерный массив (y – это двумерный массив, а y[0] – одномерный массив).

А теперь вернемся к указателям и рассмотрим одновременное участие указателя и массива. Пусть имеются такие объявления:

int z[10];

int *pz;

pz = &z[0];

В таком случае:
pz + 1 -  это адрес первого элемента;
pz + 2 -  это адрес второго элемента;
pz + i -  это адрес i -го элемента;

кроме того:
pz == z

pz[i] == *(pz + i) == z[i]
&z[i] == z + i

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

 

#include<stdio.h>

#include<conio.h>

void main ()

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

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

  int *p;

  int i,j;

  clrscr();

  printf("***array z***\n");

  for (i=0;i<2;i++)

  { for (j=0;j<3;j++)

  printf("%d  ",z[i][j]);

  printf("\n");

 

  }

  printf("************\n");

  p = z[1];

  printf("z[0][1] = %d\n",z[0][1]);

  printf("*(z[0]+1) = %d\n",*(z[0]+1));

  printf("*(p + 1)= %d\n",*(p + 1));

  printf("z[1][1] = %d\n",z[1][1]);

  p=a;

   printf("***array a***\n");

  for (i=0;i<3;i++)

  printf("%d  ",*(p + i));

  getch();

}

При выполнении этой программы на экране получим:

 

 
 

"***array z***

1 2 3

4 5 6

z[0][1] = 2

*(z[0] +1) =2

*(p + 1)=5

z[1][1]= 5

***array a***

1 2 3

 

 

 

 

 

 

 

 

 

 

 

В этой программе объявляются и инициируются два массива целых чисел: двумерный массив и одномерный массив а. На экране видно, что вначале выданы элементы массива z. Затем р настраивается на первую строку массива z (оператор p=z[1];). Далее мы видим, что выражение z[0][1] эквивалентно выражению *(z[0] +1) (и то и другое “достают” из массива для выдачи элемент z[0][1], равный 2); выражение *(p + 1) эквивалентно выражению z[1][1] (и то и другое “достают” из массива для выдачи элемент z[1][1], равный 5). Далее р настраивается на массив а и значения элементов массива а выдаются в цикле по i с помощью выражения *(p + i).
Любознательный читатель спросит: “А зачем нам нужны такие хитрости? Почему нельзя всегда “честно” пользоваться обращением к элементам массивов переменными с индексами (z[i][j], a[i])?”. Ответ такой: “Да, можно поступить и так, но тогда мы не сможем использовать рассмотренные возможности для разработки эффективных программ на Си и вряд ли сможем считать себя программистами на Си ”. А теперь вернемся к вопросу, который возник при решении задачи 2 настоящего раздела. Как реализовать динамический массив, т. е. массив с заранее неизвестным числом элементов?


21.06.2017; 16:52
хиты: 105
рейтинг:0
Точные науки
информатика
для добавления комментариев необходимо авторизироваться.
  Copyright © 2013-2024. All Rights Reserved. помощь