Урок 99. Передача аргументов по адресу

   | 

   | 

 Обновлено 15 Дек 2017  | 

 3576

 ǀ   9 

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

Вот пример функции, которая принимает параметр, передаваемый по адресу:

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

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 (33 оценок, среднее: 4,91 из 5)
Загрузка...
Подписаться на обновления:

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

  1. Torgu:

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

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

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

      1. Torgu:

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

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

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

        2. Torgu:

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

  2. 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. Юрий Юрий:

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

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

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

ВОЛШЕБНАЯ ТАБЛЕТКА ПО С++