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

Автор работы: Пользователь скрыл имя, 20 Марта 2011 в 00:49, лекция

Описание работы

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

Содержание работы

Массивы
Инициализация массивов и классы памяти
Указатели массивов
Функции, массивы и указатели
Использование указателей при работе с массивами
Операции с указателями
Многомерные массивы
Инициализация двумерного массива
Указатели и многомерные массивы
Функции и многомерные массивы

Файлы: 1 файл

13_Массивы и указатели.doc

— 183.50 Кб (Скачать файл)

/* прибавление к указателю */

main ()

{

int dates[4], *pti, index;

float bills [4], *ptf;

pti = dates; /* присваивает адрес указателю массива */

ptf = bills;

for (index = 0; index < 4; index++ )

      printf(“  указатели + %d: %10 u %10u\n", index, pti + index, ptf + index);

} 

    Вот результат 

    указатели + 0: 56014 56026

    указатели + 1: 56016 56030

    указатели + 2: 56018 56034

    указатели + 3: 56020 56038 

    Первая  напечатанная строка содержит начальные  адреса двух массивов, а следующая  строка — результат прибавления  единицы к адресу и т. д. Почему так получается? 

    56014 + 1 = 56016?

    56026 + 1 = 56030? 

    Не  знаете, что сказать? В нашей системе  единицей адресации является байт, но тип int использует два байта, а  тип float — четыре. Что произойдет, если вы скажете: «прибавить единицу  к указателю?» Компилятор языка Си добавит единицу памяти. Для массивов это означает, что мы перейдем к адресу следующего элемента, а не следующего байта. Вот почему мы должны специально оговаривать тип объекта, на который ссылается указатель; одного адреса здесь недостаточно, так как машина должна знать, сколько байтов потребуется для запоминания объекта. (Это справедливо также для указателей на скалярные переменные; иными словами, при помощи операции *pt нельзя получить значение.) 

 

    Благодаря тому, что компилятор языка Си умеет это делать, мы имеем следующие равенства: 

    dates + 2 == &dates[2] /* один и тот же адрес */

    *(dates + 2) == dates[2] /* одно и то же значение */ 

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

    Между прочим, постарайтесь различать выражения * (dates + 2), и *dates + 2. Операция (*) имеет более  высокий приоритет, чем +, поэтому  последнее выражение означает 

    (* dates) + 2:

    *(dates + 2) /* значение 3-го элемента массива dates */

    *dates + 2 /* 2 добавляется к значению 1-го элемента массива */ 

    Связь между массивами и указателями  часто позволяет нам применять  оба подхода при создании программ. Одним из примеров этого является функция с массивом в качестве аргумента. 

    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 (уears)

int *years;

{

} 

    Действительно, операторы

    int years [];

    int *years; 

    синонимы. Оба они объявляют переменную years указателем массива целых чисел. Однако главное их отличие состоит  в том, что первый из них напоминает нам, что указатель years ссылается  на массив.

    Как теперь связать его с массивом ages? Вспомним, что при использовании  указателя в качестве аргумента, функция взаимодействует с соответствующей  переменной в вызывающей программе, т.е. операторы, использующие указатель years в функции convert (), фактически работают с массивом 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. Короче говоря, когда имя массива применяется в качестве аргумента, функции передается указатель. Затем функция использует этот указатель для выполнения изменений в исходном массиве, принадлежащем программе, вызвавшей функцию. Рассмотрим пример. 

    1. Использование указателей при работе с массивами

    Попробуем написать функцию, использующую массивы, а затем перепишем ее, применяя указатели.

    Рассмотрим  простую функцию, которая находит (или пытается найти) среднее значение массива целых чисел. На входе  функции мы имеем имя массива и количество элементов. На выходе получаем среднее значение, которое передается при помощи оператора return. Оператор вызова функции может выглядеть следующим образом: 

    /* X54.C */

    #include <stdio.h>

    void main(void)

    {

          int n = 9,i;

          int array[] = {1,3,5,9,0,-9,-5,-3,-1};

          int mean(int array[],int n);

          i = mean(array,n);

          printf("Среднее из заданных  значений %d\n",i);

    }

          int mean(int array[],int n)

          {

          int index;

          long sum;

          if(n > 0)

                {

                for(index = 0, sum = 0; index < n; index++)

                      sum += array[index];

                return((int) (sum/n));

                }

          else

                {

                printf("Нет массива\n");

                return(0);

                }

          } 

    Эту программу легко переделать, применяя указатели. Объявим ра указателем на тип int. Затем заменим элемент  массива array[index] на соответствующее  значение: *(ра + index). 

/* Использование  указателей для нахождения среднего  значения

массива n целых  чисел */

int mean(pa, n)

int *pa, 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 pa[]; 

    и 

    int *pa; 

    идентичны по действию: оба объявляют ра указателем. В программе можно применять любой из них, хотя до сих пор мы использовали второй в виде *(ра + index).

    Понятно ли вам, как работать с указателями? Указатель устанавливается на первый элемент массива, и значение, находящееся  там, добавляется в sum. Затем указатель передвигается на следующий элемент (к указателю прибавляется единица), и значение, находящееся в нем, также прибавляется к sum и т. д. Это похоже на механизм работы с массивом, где индекс действует как стрелка часов, показывающая по очереди на каждый элемент массива.

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

    1. Операции  с указателями

    Что же мы теперь умеем делать с указателями? Язык Си предлагает пять основных операций, которые можно применять к  указателям, а нижеследующая программа  демонстрирует эти возможности. Чтобы показать результаты каждой операции, мы будем печатать значение указателя (являющегося адресом, на который ссылается указатель), значение, находящееся по этому адресу, и адрес самого указателя. 

/* операции  с указателями */

#define PR(X) printf(" X = %u, *X = %d, &X = %u\n", X, *X, &X)

/* печатает значение указателя (адрес), значение, находящееся по */

/* этому адресу, и адрес самого указателя */

main()

static int urn[] = {100, 200, 300};

int *ptr1, *ptr2;

{

ptr1 = urn; /* присваивает адрес указателю */

ptr2 = &urn[2]; /* то же самое */

PR(ptr1); /* см. макроопределение, указанное выше */

ptr1++; /* увеличение  указателя */

PR(ptr1);

PR(ptr2);

++ptr2; /* выходит за конец массива */

Информация о работе Массивы и указатели