- Передача аргументов в функцию
- Передача массивов в функцию
- Практика
- Функции
- Введение
- Формальные и фактические параметры
- Передача аргументов
- Объявление функции и определение функции. Создание собственной библиотеки
- Передача массива в качестве аргумента
- Урок №97. Передача по значению
- Плюсы и минусы передачи по значению
- Урок №98. Передача по ссылке
- Передача по ссылке
- Возврат сразу нескольких значений
- Передача по константной ссылке
Передача аргументов в функцию
Пожалуйста, приостановите работу AdBlock на этом сайте.
Как уже отмечалось выше, при вызове функции в неё передаются не сами переменные, а только их значения. Т.е. функция, что бы она там ни делала, не может никак повлиять на значения переменных, которые в неё передаются. Такой способ передачи значений в функцию называется передачей аргументов по значению .
Рассмотрим пример. Допустим, мы хотим написать программу, которая меняет местами значения переменных. Кажется, всё просто.
Вроде бы всё должно работать, но нет. Запустите программу и убедитесь в этом. И дело, как видите, не в функции, функция работает правильно, и значения переменных a и b действительно меняются, но к нашим исходным переменным это не имеет никакого отношения, т.к. они в функцию не передавались, а передавались только их значения.
Это никуда не годится. Чтобы с этим справиться, необходимо использовать другой способ передачи аргументов в функцию – передачу аргументов по ссылке . Для этого нам придётся воспользоваться указателями. Надеюсь вы хорошо с ними разобрались на прошлом шаге.
Напомню, что указатель – это такая переменная, в которой хранится адрес в памяти. В зависимости от типа указателя по этому адресу хранится значение соответствующего типа. Перепишем нашу программу используя передачу аргумента по ссылке.
Программа снабжена подробными комментариями, но я ещё раз поясню, что происходит. В начале программы объявляются переменные x и y . Им присваиваются значения 4 и 9 соответственно. Адреса этих переменных передаются в функцию swap . Обращаю особое внимание. Передаются адреса переменных, а не их значения. Внутри функции, используя дополнительную переменную, значения по переданным адресам меняются местами. Функция swap заканчивает свою работу.
Передача массивов в функцию
Массивы, как и простые переменные, можно передавать в функцию, но есть ряд особенностей, с которыми нам следует познакомиться. Продемонстрирую их на примере. В следующей программе я написал две функции, которые выводят на экран переданный им массив.
Первый момент. Если в функцию предполагается передавать массив, то необходимо об этом предупредить компилятор. Для этого в заголовке функции при её описании необходимо рядом с именем переменной-массива указать пару квадратных скобок [] . Посмотрите на объявление функции print_arr : из него сразу понятно, что переменная arr будет массивом, т.к. рядом с её именем указаны [] .
Второй момент. Когда мы передаём массив в функцию, то функция ничего не знает о размере этого массива. Поэтому необходимо дополнительно передавать размер массива в функцию отдельным параметром.
Третий нюанс. Если в функцию передаётся двумерный массив, то кроме двух пар квадратных скобочек [][] , во второй из них необходимо явно указать её размерность. В примере выше это 5 .
Четвертый нюанс. Массивы всегда передаются в функцию по ссылке. Это означает, что любые изменения массива внутри функции отразятся на исходном массиве.
Есть и другие способы передать массив в функцию, они связаны с передачей указателей на начало массива, но о них мы не будем упоминать в рамках данного курса.
Практика
Решите предложенные задачи:
Для удобства работы сразу переходите в полноэкранный режим
Источник
Функции
Введение
Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию main и по несколько раз копируем одни и те же действия, создаём десятки переменных с уникальными именами. Наши программы распухают и становятся всё менее и менее понятными, ветвления становятся всё длиннее и ветвистее.
Но из сложившейся ситуации есть выход! Теперь мы научимся создавать функции на си. Функции, во-первых, помогут выделить в отдельные подпрограммы дублирующийся код, во-вторых, помогут логически разбить программу на части, в-третьих, с функциями в си связано много особенностей, которые позволят использовать новые подходы к структурированию приложений.
Функция – это именованная часть программы, которая может быть многократно вызвана из другого участка программы (в котором эта функция видна). Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов. Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать.
Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в си определяется в глобальном контексте. Синтаксис функции:
Самый простой пример – функция, которая принимает число типа float и возвращает квадрат этого числа
Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:
В данном случае сначала будет выполнено умножение, а после этого возврат значения. В том случае, если функция ничего не возвращает, типом возвращаемого значения будет void. Например, функция, которая печатает квадрат числа:
в данном случа return означает выход из функции. Если функция ничего не возвращает, то return можно не писать. Тогда функция доработает до конца и произойдёт возврат управления вызывающей функции.
Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:
Формальные и фактические параметры
П ри объявлении функции указываются формальные параметры, которые потом используются внутри самой функции. При вызове функции мы используем фактические параметры. Фактическими параметрами могут быть переменные любого подходящего типа или константы.
Например, пусть есть функция, которая возвращает квадрат числа и функция, которая суммирует два числа.
Обращаю внимание, что приведение типов просиходит неявно и только тогда, когда это возможно. Если функция получает число в качестве аргумента, то нельзя ей передать переменную строку, например «20» и т.д. Вообще, лучше всегда использовать верный тип или явно приводить тип к нужному.
Если функция возвращает значение, то оно не обязательно должно быть сохранено. Например, мы пользуемся функцией getch, которая считывает символ и возвращает его.
Передача аргументов
При передаче аргументов происходит их копирование. Это значит, что любые изменения, которые функция производит над переменными, имеют место быть только внутри функции. Например
Программы выведет
200
100
200
Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int
Вот теперь программа выводит
200
100
100
Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.
В программировании первый способ передачи параметров называют передачей по значению, второй – передачей по указателю. Запомните простое правило: если вы хотите изменить переменную, необходимо передавать функции указатель на эту переменную. Следовательно, чтобы изменить указатель, необходимо передавать указатель на указатель и т.д. Например, напишем функцию, которая будет принимать размер массива типа int и создавать его. С первого взгляда, функция должна выглядеть как-то так:
Но эта функция выведет ERROR. Мы передали адрес переменной. Внутри функции init была создана локальная переменная a, которая хранит адрес массива. После выхода из функции эта локальная переменная была уничтожена. Кроме того, что мы не смогли добиться нужного результата, у нас обнаружилась утечка памяти: была выделена память на куче, но уже не существует переменной, которая бы хранила адрес этого участка.
Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.
Вот теперь всё работает как надо.
Ещё подобный пример. Напишем функцию, которая принимает в качестве аргумента строку и возвращает указатель на область памяти, в которую скопирована эта строка.
В этом примере утечки памяти не происходит. Мы выделили память с помощью функции malloc, скопировали туда строку, а после этого вернули указатель. Локальные переменные были удалены, но переменная test хранит адрес участка памяти на куче, поэтому можно его удалить с помощью функции free.
Объявление функции и определение функции. Создание собственной библиотеки
В си можно объявить функцию до её определения. Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать. Например
Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.
Обычно объявление функции помещают отдельно, в .h файл, а определение функций в .c файл. Таким образом, заголовочный файл представляет собой интерфейс библиотеки и показывает, как с ней работать, не вдаваясь в содержимое кода.
Давайте создадим простую библиотеку. Для этого нужно будет создать два файла – один с расширением .h и поместить туда прототипы функций, а другой с расширением .c и поместить туда определения этих функций. Если вы работаете с IDE, то .h файл необходимо создавать в папке Заголовочные файлы, а файлы кода в папке Файлы исходного кода. Пусть файлы называются File1.h и File1.c
Перепишем предыдущий код. Вот так будет выглядеть заголовочный файл File1.h
Содержимое файла исходного кода File1.c
Наша функция main
Рассмотрим особенности каждого файла. Наш файл, который содержит функцию main, подключает необходимые ему библиотеки а также заголовочный файл File1.h. Теперь компилятору известны прототипы функций, то есть он знает возвращаемый тип, количество и тип аргументов и имена функций.
Заголовочный файл, как и оговаривалось ранее, содержит прототип функций. Также здесь могут быть подключены используемые библиотеки. Макрозащита #define _FILE1_H_ и т.д. используется для предотвращения повторного копирования кода библиотеки при компиляции. Эти строчки можно заменить одной
Файл File1.c исходного кода подключает свой заголовочный файл. Всё как обычно логично и просто. В заголовочные файлах принято кроме прототипов функций выносить константы, макроподстановки и определять новые типы данных. Кроме того, именно в заголовочных файлах можно обширно комментировать код и писать примеры его использования.
Передача массива в качестве аргумента
К ак уже говорилось ранее, имя массива подменяется на указатель, поэтому передача одномерного массива эквивалентна передаче указателя. Пример: функция получает массив и его размер и выводит на печать:
В этом примере функция может иметь следующий вид
Также напомню, что правило подмены массива на указатель не рекурсивное. Это значит, что необходимо указывать размерность двумерного массива при передаче
Либо, можно писать
Если двумерный массив создан динамически, то можно передавать указатель на указатель. Например функция, которая получает массив слов и возвращает массив целых, равных длине каждого слова:
Можно вместо того, чтобы возвращать указатель на массив, передавать массив, который необходимо заполнить
На этом первое знакомство с функциями заканчивается: тема очень большая и разбита на несколько статей.
Источник
Урок №97. Передача по значению
Обновл. 13 Сен 2021 |
По умолчанию, аргументы в C++ передаются по значению. Когда аргумент передается по значению, то его значение копируется в параметр функции. Например:
В первом вызове функции boo() аргументом является литерал 7 . При вызове boo() создается переменная y , в которую копируется значение 7 . Затем, когда boo() завершает свое выполнение, переменная y уничтожается.
Во втором вызове функции boo() аргументом является уже переменная x = 8 . Когда boo() вызывается во второй раз, переменная y создается снова и значение 8 копируется в y . Затем, когда boo() завершает свое выполнение, переменная y снова уничтожается.
В третьем вызове функции boo() аргументом является выражение x + 2 , которое вычисляется в значение 10 . Затем это значение передается в переменную y . При завершении выполнения функции boo() переменная y вновь уничтожается.
Таким образом, результат выполнения программы:
y = 7
y = 8
y = 10
Поскольку в функцию передается копия аргумента, то исходное значение не может быть изменено функцией. Это хорошо проиллюстрировано в следующем примере:
x = 7
y = 7
y = 8
x = 7
В начале функции main() x равно 7 . При вызове boo() значение x ( 7 ) передается в параметр y функции boo(). Внутри boo() переменной y сначала присваивается значение 8 , а затем у уничтожается. Значение x не изменяется, даже если изменить y .
Параметры функции, переданные по значению, также могут быть const. Тогда уже будет 100% гарантия того, что функция не изменит значение параметра.
Плюсы и минусы передачи по значению
Плюсы передачи по значению:
Аргументы, переданные по значению, могут быть переменными (например, x ), литералами (например, 8 ), выражениями (например, x + 2 ), структурами, классами или перечислителями (т.е. почти всем, чем угодно).
Аргументы никогда не изменяются функцией, в которую передаются, что предотвращает возникновение побочных эффектов.
Минусы передачи по значению:
Копирование структур и классов может привести к значительному снижению производительности (особенно, когда функция вызывается много раз).
Когда использовать передачу по значению:
При передаче фундаментальных типов данных и перечислителей, когда предполагается, что функция не должна изменять аргумент.
Когда не использовать передачу по значению:
При передаче массивов, структур и классов.
В большинстве случаев, передача по значению — это наилучший способ передачи аргументов фундаментальных типов данных, когда функция не должна изменять исходные значения. Передача по значению является гибкой и безопасной, а в случае фундаментальных типов данных еще и эффективной.
Поделиться в социальных сетях:
Источник
Урок №98. Передача по ссылке
Обновл. 13 Сен 2021 |
Хотя передача по значению является хорошим вариантом во многих случаях, она имеет несколько ограничений. Во-первых, при передаче по значению большой структуры или класса в функцию, создается копия аргумента и уже эта копия передается в параметр функции. В большинстве своем это бесполезная трата ресурсов, которая снижает производительность.
Во-вторых, при передаче аргументов по значению, единственный способ вернуть значение обратно в вызывающий объект — это использовать возвращаемое значение функции. Но иногда случаются ситуации, когда нужно, чтобы функция изменила значение переданного аргумента. Передача по ссылке решает все эти проблемы.
Передача по ссылке
При передаче переменной по ссылке нужно просто объявить параметры функции как ссылки, а не как обычные переменные:
При вызове функции переменная x станет ссылкой на аргумент. Поскольку ссылка на переменную обрабатывается точно так же, как и сама переменная, то любые изменения, внесенные в ссылку, приведут к изменениям исходного значения аргумента! В следующем примере это хорошо проиллюстрировано:
Эта программа точно такая же, как и программа из предыдущего урока, за исключением того, что параметром функции boo() теперь является ссылка вместо обычной переменной.
Результат выполнения программы:
value = 6
value = 7
Как вы можете видеть, функция изменила значение аргумента с 6 на 7 !
Вот еще один пример:
Результат выполнения программы:
Обратите внимание, значение аргумента a было изменено функцией.
Возврат сразу нескольких значений
Иногда нам может понадобиться, чтобы функция возвращала сразу несколько значений. Однако оператор return позволяет функции иметь только одно возвращаемое значение. Одним из способов возврата сразу нескольких значений является использование ссылок в качестве параметров:
Эта функция принимает один параметр (передача по значению) в качестве входных данных и «возвращает» два параметра (передача по ссылке) в качестве выходных данных. Параметры, которые используются только для возврата значений обратно в caller, называются параметрами вывода. Они дают понять caller-у, что значения исходных переменных, переданных в функцию, не столь значительны, так как мы ожидаем, что эти переменные будут перезаписаны.
Давайте рассмотрим это детально. Во-первых, в функции main() мы создаем локальные переменные sin и cos . Они передаются в функцию getSinCos() по ссылке (а не по значению). Это означает, что функция getSinCos() имеет прямой доступ к исходным значениям переменных sin и cos , а не к их копиям. Функция getSinCos(), соответственно, присваивает новые значения переменным sin и cos (через ссылки sinOut и cosOut ), перезаписывая их старые значения. Затем main() выводит эти обновленные значения.
Если бы sin и cos были переданы по значению, а не по ссылке, то функция getSinCos() изменила бы копии sin и cos , а не исходные значения и эти изменения уничтожились бы в конце функции — переменные вышли бы из локальной области видимости. Но, поскольку sin и cos передавались по ссылке, любые изменения, внесенные в sin или cos (через ссылки), сохраняются и за пределами функции getSinCos(). Таким образом, мы можем использовать этот механизм для возврата сразу нескольких значений обратно в caller.
Хотя этот способ хорош, но он также имеет свои нюансы. Во-первых, синтаксис немного непривычен, так как параметры ввода и вывода указываются вместе с вызовом функции. Во-вторых, в caller-е не очевидно, что sin и cos являются параметрами вывода, и они будут изменены функцией. Это, вероятно, самая опасная часть данного способа передачи (так как может привести к ошибкам). Некоторые программисты считают, что это достаточно большая проблема, и не советуют передавать аргументы по ссылке, отдав предпочтение передаче по адресу, не смешивая при этом параметры ввода и вывода.
Лично я не рекомендую смешивать параметры ввода и вывода именно по этой причине, но если вы это делаете, то обязательно добавляйте комментарии к коду, описывая, что вы делаете и как это делаете.
Неконстантные ссылки могут ссылаться только на неконстантные l-values (например, на неконстантные переменные), поэтому параметр-ссылка не может принять аргумент, который является константным l-value или r-value (например, литералом или результатом выражения).
Передача по константной ссылке
Одним из самых главных недостатков передачи по значению является то, что все аргументы, переданные по значению, копируются в параметры функции. Когда аргументами являются большие структуры или классы, то этот процесс может занять много времени. В случае с передачей по ссылке эта проблема легко решается. Когда аргумент передается по ссылке, то создается ссылка на фактический аргумент (что занимает минимальное количество времени на выполнение), и никакого копирования значений не происходит. Это позволяет передавать большие структуры или классы с минимальной затратой ресурсов.
Однако здесь также могут возникнуть потенциальные проблемы. Ссылки позволяют функции изменять значения аргументов напрямую, что нежелательно, если мы хотим, чтобы аргумент был доступен только для чтения. Когда мы знаем, что функция не должна изменять значение аргумента, но не хотим использовать передачу по значению, то лучшим решением будет использовать передачу по константной ссылке.
Вы уже знаете, что константная ссылка — это ссылка на переменную, значение которой изменить через эту же ссылку не получится никак. Следовательно, если мы используем константную ссылку в качестве параметра, то получаем 100% гарантию того, что функция не изменит аргумент!
Запустив следующий фрагмент кода, мы получим ошибку компиляции:
Источник