Как передать адрес переменной в функцию c
Существует два способа передачи параметров в метод в языке C#: по значению и по ссылке .
Передача параметров по значению
Наиболее простой способ передачи параметров представляет передача по значению, по сути это обычный способ передачи параметров:
При передаче аргументов параметрам по значению параметр метода получает не саму переменную, а ее копию и далее работает с этой копией независимо от самой переменной.
Так, выше при вызове метод Increment получает копию переменной number и увеличивает значение этой копии. Поэтому в самом методе Increment мы видим, что значение параметра n увеличилось на 1, но после выполнения метода переменная number имеет прежнее значение — 5. То есть изменяется копия, а сама переменная не изменяется.
Передача параметров по ссылке и модификатор ref
При передаче параметров по ссылке перед параметрами используется модификатор ref :
При передаче значений параметрам по ссылке метод получает адрес переменной в памяти. И, таким образом, если в методе изменяется значение параметра, передаваемого по ссылке, то также изменяется и значение переменной, которая передается на его место..
Так, в метод Increment передается ссылка на саму переменную number в памяти. И если значение параметра n в Increment изменяется, то это приводит и к изменению переменной number, так как и параметр n и переменная number указывают на один и тот же адрес в памяти.
Обратите внимание, что модификатор ref указывается как перед параметром при объявлении метода, так и при вызове метода перед аргументом, который передается параметру.
Выходные параметры. Модификатор out
Выше мы использовали входные параметры. Но параметры могут быть также выходными. Чтобы сделать параметр выходным, перед ним ставится модификатор out :
Здесь результат возвращается не через оператор return , а через выходной параметр result. Использование в программе:
Причем, как и в случае с ref ключевое слово out используется как при определении метода, так и при его вызове.
Также обратите внимание, что методы, использующие такие параметры, обязательно должны присваивать им определенное значение. То есть следующий код будет недопустим, так как в нем для out-параметра не указано никакого значения:
Прелесть использования подобных параметров состоит в том, что по сути мы можем вернуть из метода не одно значение, а несколько. Например:
Здесь у нас есть метод GetRectangleData, который получает ширину и высоту прямоугольника (параметры width и height). А два выходных параметра мы используем для подсчета площади и периметра прямоугольника.
При этом можно определять переменные, которые передаются out -параметрам в непосредственно при вызове метода. То есть мы можем сократить предыдущий пример следующим образом:
При этом, если нам неизвестен тип значений, которые будут присвоены параметрам, то мы можем для их определения использовать оператор var :
Входные параметры. Модификатор in
Кроме выходных параметров с модификатором out метод может использовать входные параметры с модификатором in . Модификатор in указывает, что данный параметр будет передаваться в метод по ссылке, однако внутри метода его значение параметра нельзя будет изменить. Например, возьмем следующий метод:
В данном случае через входные параметры width и height в метод передаются значения, но в самом методе мы не можем изменить значения этих параметров, так как они определены с модификатором in .
Передача по ссылке в некоторых случаях может увеличить произодительность, а использование оператора in гарантирует, что значения переменных, которые передаются параметрам, нельзя будет изменить в этом методе.
11.4 – Передача аргументов по адресу
Есть еще один способ передать переменные функциям – по адресу. Передача аргумента по адресу включает в себя передачу не самой переменной аргумента, а ее адреса. Поскольку аргумент является адресом, параметр функции должен быть указателем. Затем, чтобы получить доступ или изменить значение, на которое указывает указатель, функция может разыменовать его.
Вот пример функции, которая принимает параметр, переданный по адресу:
Как видите, функция foo() изменила значение аргумента (значение переменной) через параметр-указатель ptr .
Передача по адресу обычно используется с указателями, которые чаще всего используются для указания на встроенные массивы. Например, следующая функция распечатает все значения в массиве:
Вот пример программы, которая вызывает эту функцию:
Эта программа печатает следующее:
Помните, что фиксированные массивы при передаче в функцию распадаются на указатели, поэтому мы должны передавать длину как отдельный параметр.
Перед разыменованием всегда рекомендуется убедиться, что параметры, передаваемые по адресу, не являются нулевыми указателями. Разыменование нулевого указателя обычно приводит к сбою программы. Вот наша функция printArray() с проверкой нулевого указателя:
Передача по адресу на константное значение
Поскольку printArray() не изменяет ни один из своих аргументов, лучше сделать параметр array константным:
Это позволяет нам с первого взгляда сказать, что printArray() не изменяет переданный аргумент array , и гарантирует, что мы не сделаем этого случайно.
Адреса фактически передаются по значению
Когда вы передаете указатель на функцию, значение указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Другими словами, он передается по значению! Если вы измените значение параметра функции, вы измените только копию. Следовательно, исходный аргумент-указатель не будет изменен.
Вот пример программы, которая это иллюстрирует.
tempPtr получает копию адреса, который удерживает ptr . Несмотря на то, что мы изменяем tempPtr , чтобы он указывал на что-то еще ( nullptr ), это не меняет значение, на которое указывает ptr . Следовательно, эта программа печатает:
Обратите внимание, что даже если сам адрес передается по значению, вы всё равно можете разыменовать этот адрес, чтобы изменить значение аргумента. Это обычная путаница, поэтому давайте проясним:
- При передаче аргумента по адресу переменная параметра функции получает копию адреса из аргумента. На этом этапе параметр функции и аргумент указывают на одно и то же значение.
- Если затем разыменовать параметр функции, чтобы изменить значение, на которое он указывает, это повлияет на значение, на которое указывает аргумент, поскольку и параметр функции, и аргумент указывают на одно и то же значение!
- Если параметру функции назначить другой адрес, это не повлияет на аргумент, поскольку параметр функции является копией, и изменение копии не повлияет на оригинал. После изменения адреса параметра функции параметр и аргумент функции будут указывать на разные значения, поэтому разыменование параметра и изменение значения больше не повлияют на значение, на которое указывает аргумент.
Следующая программа иллюстрирует это:
Передача адресов по ссылке
Следующий логичный вопрос: «Что, если внутри функции мы хотим изменить адрес, на который указывает аргумент?». Оказывается, это на удивление просто. Вы можете просто передать адрес по ссылке. Синтаксис для ссылки на указатель немного странный (и его легко набрать задом наперед). Однако если вы напечатаете его в обратном направлении, компилятор выдаст вам ошибку.
Следующая программа иллюстрирует использование ссылки на указатель:
Когда мы запустим программу с этой версией функции, мы получим:
Это показывает, что вызов setToNull() действительно изменил значение ptr с &five на nullptr !
Существует только передача по значению
Теперь, когда вы понимаете основные различия между передачей по ссылке, по адресу и по значению, давайте на мгновение перейдем к упрощению. 🙂
В уроке по ссылкам мы кратко упомянули, что ссылки обычно реализуются компилятором как указатели. Это означает, что за кулисами передача по ссылке – это, по сути, просто передача по адресу (с доступом к ссылке, выполняющим неявное разыменование).
И чуть выше мы показали, что передача по адресу на самом деле просто передача адреса по значению!
Таким образом, можно сделать вывод, что C++ на самом деле всё передает по значению! Свойства передачи по адресу (и ссылке) исходят исключительно из того факта, что мы можем разыменовать переданный адрес для изменения аргумента, чего мы не можем сделать с параметром обычного значения!
Передача по адресу делает изменяемые параметры явными
Рассмотрим следующий пример:
Из вызова foo2() не очевидно, что функция может изменять переменную i , не так ли?
По этой причине некоторые руководства рекомендуют передавать все модифицируемые аргументы по адресу, чтобы из вызова функции было более очевидно, что аргумент может быть изменен.
Однако у этого есть свой набор недостатков: вызывающий может подумать, что он может передать nullptr , когда он не должен так делать, и теперь вам нужно тщательно проверять на нулевые указатели.
Мы склоняемся к рекомендации передавать необязательные изменяемые параметры по ссылке. Более того, избегайте изменяемых параметров вообще.
Плюсы и минусы передачи по адресу
Преимущества передачи по адресу:
- Передача по адресу позволяет функции изменять значение аргумента, что иногда бывает полезно. В противном случае можно использовать const , чтобы гарантировать, что функция не изменит аргумент. (Однако если вы хотите сделать это без указателя, вам следует использовать передачу по ссылке.)
- Поскольку копия аргумента не создается, передача выполняется быстро, даже при использовании с большими структурами или классами.
- Мы можем вернуть несколько значений из функции через выходные параметры.
Недостатки передачи по адресу:
- Поскольку литералы (за исключением строковых литералов в стиле C) и выражения не имеют адресов, аргументы-указатели должны быть обычными переменными.
- Все значения необходимо проверять, чтобы убедиться, что они равны нулю. Попытка разыменовать нулевое значение приведет к сбою. Об этом легко забыть.
- Поскольку разыменование указателя происходит медленнее, чем прямой доступ к значению, доступ к аргументам, переданным по адресу, происходит медленнее, чем доступ к аргументам, переданным по значению.
Когда использовать передачу по адресу:
- При передаче встроенных массивов (если вас не волнует, что они превращаются в указатель).
- При передаче указателя, и nullptr – логически допустимый аргумент.
Когда не использовать передачу по адресу:
- При передаче указателя, и nullptr – логически недопустимый аргумент (используйте передачу по ссылке).
- При передаче структур или классов (используйте передачу по ссылке).
- При передаче базовых типов (используйте передачу по значению).
Как видите, передача по адресу и передача по ссылке имеют практически одинаковые преимущества и недостатки. Поскольку передача по ссылке обычно более безопасна, чем передача по адресу, в большинстве случаев предпочтительнее использовать ее.
Урок №99. Передача по адресу
Есть еще один способ передачи переменных в функцию в языке C++ — по адресу.
Передача по адресу
Передача аргументов по адресу — это передача адреса переменной-аргумента (а не значения исходной переменной). Поскольку аргумент является адресом, то параметром функции должен быть указатель. Затем функция сможет разыменовать этот указатель для доступа или изменения исходного значения. Вот пример функции, которая принимает параметр, передаваемый по адресу:
Вот пример программы, которая вызывает эту функцию:
Помните, что фиксированные массивы распадаются в указатели при передаче в функцию, поэтому их длину нужно передавать в виде отдельного параметра. Перед разыменованием параметров, передаваемых по адресу, не лишним будет проверить — не являются ли они нулевыми указателями. Разыменование нулевого указателя приведет к сбою в программе. Вот функция printArray() с проверкой (обнаружением) нулевых указателей:
Передача по константному адресу
Поскольку printArray() все равно не изменяет значения получаемых аргументов, то хорошей идеей будет сделать параметр array константным:
Так мы видим сразу, что printArray() не изменит переданный аргумент array . Когда вы передаете указатель в функцию по адресу, то значение этого указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Другими словами, он передается по значению! Если изменить значение параметра функции, то изменится только копия, исходный указатель-аргумент не будет изменен. Например:
В tempPtr копируется адрес указателя ptr . Несмотря на то, что мы изменили tempPtr на нулевой указатель (присвоили ему nullptr ), это никак не повлияло на значение, на которое указывает ptr . Следовательно, результат выполнения программы:
Обратите внимание, хотя сам адрес передается по значению, вы все равно можете разыменовать его для изменения значения исходного аргумента. Запутано? Давайте проясним:
При передаче аргумента по адресу в переменную-параметр функции копируется адрес из аргумента. В этот момент параметр функции и аргумент указывают на одно и то же значение.
Если параметр функции затем разыменовать для изменения исходного значения, то это приведет к изменению значения, на которое указывает аргумент, поскольку параметр функции и аргумент указывают на одно и то же значение!
Если параметру функции присвоить другой адрес, то это никак не повлияет на аргумент, поскольку параметр функции является копией, а изменение копии не приводит к изменению оригинала. После изменения адреса параметра функции, параметр функции и аргумент будут указывать на разные значения, поэтому разыменование параметра и дальнейшее его изменение никак не повлияют на значение, на которое указывает аргумент.
Как передать адрес переменной в функцию c
Передача через указатель
С точки зрения удобства программирования, передавать значения, которые могут изменяться внутри функции лучше всего через ссылку (а не через указатель). Чтобы в этом разобраться, ниже описан возможный, но не самый лучший способ передачи параметров — по указателю.
Если передавать параметры через указатель (так называемый С-стиль), код функции выглядит так:
mul5(&var); // Здесь символ & обозначает операцию взятия адреса переменной
Такой синтаксис неудобен. Внутри тела функции, перед переменной, которая является указателем, приходится ставить звездочку * для работы со значением, на который указывает указатель. Кроме того, при вызове функции приходится ставить амперсанд & перед именем переменной для того, чтобы передать адрес переменной (который будет принят в функцию как указатель).
Передача через ссылку
Если передавать параметры через ссылку , код функции выглядит так:
А вызов выглядит так:
Видно, что нет никаких разыменовываний в теле функции функции. Так же при таком вызове не требуется передача адреса переменной.
Однако, следует помнить , что такая передача по ссылке возможна только для единичных типов C/C++ . И не работает для передачи массивов!
Поэтому, к примеру, если функция принимает:
- одномерный массив block из элементов char , элементы которого надо изменить внутри функции (обозначим это как сложный/составной тип)
- целочислительную переменную shift , которую тоже надо изменить внутри функции (это простой базовый тип)
то реализация функции будет выглядеть так:
void clear(unsigned char *block, int &shift)
А вызов функции будет выглядеть так:
unsigned char* anyBlock="Long string";
for(int i=0; i<len; ++i)
clear(anyBlock, anyShift); // Вызов функции
То есть, в вызове нет никаких спецсимволов указателей/адресов ни для первого ни для второго параметра. Для первого параметра нет спецсимвола взятия адреса (хоть и идет работа с указателем) из-за того, что имя массива имеет тип указателя. Для второго параметра нет спецсимвола взятия адреса из-за того, что передача идет по ссылке.
При таком вызове будут изменяться как ячейки массива anyBlock , так и переменная anyShift .