Урок №82. Указатели и массивы

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 97516

 ǀ   12 

В языке C++ указатели и массивы тесно связаны между собой.

Сходства между указателями и массивами

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

Для нас это массив из 4 целых чисел, но для компилятора array является переменной типа int[4]. Мы знаем что array[0] = 5, array[1] = 8, array[2] = 6 и array[3] = 4. Но какое значение имеет сам array?

Переменная array содержит адрес первого элемента массива, как если бы это был указатель! Например:

Результат на моем компьютере:

The array has address: 004BF968
Element 0 has address: 004BF968

Обратите внимание, адрес, хранящийся в переменной array, является адресом первого элемента массива.

Распространенная ошибка думать, что переменная array и указатель на array являются одним и тем же объектом. Это не так. Хотя оба указывают на первый элемент массива, информация о типе данных у них разная. В вышеприведенном примере типом переменной array является int[4], тогда как типом указателя на массив является int *.

Путаница вызвана тем, что во многих случаях, при вычислении, фиксированный массив распадается (неявно преобразовывается) в указатель на первый элемент массива. Доступ к элементам по-прежнему осуществляется через указатель, но информация, полученная из типа массива (например, его размер), не может быть доступна из типа указателя.

Однако и это не является столь весомым аргументом, чтобы рассматривать фиксированные массивы и указатели как разные значения. Например, мы можем разыменовать массив, чтобы получить значение первого элемента:

Обратите внимание, мы не разыменовываем фактический массив. Массив (типа int[4]) неявно конвертируется в указатель (типа int *), и мы разыменовываем указатель, который указывает на значение первого элемента массива.

Также мы можем создать указатель и присвоить ему array:

Это работает из-за того, что переменная array распадается в указатель типа int *, а тип нашего указателя такой же (т.е. int *).

Различия между указателями и массивами


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

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

16
4

Фиксированный массив знает свою длину, а указатель на массив — нет.

Второе различие возникает при использовании оператора адреса &. Используя адрес указателя, мы получаем адрес памяти переменной указателя. Используя адрес массива, возвращается указатель на целый массив. Этот указатель также указывает на первый элемент массива, но информация о типе отличается. Вряд ли вам когда-нибудь понадобится это использовать.

Передача массивов в функции

На уроке №75 мы говорили, что, из-за того, что копирование больших массивов при передаче в функцию является очень затратной операцией, C++ не копирует массив. При передаче массива в качестве аргумента в функцию, массив распадается в указатель на массив и этот указатель передается в функцию:

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

32
4

Обратите внимание, результат будет таким же, даже если параметром будет фиксированный массив:

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

32
4

В примере, приведенном выше, C++ неявно конвертирует параметр из синтаксиса массива ([]) в синтаксис указателя (*). Это означает, что следующие два объявления функции идентичны:

Некоторые программисты предпочитают использовать синтаксис [], так как он позволяет понять, что функция ожидает массив, а не указатель на значение. Однако, в большинстве случаев, поскольку указатель не знает, насколько велик массив, вам придется передавать размер массива в качестве отдельного параметра (строки являются исключением, так как они нуль-терминированные).

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

Совет: Используйте синтаксис указателя (*) вместо синтаксиса массива ([]) при передаче массивов в качестве параметров в функции.

Передача по адресу


Тот факт, что массивы распадаются на указатели при передаче в функции, объясняет основную причину, по которой изменение массива в функции приведет к изменению фактического массива. Рассмотрим следующий пример:

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

Element 0 has value: 1
Element 0 has value: 5

При вызове функции changeArray(), массив распадается на указатель, а значение этого указателя (адрес памяти первого элемента массива) копируется в параметр ptr функции changeArray(). Хотя значение ptr в функции является копией адреса массива, ptr всё равно указывает на фактический массив (а не на копию!). Следовательно, при разыменовании ptr, разыменовывается и фактический массив!

Этот феномен работает так же и с указателями на значения не из массива. Более детально эту тему мы рассмотрим на соответствующем уроке.

Массивы в структурах и классах

Стоит упомянуть, что массивы, которые являются частью структур или классов, не распадаются, когда вся структура или класс передается в функцию.

На следующем уроке мы рассмотрим адресную арифметику и поговорим о том, как на самом деле работает индексация массива.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (433 оценок, среднее: 4,91 из 5)
Загрузка...

Комментариев: 12

  1. Дмитрий:

    Спасибо за такие качественные уроки. Я новичок в программировании, но тем не менее, благодаря вашим урокам, мне становится всё понятно!

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  2. Ваня:

    Спасибо за такое разжёвывание. Очень нравится, как преподносите материал.

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  3. AntoH:

    Я давно изучал C++ по другим урокам,но там была только общая информация, и я думал что массивы работают как обычные переменные. Когда пытался написать функцию которая бы принимала и возвращала массив, не мог понять почему не компилится. Думал перенести массив в глобальную область или использовать указатели, а теперь понял что это и не нужно. Так-же были и другие непонятки смассивами, например: почему нельзя объявить массив через переменную которая инициализируется оператором, хотя на другом компиляторе это работало, хоть и со своими проблемами, но теперь всё встало на свои места. В общем, ОГРОМНОЕ ВАМ СПАСИБО за эти уроки! И почему я не наткнулся на них раньше, возможно уже сейчас писал бы игру своей мечты!

    1. Фото аватара Юрий:

      Пожалуйста 🙂 Спасибо за отзыв!

  4. Денис:

    Добрый день! Раз 10 перечитывал статью ,но так и не могу понять, как в функции с переданным в нее массивом через указатель работать с разными индексами ?
    Например:
    Мы передали в функцию массив int array[]={1, 2, 3} использовав в качестве аргумента например — (int *ptr).
    Выводим значение первого элемента прямо из функции std::cout << *ptr; — понятно , что выведется элемент с индексом [0] , т.к. мы разименовали указатель , который указывает на массив , а указатель на массив указывает на его первый элемент — это ок.
    !Но как мне например из функции работать с элементом [1], [2] ?
    P.S. Извините если вопрос глупый. Всем спасибо за помощ !!!)))

    1. Фото аватара Дмитрий Бушуев:

      Арифметика указателей же 🙂
      Если *ptr=ptr[0], то
      *(ptr+1)=ptr[1]
      *(ptr+2)=ptr[2]

      Почитать об этом:
      https://ravesli.com/urok-83-adresnaya-arifmetika-i-indeksatsiya-massiva/

      1. Денис:

        Спасибо !!)

      2. Анатолий:

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

        Не получится ли так что на адрес &ptr[5] например, не укажет другой указатель и не испортит мне массив? Как защищена память массива по указателю, если мы не знаем его размер?
        Извиняюсь, может что то не понимаю…

  5. Тарас:

    Извините, но мне кажется , что писать "массив распадается на указатель" это может сбить с толку. Не лучше ли сказать что в функцию просто передается только указатель на массив.

  6. Spardoks:

    Возможно, интересно будет, если упомянуть, что хоть передача массивов в функцию имеет такую специфику, тем не менее, массивы фиксированной длины можно передавать в функции, например, так:

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *