Шрифт:
Интервал:
Закладка:
РИС. 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 [ ]; /* каков размер массива? */
{
...
}
Очевидно, что массив ages состоит из 50 элементов. А что можно сказать о массиве 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. Оператор вызова функции может выглядеть следующим образом:
printf("Среднее из заданных значений %d.n", mean(numbs,size));
/* находит среднее значение массива из n целых чисел */
int mean(array, n);
int array[ ], n;
{
int index;
long sum; /* Если целых слишком много, их можно
суммировать в формате long int */
if(n > 0)
{
for(index = 0, sum = 0; index < n; index++)
sum + = array[index];
return((int)(sum/n)); /* возвращает int * / }
else {
printf("Нет массива. n");
return(0); }
}
Эту программу легко переделать, применяя указатели. Объявим ра указателем на тип int. Затем заменим элемент массива array[index] на соответствующее значение: *(ра + index).
/* Использование указателей для нахождения
среднего значения массива n целых чисел */
int mean(pa, n) int oра, n;
{
int index;
long sum; /*Если целых слишком много,
их можно суммировать в формате long int */
if(n > 0)
{
for(index=0, sum=0; index < n; index++)
sum + = *(pa + index);
return((int)(sum/n)); /* Возвращает целое */ }
else {
printf("Нет массива.n");
return(0); }
}
Это оказалось несложным, но возникает вопрос: должны ли мы изменить при этом вызов функции, в частности numbs, который был именем массива в операторе mean(numbs, size)? Ничего не нужно менять, поскольку имя массива является указателем. Как мы уже говорили в предыдущем разделе, операторы описания:
int ра[ ];
и
int *ра;
идентичны по действию: оба объявляют ра указателем. В программе можно применять любой из них, хотя до сих пор мы использовали второй в виде *(ра + index).
Понятно ли вам, как работать с указателями? Указатель устанавливается на первый элемент массива, и значение, находящееся там, добавляется в sum. Затем указатель передвигается на следующий элемент (к указателю прибавляется единица), и значение, находящееся в нем, также прибавляется к sum и т. д. Это похоже на механизм работы с массивом, где индекс действует как стрелка часов, показывающая по очереди на каждый элемент массива.
Теперь у нас есть два подхода; какой же из них выбрать? Во-первых, хотя массивы и указатели тесно связаны, у них есть отличия. Указатели являются более общим и широко применяемым средством, однако многие пользователи (по крайней мере начинающие) считают, что массивы более привычны к понятны. Во-вторых, при использовании указателей у нас нет простого эквивалента для задания размера массива. Самую типичную ситуацию, в которой можно применять указатель, мы уже показали: это функция, работающая с массивом, который находится где-то в другой части программы. Мы предлагаем использовав любой из подходов по вашему желанию. Однако несомненное преимущество использования указателей в приведенном выше примере должно научить вас легко применять их, когда в этом возникает необходимость.
ОПЕРАЦИИ С УКАЗАТЕЛЯМИ
Что же мы теперь умеем делать с указателями? Язык Си предлагает пять основных операций, которые можно применять к указателям, а нижеследующая программа демонстрирует эти возможности. Чтобы показать результаты каждой операции, мы будем печатать значение указателя (являющегося адресом, на который ссылается указатель), значение, находящееся по этому адресу, и адрес самого указателя.
- C++ - Страустрап Бьярн - Программирование
- Искусство программирования на языке сценариев командной оболочки - Мендель Купер - Программирование
- Delphi. Учимся на примерах - Сергей Парижский - Программирование