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

  Юрий  | 

    | 

  Обновл. 25 мая 2019  | 

 13109

В 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 (94 оценок, среднее: 4,89 из 5)
Загрузка...

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

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