- Функции. Передача аргументов по значению и по ссылке
- Объявление и определение функций
- Статические переменные
- Передача аргументов по ссылке
- Java: передача параметров по значению или по ссылке
- 11.3 – Передача аргументов по ссылке
- Передача по ссылке
- Возврат нескольких значений через выходные параметры
- Ограничения передачи по ссылке
- Передача по константной ссылке
- Ссылки на указатели
- Плюсы и минусы передачи по ссылке
Функции. Передача аргументов по значению и по ссылке
Объявление и определение функций
Язык C как и большинство других языков программирования позволяет создавать программы, состоящие из множества функций, а также из одного или нескольких файлов исходного кода. До сих пор мы видели только функцию main() , которая является главной в программе на C, поскольку выполнение кода всегда начинается с нее. Однако ничего не мешает создавать другие функции, которые могут быть вызваны из main() или любой другой функции. В этом уроке мы рассмотрим создание только однофайловых программ, содержащих более чем одну функцию.
При изучении работы функций важно понимать, что такое локальная и что такое глобальная переменные. В языке программирования C глобальные (внешние) переменные объявляются вне какой-либо функции. С их помощью удобно организовывать обмен данными между функциями, однако это считается дурным тоном, т.к. легко запутывает программу. Локальные переменные в языке программирования C называют автоматическими. Область действия автоматических переменных распространяется только на ту функцию, в которой они были объявлены. Параметры функции также являются локальными переменными.
Структурная организация файла программы на языке C, содержащего несколько функций, может выглядеть немного по-разному. Так как выполнение начинается с main() , то ей должны быть известны спецификации (имена, количество и тип параметров, тип возвращаемого значения) всех функций, которые из нее вызываются. Отсюда следует, что объявляться функции должны до того, как будут вызваны. А вот определение функции уже может следовать и до и после main() . Рассмотрим такую программу:
В данном случае в начале программы объявляется функция median() . Объявляются тип возвращаемого ею значения ( float ), количество и типы параметров ( int a, int b ). Обратите внимание, когда объявляются переменные, то их можно группировать: int a, b; . Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (inta, int b) .
Далее идет функция main() , а после нее — определение median() . Имена переменных-параметров в объявлении функции никакой роли не играют (их вообще можно опустить, например, float median (int, int); ). Поэтому когда функция определяется, то имена параметров могут быть другими, однако тип и количество должны строго совпадать с объявлением.
Функция median() возвращает число типа float . Оператор return возвращает результат выполнения переданного ему выражения; после return функция завершает свое выполнение, даже если далее тело функции имеет продолжение. Функция median() вычисляет среднее значение от двух целых чисел. В выражении (float) (n1 + n2) / 2 сначала вычисляется сумма двух целых чисел, результат преобразуется в вещественное число и только после этого делится на 2. Иначе мы бы делили целое на целое и получили целое (в таком случае дробная часть просто усекается).
В теле main() функция median() вызывается три раза. Результат выполнения функции не обязательно должен быть присвоен переменной.
Вышеописанную программу можно было бы записать так:
Хотя такой способ и экономит одну строчку кода, однако главная функция, в которой отражена основная логика программы, опускается вниз, что может быть неудобно. Поэтому первый вариант предпочтительней.
Напишите функцию, возводящую куб числа, переданного ей в качестве аргумента. Вызовите эту функцию с разными аргументами.
Статические переменные
В языке программирования C существуют так называемые статические переменные. Они могут быть как глобальными, так и локальными. Перед именем статической переменной пишется ключевое слово static .
Внешние статические переменные, в отличие от обычных глобальных переменных, нельзя использовать из других файлов в случае программы, состоящей не из одного файла. Они глобальны только для функций того файла, в котором объявлены. Это своего рода сокрытие данных, по принципу «не выставлять наружу ничего лишнего, чтобы ‘что-нибудь’ нечаянно не могло ‘испортить’ данные».
Статические переменные, объявленные внутри функций имеют такую же область действия, как автоматические. Однако в отличие от автоматических, значения локальных статических переменных не теряются, а сохраняются между вызовами функции:
В этом примере в функции hello() производится подсчет ее вызовов.
Передача аргументов по ссылке
В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.
Однако можно организовать изменение локальной переменной одной функции с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения?! Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение. Рассмотрим пример:
Функция multi() ничего не возвращает, что подчеркнуто с помощью ключевого слова void . Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Но по сути это адрес переменной x из фукнции main() , а значит меняется и ее значение.
Когда multi() вызывается в main() , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов multi(x, 786) привел бы к ошибке, а вызов multi(&x, 786) — правильный, т.к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main() указатель и передавать именно его (в данном случае сама переменная p содержит адрес):
Кроме того, следует знать, что функция может возвращать адрес.
Важно понять механизм так называемой передачи аргументов по ссылке, т.к. это понимание пригодится при изучении массивов и строк. Использовать указатели при работе с простыми типами данных не стоит. Лучше возвращать из функции значение, чем менять локальные переменные одной функции с помощью кода другой функции. Функции должны быть достаточно автономными.
Источник
Java: передача параметров по значению или по ссылке
Простое объяснение принципов передачи параметров в Java.
Многие программисты часто путают, какие параметры в Java передаются по значению, а какие по ссылке. Давайте визуализируем этот процесс, и тогда вы увидите насколько все просто.
Данные передаются между методами через параметры. Есть два способа передачи параметров:
Передача по значению (by value). Значения фактических параметров копируются. Вызываемый метод создает свою копию значений аргументов и затем ее использует. Поскольку работа ведется с копией, на исходный параметр это никак не влияет.
Передача по ссылке (by reference). Параметры передаются как ссылка (адрес) на исходную переменную. Вызываемый метод не создает свою копию, а ссылается на исходное значение. Следовательно, изменения, сделанные в вызываемом методе, также будут отражены в исходном значении.
В Java переменные хранятся следующим образом:
Локальные переменные, такие как примитивы и ссылки на объекты, создаются в стеке.
Объекты — в куче (heap).
Теперь вернемся к основному вопросу: переменные передаются по значению или по ссылке?
Java всегда передает параметры по значению
Чтобы разобраться с этим, давайте посмотрим на пример.
Пример передачи примитивов по значению
Поскольку Java передает параметры по значению, метод processData работает с копией данных. Следовательно, в исходных данных (в методе main ) не произошло никаких изменений.
Теперь рассмотрим другой пример:
Передача объекта
Что здесь происходит? Если Java передает параметры по значению, то почему был изменен исходный список? Похоже, что Java все-таки передает параметры не по значению? Нет, неправильно. Повторяйте за мной: «Java всегда передает параметры по значению».
Чтобы с этим разобраться, давайте посмотрим на следующую диаграмму.
Память стека (stack) и кучи (heap)
В программе, приведенной выше, список fruits передается методу processData . Переменная fruitRef — это копия параметра fruit . И fruits и fruitsRef размещаются в стеке. Это две разные ссылки. Но самое интересное заключается в том, что они указывают на один и тот же объект в куче. То есть, любое изменение, которое вы вносите с помощью любой из этих ссылок, влияет на объект.
Давайте посмотрим на еще один пример:
Передача объекта по ссылке
Память стека (stack) и кучи (heap)
В этом случае для изменения ссылки fruitRef мы использовали оператор new . Теперь fruitRef указывает на новый объект, и, следовательно, любые изменения, которые вы вносите в него, не повлияют на исходный объект списка фруктов.
Итак, Java всегда передает параметры по значению. Однако мы должны быть осторожны при передаче ссылок на объекты.
Вышеприведенная концепция очень важна для правильного решения ваших задач.
Например, рассмотрим удаление узла в односвязном списке.
Удаление узла в связанном списке
Это решение работает во всех случаях, кроме одного — когда вы удаляете первый узел ( Position = 1 ). Основываясь на ранее описанной концепции, видите ли вы в чем здесь проблема? Возможно, поможет следующая диаграмма.
Удаление первого узла односвязного списка
Для исправления алгоритма необходимо сделать следующее:
В этой статье мы обсудили одну небольшую, но важную концепцию Java: передачу параметров.
Подробнее о курсе и программе обучения можно узнать на бесплатном вебинаре, который пройдет 15 апреля.
Источник
11.3 – Передача аргументов по ссылке
Хотя передача по значению во многих случаях подходит, у нее есть несколько ограничений. Во-первых, при передаче в функцию большой структуры или класса передача по значению создаст копию аргумента в параметре функции. Во многих случаях это ненужное снижение производительности, поскольку исходного аргумента было бы достаточно. Во-вторых, при передаче аргументов по значению единственный способ вернуть значение вызывающей функции – через возвращаемое значение функции. Хотя это часто подходит, есть случаи, когда было бы более понятно и эффективно, чтобы функция изменяла переданный аргумент. Передача по ссылке решает обе эти проблемы.
Передача по ссылке
Чтобы передать переменную по ссылке, мы просто объявляем параметры функции как ссылки, а не как обычные переменные:
Когда функция вызывается, ref станет ссылкой на аргумент. Поскольку ссылка на переменную обрабатывается точно так же, как и сама переменная, любые изменения, внесенные в ссылку, передаются аргументу!
Следующий пример показывает это в действии:
Эта программа аналогична той, которую мы использовали в примере передачи по значению, за исключением того, что параметр foo теперь является ссылкой, а не обычной переменной. Когда мы вызываем addOne(value) , ref становится ссылкой на переменную value из main . Этот код дает следующий вывод:
Как видите, функция изменила значение аргумента с 5 на 6!
Возврат нескольких значений через выходные параметры
Иногда нам нужно, чтобы функция возвращала несколько значений. Однако функции могут иметь только одно возвращаемое значение. Один из способов вернуть несколько значений – использовать параметры-ссылки:
Эта функция принимает один параметр (по значению) в качестве входных данных и «возвращает» два параметра (по ссылке) в качестве выходных данных. Параметры, которые используются только для возврата значений вызывающей стороне, называются выходными параметрами. Мы назвали эти параметры с суффиксом out , чтобы обозначить, что эти параметры выходные. Это помогает напомнить вызывающему, что начальное значение, переданное этим параметрам, не имеет значения и что мы должны ожидать их перезаписи. По соглашению выходные параметры обычно являются крайними правыми параметрами.
Давайте рассмотрим, как это работает, более подробно. Сначала функция main создает локальные переменные sin и cos . Они передаются в функцию getSinCos() по ссылке (а не по значению). Это означает, что функция getSinCos() имеет доступ к реальным переменным sin и cos , а не к копиям. getSinCos() присваивает новые значения sin и cos (через ссылки sinOut и cosOut соответственно), которые перезаписывают старые значения в sin и cos . Затем main печатает эти обновленные значения.
Если бы sin и cos были переданы по значению, а не по ссылке, getSinCos() изменила бы копии sin и cos , что привело бы к отмене любых изменений в конце функции. Но поскольку sin и cos были переданы по ссылке, любые изменения, внесенные в sin и cos (через ссылки), сохраняются за пределами функции. Таким образом, мы можем использовать этот механизм для возврата значений обратно вызывающей стороне.
Этот метод, хотя и работает, имеет несколько незначительных недостатков. Во-первых, вызывающий должен передать аргументы для хранения обновленных выходных данных, даже если он не намеревается их использовать. Что еще более важно, синтаксис немного неестественный: и входные, и выходные параметры объединяются в вызове функции. Со стороны вызывающего не очевидно, что sin и cos являются выходными параметрами и будут изменены. Это, наверное, самая опасная часть этого метода (так как может привести к ошибкам). Некоторые программисты и компании считают, что это достаточно большая проблема, чтобы посоветовать вообще избегать выходных параметров или использовать для выходных параметров вместо этого передачу по адресу (что имеет более четкий синтаксис, указывающий, можно ли изменять параметр или нет).
Лично мы рекомендуем по возможности полностью избегать использования выходных параметров. Если вы всё же используете их, обозначайте параметры (и выходные аргументы) суффиксом (или префиксом) «out» , чтобы помочь прояснить, что значение может быть изменено.
Ограничения передачи по ссылке
Неконстантные ссылки могут ссылаться только на неконстантные l-значения (например, неконстантные переменные), поэтому параметр-ссылка не может принимать аргумент, который является константным l-значением или r-значением (например, литералы и результаты выражений).
Передача по константной ссылке
Как упоминалось во введении, одним из основных недостатков передачи по значению является то, что все аргументы, передаваемые по значению, копируются в параметры функции. Когда аргументы представляют собой большие структуры или классы, это может занять много времени. Ссылки позволяют избежать этих затрат. Когда аргумент передается по ссылке, создается ссылка на фактический аргумент (что занимает минимальное время), и копирование значений не происходит. Это позволяет нам передавать большие структуры и классы с минимальной потерей производительности.
Однако это также открывает перед нами потенциальные проблемы. Ссылки позволяют функции изменять значение аргумента, что нежелательно, если мы хотим, чтобы аргумент был доступен только для чтения. Если мы знаем, что функция не должна изменять значение аргумента, но не хотим передавать по значению, лучшим решением будет передача по константной ссылке.
Вы уже знаете, что константная ссылка – это ссылка, которая не позволяет изменять переменную, на которую она ссылается. Следовательно, если мы используем константную ссылку в качестве параметра, мы гарантируем вызывающему, что функция не изменит аргумент!
Следующая функция вызовет ошибку компиляции:
Использование const полезно по нескольким причинам:
- Он обращается к компиляторам за помощью в обеспечении того, чтобы значения, которые нельзя изменять, не изменились (компилятор выдаст ошибку, если вы попытаетесь это сделать, как в приведенном выше примере).
- Он сообщает программисту, что функция не изменит значение аргумента. Это может помочь с отладкой.
- Вы не можете передать константный аргумент неконстантному параметру-ссылке. Использование константных параметров гарантирует, что вы можете передавать функции как неконстантные, так и константные аргументы.
- Ссылки на константные значения могут принимать аргументы любого типа, включая неконстантные l-значения, константные l-значения и r-значения.
Лучшая практика
При передаче аргумента по ссылке всегда используйте константную ссылку, если вам не нужно изменять значение аргумента.
Напоминание
Неконстантные ссылки не могут связываться с r-значениями. Функция с неконстантным параметром-ссылкой не может быть вызвана с литералами или временными значениями.
Ссылки на указатели
Указатель можно передать по ссылке, и функция в этом случае может изменить адрес, на который указывает указатель:
(Мы покажем еще один пример в следующем уроке)
Напоминаем, что вы можете передать по ссылке массив в стиле C. Это полезно, если в функции вам нужна возможность изменять массив (например, для функции сортировки) или вам нужен доступ к информации о типе фиксированного массива (для выполнения sizeof() или цикла for -each). Однако обратите внимание, что для того, чтобы это работало, в параметре вам необходимо явно указать размер массива:
Это означает, что это работает только с фиксированными массивами одной конкретной длины. Если вы хотите, чтобы это работало с фиксированными массивами любой длины, вы можете сделать длину массива параметром шаблона (это будет рассмотрено в другой главе).
Плюсы и минусы передачи по ссылке
Преимущества передачи по ссылке:
- Ссылки позволяют функции изменять значение аргумента, что иногда бывает полезно. В противном случае можно использовать константные ссылки, чтобы гарантировать, что функция не изменит аргумент.
- Поскольку копия аргумента не создается, передача по ссылке выполняется быстро, даже при использовании с большими структурами или классами.
- Ссылки могут использоваться для возврата нескольких значений из функции (через выходные параметры).
- Ссылки должны быть инициализированы, поэтому о нулевых значениях можно не беспокоиться.
Недостатки передачи по ссылке:
- Поскольку неконстантная ссылка не может быть инициализирована константным l-значением или r-значением (например, литералом или выражением), аргументы неконстантных ссылочных параметров должны быть обычными переменными.
- Может быть трудно определить, предназначен ли аргумент, переданный неконстантной ссылкой, для входных данных, выходных данных или для того и другого. Разумное использование const и суффикса именования выходных переменных может помочь прояснить это.
- По вызову функции невозможно сказать, может ли аргумент измениться. Аргументы, переданные по значению и переданные по ссылке, выглядят одинаково. Мы можем определить, передан ли аргумент по значению или по ссылке, только взглянув на объявление функции. Это может привести к ситуациям, когда программист не понимает, что функция изменит значение аргумента.
Когда использовать передачу по ссылке:
- При передаче структур или классов (используйте const , если они доступны только для чтения).
- Когда вам нужна функция для изменения аргумента.
- Когда вам нужен доступ к информации о типе фиксированного массива.
Когда не использовать передачу по ссылке:
- При передаче базовых типов, которые не нужно изменять (используйте передачу по значению).
Лучшая практика
Используйте передачу по (константной) ссылке вместо передачи по значению для структур, классов и других типов, которые требуют больших затрат на копирование.
Источник