Язык Си - руководство для начинающих
Шрифт:
}
Вот результат:
указатели + 0 56014 56026
указатели + 1 56016 56030
указатели + 2 56018 56034
указатели + 3 56020 56038
Первая напечатанная строка содержит начальные адреса двух массивов, а следующая строка - результат прибавления единицы к адресу и т. д. Почему так получается?
56014 + 1 = 56016? 56026 + 1 = 56030?
Не знаете, что сказать? В нашей системе единицей адресации является байт, но тип int использует два байта, а тип float–
РИС. 12.1. Увеличение указателя массива.
Благодаря тому что компилятор языка Си умеет это делать, мы имеем следующие равенства:
dates + 2 == &dates[2] /* один и тот же адрес */
*(dates + 2) == dates[2] /* одно и то же значение */
Эти соотношения суммируют тесную связь между массивами и указателями. Они показывают, что можно использовать указатель для определения отдельного элемента массива, а также для получения его значения. По существу мы имеем два различных обозначения для одного и того же. Действительно, компилятор превращает обозначение массива в указатели, поэтому метод указателей более предпочтителен.
Между прочим, постарайтесь различать выражения *(dates + 2), и *dates + 2. Операция (*) имеет более высокий приоритет, чeм +, поэтому последнее выражение означает
(*dates) + 2:
*(dates + 2) /* значение 3-го элемента массива dates */
*dates +2 /* 2 добавляется к значению 1-го элемента массива */
Связь между массивами и указателями часто позволяет нам применять оба подхода при создании программ. Одним из примеров этого является функция с массивом в качестве аргумента.
ФУНКЦИИ, МАССИВЫ И УКАЗАТЕЛИ
Массивы можно использовать в программе двояко. Во-первых, их можно описать в теле функции. Во-вторых, они могут быть аргументами функции. Вес, что было сказано в этой главе о массивах, относится к первому их применению; теперь рассмотрим массивы в качестве аргументов.
Об этом уже говорилось в гл. 10. Сейчас, когда мы познакомились с указателями, можно заняться более глубоким изучением массивов-аргументов. Давайте проанализируем скелет программы, обращая внимание на описания:
/* массив-аргумент */
main
{
int ages[50]; /* массив из 50 элементов */
convert(ages);
...
}
convert (years);
int years [ ]; /* каков размер массива? */
{
...
}
Очевидно,
int years[ ];
создает не массив, а указатель на него. Посмотрим, почему это так.
Вот вызов нашей функции:
convert(ages);
ages– аргумент функции convert. Вы помните, что имя ages является указателем на первый элемент массива, состоящего из 50 элементов. Таким образом, оператор вызова функции передает ей указатель, т. е. адрес функции convert. Это значит, что аргумент функции является указателем, и мы можем написать функцию convert следующим образом:
convert (years);
int *years;
{
}
Действительно, операторы
int years[ ];
int *years;
синонимы. Оба они объявляют переменную years указателем массива целых чисел. Однако главное их отличие состоит в том, что первый из них напоминает нам, что указатель years ссылается на массив.
Как теперь связать его с массивом ages? Вспомним, что при использовании указателя в качестве аргумента, функция взаимодействует с соответствующей переменной в вызывающей программе, т. е. операторы, использующие указатель years в функции соnvert, фактически работают с массивом ages, находящимся в теле функции main.
Посмотрим, как работает этот механизм. Во-первых, вызов функции инициализирует указатель years, ссылаясь на ages[0]. Теперь предположим, что где-то внутри функции convert есть выражение years[3]. Как вы видели в предыдущем разделе, оно аналогично *(years + 3). Однако если years указывает на ages[0], то years+3 ссылается на ages[3]. Это приводит к тому, что *(years+3) означает ages[3]. Если внимательно проследить данную цепочку, то мы увидим, что years[3] аналогично *(years + 3), которое в свою очередь совпадает с ages[3]. Что и требовалось доказать, т. е. операции над указателем years приводят к тем же результатам, что и операции над массивом ages.
Короче говоря, когда имя массива применяется в качестве аргумента, функции передается указатель. Затем функция использует этот указатель для выполнения изменений в исходном массиве, принадлежащем программе, вызвавшей функцию. Рассмотрим пример.
ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ ПРИ РАБОТЕ С МАССИВАМИ
Попробуем написать функцию, использующую массивы, а затем перепишем ее, применяя указатели.
Рассмотрим простую функцию, которая находит (или пытается найти) среднее значение массива целых чисел. На входе функции мы имеем имя массива и количество элементов. На выходе получаем среднее значение, которое передастся при помощи оператора return. Оператор вызова функции может выглядеть следующим образом: