Урок №99. Передача по адресу

  Юрий  | 

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

 15298

 ǀ   14 

Есть ещё один способ передачи переменных в функцию — по адресу.

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

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

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

value = 4
value = 7

Как вы можете видеть, функция boo() изменила значение аргумента (переменную value) через параметр-указатель ptr. Передачу по адресу обычно используют с указателями на обычные массивы. Например, следующая функция выведет все значения массива:

Вот пример программы, которая вызывает эту функцию:

Результат:

9 8 6 4 3 2 1

Помните, что фиксированные массивы распадаются в указатели при передаче в функцию, поэтому их длину нужно передавать в виде отдельного параметра. Хорошей практикой является проверка параметров, передаваемых по адресу, перед разыменованием на то, не являются ли они нулевыми указателями. Разыменование нулевого указателя приведёт к сбою в программе. Вот функция printArray() с проверкой (обнаружением) нулевых указателей:

Передача по константному адресу


Поскольку printArray() всё равно не изменяет значения получаемых аргументов, то хорошей идеей будет сделать параметр array константным:

Так мы видим сразу, что printArray() не изменит переданный аргумент array. Когда вы передаёте указатель в функцию по адресу, то значение этого указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Другими словами, он передаётся по значению! Если изменить значение параметра функции, то изменится только копия, исходный указатель-аргумент не будет изменён. Например:

В tempPtr копируется адрес указателя ptr. Несмотря на то, что мы изменили tempPtr на нулевой указатель (присвоили ему nullptr), это никак не повлияло на значение, на которое указывает ptr. Следовательно, результат выполнения программы выше:

6
6

Обратите внимание, хотя сам адрес передаётся по значению, вы всё равно можете разыменовать его для изменения значения исходного аргумента. Запутано? Давайте проясним:

   При передаче аргумента по адресу в переменную-параметр функции копируется адрес из аргумента. В этот момент параметр функции и аргумент указывают на одно и то же значение.

   Если параметр функции затем разыменовать для изменения исходного значения, то это приведёт к изменению значения, на которое указывает аргумент, поскольку параметр функции и аргумент указывают на одно и то же значение!

   Если параметру функции присвоить другой адрес, то это никак не повлияет на аргумент, поскольку параметр функции является копией, а изменение копии не приводит к изменению оригинала. После изменения адреса параметра функции, параметр функции и аргумент будут указывать на разные значения, поэтому разыменование параметра и дальнейшее его изменение никак не повлияет на значение, на которое указывает аргумент.

В следующей программе это всё хорошо проиллюстрировано:

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

6
7

Передача адресов по ссылке

Следует вопрос: «А что, если мы хотим изменить адрес, на который указывает аргумент, внутри функции?». Оказывается, это можно сделать очень легко. Вы можете просто передать адрес по ссылке. Синтаксис ссылки на указатель может показаться немного странным, но всё же:

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

6 ptr is null

Наконец, наша функция setToNull() действительно изменила значение ptr с &six на nullptr!

Существует только передача по значению


Теперь, когда вы понимаете основные различия между передачей по ссылке, по адресу и по значению, давайте немного поговорим о том, что находится под капотом.

В уроке о ссылках мы упоминали, что ссылки на самом деле реализуются с помощью указателей. Это означает, что передача по ссылке является просто передачей по адресу. И чуть выше мы говорили, что передача по адресу на самом деле является передачей адресу по значению! Из этого следует, что C++ действительно передает всё по значению!

Плюсы и минусы передачи по адресу

Плюсы передачи по адресу:

   Передача по адресу позволяет функции изменить значение аргумента, что иногда полезно. В противном случае, используем const для гарантии того, что функция не изменит аргумент.

   Поскольку копирования аргументов не происходит, то скорость передачи по адресу достаточно высокая, даже если передавать большие структуры или классы.

   Мы можем возвратить сразу несколько значений из функции используя параметры вывода.

Минусы передачи по адресу:

   Все указатели нужно проверять, не являются ли они нулевыми. Попытка разыменовать нулевой указатель приведёт к сбою в программе.

   Поскольку разыменование указателя выполняется медленнее, чем доступ к значению напрямую, то доступ к аргументам, переданным по адресу, выполняется также медленнее, чем доступ к аргументам, переданным по значению.

Когда использовать передачу по адресу:

   при передаче обычных массивов (если нет никаких проблем с тем, что массивы распадаются в указатели при передаче).

Когда не использовать передачу по адресу:

   при передаче структур или классов (используйте передачу по ссылке);

   при передаче фундаментальных типов данных (используйте передачу по значению).

Как вы можете видеть сами, передача по адресу и по ссылке имеют почти одинаковые преимущества и недостатки. Поскольку передача по ссылке обычно безопаснее, чем передача по адресу, то в большинстве случаев предпочтительнее использовать передачу по ссылке.

Правило: Используйте передачу по ссылке, вместо передачи по адресу, когда это возможно.


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

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

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

  1. Аватар Алексей:

    Немного запутали, но на практике вопросы уйдут.

  2. Аватар Дмитрий:

    Я вот честно говоря тоже запутался как нужно передавать массив. В главе Урок №75 написано, что массив и без амперсанта и без указателя и так передается в функцию без копирования. Здесь написано, что нужно через адрес. При этом структуры и классы лучше через ссылку. Чем массив принципиально отличается от структуры? Не понятно, как правильно

    1. Аватар Егор:

      Насколько я понял, фиксированный массив при передаче в функцию без ничего, приходит в неё в виде указателя на первый элемент, (ничего не копируется). Если же ты передаешь std::array или std::vector, то без ничего они полностью скопируются в аргумент функции (что затратно)
      Поэтому лучше использовать адреса. А еще лучше ссылки.

  3. Аватар Torgu:

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

    1. Аватар Анастасия:

      Потому что массив при передаче в функцию распадается в указатель.

      1. Аватар Torgu:

        так в чем собственно преимущество?

        1. Аватар Анастасия:

          Даже если передавать по ссылке, все равно массив распадется в указатель. Так в чем преимущество передачи массива по ссылке?

        2. Аватар Torgu:

          Здесь ограничение по глубине комментариев, поэтому могу ответить только себе. Итак, вы отвечаете вопросом на вопрос? То есть у указателей все-таки нет преимуществ перед ссылками?

    2. Аватар Александр:

      по ссылке на что Вы собираетесь передавать массив?

      Я так понимаю, что немного дальше автор нас будет знакомить с итераторами. Так вот, ссылка для массива — это простой итератор. И передача массива по ссылке унифицирует интерфейсы работы функций с различными контейнерами. Если Вы умеете обрабатывать массив, жонглируя исключительно ссылками (при обращении к элементам пользуетесь разыменованием, а не оператором [] ), то в большинстве случаев можете тот же код применить и к другим контейнерам.

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

  4. Аватар Shom:

    Здравствуйте! Извините за тупость, но вот тут:

    мне не совсем понятно array[index].
    Ведь array — это указатель на первый элемент массива, то-есть, фактически, это адрес первого элемента и как к этому адресу пристыкуется [index]?
    При следующих итерациях цикла, по моей логике, должен вновь получится адрес первого элемента, но уже со следующим индексом в квадратных скобках.

    1. Юрий Юрий:

      Привет.

      array — это тип указателя на массив (int *), а array[index] — это уже набор значений определенного типа, например, int-ов. Определив array[4] и используя возле array квадратные скобки со значением внутри вы автоматически обращаетесь к array, как к набору int-ов, а не как к указателю на первый элемент. Не указывая квадратных скобок и значения в них вы можете относиться к array как к указателю на массив (на первый элемент).

      Т.е. если вы добавляете [index] к array, то array используется в этом случае как тип данных (int), а не как указатель (int *). И при последующих итерациях цикла у вас получается не адрес первого элемента + следующий индекс в квадратных скобках, а адрес 2-ого значения из набора int-ов (т.е. из массива).

      1. Аватар Shom:

        Всё понял, спасибо!
        Но возник ещё один вопрос: память для массива всегда одним целым куском выделяется? Не может быть так, к примеру, что сначала идут первые сто адресов для массива, потом адрес памяти выделен под переменную (массив инициализировали позже переменной), а потом следующие адреса продолжают массив и при инструкции в цикле

        эта переменная попадёт в вывод?

        1. Юрий Юрий:

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

        2. Аватар Александр:

          при выделении памяти под массив (в том числе и под динамический) адреса элементов ГАРАНТИРОВАНО идут по порядку

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

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