logo
Подбельский Фомин_Программирование на языке СИ_

4.2. Указатели и массивы Указатели и доступ к элементам массивов.

Указатели и доступ к элементам массивов. По определению, указатель - это либо объект со значением "адрес объекта" или "адрес функции", либо выражение, позволяющее получить

адрес объекта или функции. Рассмотрим фрагмент:

Здесь р - указатель-объект, а &х, &у - указатели-выражения, т.е. адреса-константы. Мы уже знаем, что р - переменная того же типа, что и значения &х, &у. Различие между адресом (т.е. указателем-выражением) и указателем-объектом заключается в возможности изменять значения указателей-объектов. Именно поэтому указатели-выражения называют указателями-константами или адресами, а для указателя объекта используют название указатель-переменная или просто указатель.

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

Рассмотрим задачу "инвертирования" массива символов и различные способы ее решения с применением указателей (заметим, что задача может быть легко решена и без указателей - с использованием индексации). Предположим, что длина массива типа char равна 80.

Первое решение задачи инвертирования массива:

В заголовке цикла указателю d присваивается адрес первого (с нулевым индексом) элемента массива z. Здесь можно было бы применить и другую операцию, а именно: d=&z[0]. Указатель h получает значение адреса последнего элемента массива z. Далее работа с указателями в заголовке ведется, как с обычными целочисленными переменными. Цикл выполняется до тех пор, пока d<h. После каждой итерации значение d увеличивается, значение h уменьшается на 1. При первой итерации в теле цикла выполняется обмен значений z[0] и z[79], так как d - адрес z[0], h - адрес z[79]. При второй итерации значением d является адрес z[1], для h - адрес z[78] и т.д.

Второе решение задачи инвертирования массива:

Приращение указателя d и уменьшение указателя h перенесены в тело цикла. Напоминаем, что в выражениях *d++ и *h-- операции увеличения и уменьшения на 1 имеют тот же приоритет, что и унарная адресная операция '*'. Поэтому изменяются на 1 не значения элементов массива, на которые указывают d и h, а сами указатели. Последовательность действий такая: по значению указателя d (или h) обеспечивается доступ к элементу массива; в этот элемент заносится значение из правой части оператора присваивания; затем увеличивается (уменьшается) на 1 значение указателя d (или h).

Третье решение задачи инвертирования массива (используется цикл с предусловием):

Четвертое решение задачи инвертирования массива (имитация индексированных переменных указателями со смещениями):

Последний пример демонстрирует возможность использования вместо индексированного элемента z[i] выражения *(z+i). В языке Си, как мы упоминали, имя массива без индексов есть адрес его первого элемента (с нулевым значением индекса). Прибавив к имени массива целую величину, получаем адрес соответствующего элемента, таким образом, &z[i] и z+i - это две формы определения адреса одного и того же элемента массива, отстоящего на i позиций от его начала.

Итак, в соответствии с синтаксисом языка операция индексирования Е1[Е2] определена таким образом, что она эквивалентна *(Е1+Е2), где Е1 - имя массива, Е2 - целое. Для многомерного массива правила остаются теми же. Таким образом, E[n][m][k] эквивалентно *(E[n][m]+k) и, далее

*(*(*(E+n)+m)+k).

Отметим, что имя массива не является переменной типа указатель, а есть константа - адрес начала массива. Таким образом, к имени массива не применимы операции '++' (увеличения), '--' (уменьшения), имени массива нельзя присвоить значение, т.е. имя массива не может использоваться в левой части оператора присваивания.

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

В следующей программе продемонстрируем еще раз особенности изменения указателей при переходе от элемента к элементу в массивах с элементами разных типов:

Результат выполнения программы:

Как подтверждает рассмотренный пример, изменение указателя на 1 приводит к разным результатам в зависимости от типа объекта, с которым связан указатель. Значение указателя изменяется на длину участка памяти, выделенного для элемента, связанного с указателем. Символы занимают в памяти по одному байту, поэтому значение указателя uz изменяется на 1 при переходе к соседнему элементу символьного массива. Для целочисленного массива переход к соседнему элементу изменяет указатель urn на 2. Для массива вещественных элементов с плавающей точкой переход к соседнему элементу изменяет указатель uа на 4.