Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Подбельский Фомин_Программирование на языке СИ_...doc
Скачиваний:
259
Добавлен:
10.08.2019
Размер:
53.81 Mб
Скачать

Арифметические операции и указатели.

Арифметические операции и указатели. Унарные адресные операции '&' и '*' имеют более высокий приоритет, чем арифметические операции. Рассмотрим следующий пример, иллюстрирующий это правило:

*/

При использовании адресной операции '*' в арифметических выражениях следует остерегаться случайного сочетания знаков операций деления '/' и разыменования '*', так как комбинацию '/*' компилятор воспринимает как начало комментария. Например, выражение

следует заменить таким:

Унарные операции '*' и '++' или '--' имеют одинаковый приоритет и при размещении рядом выполняются справа налево.

Добавление целочисленного значения n к указателю, адресующему некоторый элемент массива, приводит к тому, что указатель получает значение адреса того элемента, который отстоит от текущего на n позиций (элементов). Если длина элемента массива равна d байтов, то численное значение указателя изменяется на (d*n). Рассмотрим следующий фрагмент программы, иллюстрирующий перечисленные правила;

Указатели и отношения.

Указатели и отношения. К указателям применяются операции сравнения '>', '>=', '!=', '==', '<=', '<'. Таким образом, указатели можно использовать в отношениях. Но сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL, обозначающей значение условного нулевого адреса.

Приведем пример, в котором используются операции над указателями и выводятся (печатаются) получаемые значения Обратите внимание, что для вывода значений указателей (адресов) в форматной строке функции printf( ) используется спецификация преобразования .

При печати значений разностей указателей и адресов в функции printf( ) использована спецификация преобразования %d -вывод знакового десятичного целого.

Возможный результат выполнения программы (конкретные значения адресов могут быть другими):

На рис. 4.4 приводится схема размещения в памяти массива float x[5] и указателей до начала выполнения цикла изменения указателей.

Рис. 4.4. Схема размещения в памяти массива и указателей

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.