Как сделать функцию в c
Пройдя не такой уж долгий путь от нашего первого урока к этому, вы “подошли” к изучению функций в C++. Функции – это именованный фрагмент кода, который повторяется в программе 2 или больше раз . Когда мы пишем функцию, необходимо дать ей имя и в дальнейшем, чтобы её вызвать в программе (из main() или из другой функции), надо обратиться к ней по этому имени.
Мы уже встречали функции в предыдущих уроках. Это функции для строк (символьных массивов) strlen() , strcmp() , функция для генерации случайных чисел rand() . Мы их применяли в программах и, например, передавали в функцию strlen() строку, а она нам возвращала количество символов в этой строке (целое число).
Это конечно происходило не волшебным образом, а функция принимала нашу строку, обрабатывала её и возвращала нам значение, которое подсчитала. То есть кто-то до нас написал этот самый код функции, которая считает длину строки и мы успешно ею пользуемся в своих программах. И эта функция здорово экономит наше время, сокращает количество строк кода и облегчает его читаемость.
Да – есть эти замечательные стандартные библиотечные функции, которые мы можем применять в своих программах, но в большинстве случаев каждое новое задание уникально и стандартные функции не всегда подойдут. В С++ программист может самостоятельно написать собственную функцию и применять её с таким же успехом, как и библиотечные функции.
До определённого времени можно обходиться и без функций. Вместо этого плодить одинаковый участок кода во всей программе. Но если придется изменить этот код (усовершенствовать или что-то убрать из него), придется вносить изменения по всей программе. Лучше сразу освоить тему функций и активно применять.
Определить функцию можно двумя способами:
- до main -функции;
- после main -функции. В этом случае необходимо до main -функции объявить прототип собственной функции.
В этой статье и следующих мы будем пользоваться вторым способом, так как он является более распространённым. Первый способ можно использовать, если функция одна и её код совсем небольшой. Пока мы пишем простые программы, такое встречается часто. Но для программ посложней, будем писать несколько функций которые будут состоять не из 2-3 строк, а побольше. Покажу вам как выглядит определение функции до main() :
С использованием прототипа это будет выглядеть так:
Прототип функции размещен в строке 4, а её определение находится в самом низу программы в строках 20 – 25. Что касается выполнения программы: сначала компилятор прочтет прототип. Это даст ему знать о том, что где-то после main() располагается определение этой функции.
Далее начнется выполнение главной функции main() . Выполняться она будет, пока компилятор не встретит имя функции ourFunctionForPrint() . Тогда он найдет определение этой функции, которое расположено после main() , по имени, указанному в прототипе, выполнит её код, после чего снова вернется к выполнению команд main -функции.
В итоге на экране увидим:
Поговорим об определении функций.
Функции в C++ могут не возвращать никаких значений (как в примере) и могут возвращать какое-либо значение. Если функция не возвращает ничего, то это функция типа void .
Синтаксис функции, которая не возвращает значений:
Имя функции следует давать придерживаясь правил для имен переменных. Единственное – желательно чтобы оно содержало глагол, так как функция выполняет действие. Например если она считает среднее арифметическое можно дать название calculateAverage , если выводит что-то на экран – showText . Имя должно говорить за себя, чтобы не пришлось оставлять лишние комментарии в коде.
Параметры (или аргументы функции) – это данные, которые функция принимает и обрабатывает в теле. Если функции не нужно ничего принимать для обработки, круглые скобки оставляют пустыми. Согласно правилам High Integrity C++ Coding Standard желательно не определять функции с больш и м количеством параметров (больше 6).
Рассмотрим пару примеров с функциями, которые принимают параметры и не возвращают значений.
Принимает один параметр:
В 10-й строке кода функция получает параметр – целое число 7. С ним (с этим числом) произойдет то, что описано в определении функции – строки 16 – 22. А именно – это число подставится в заголовок цикла for . Выражение i questionCount станет равнозначным i 7 . В итоге мы увидим на экране 7 знаков вопроса.
Принимает три параметра:
Синтаксис функции, которая возвращает значение:
Такие функции отличаются тем, что необходимо указать тип значения, которое вернет функция в результате своей работы. Сам возврат значения в программу оформляется оператором return и это значение программа получит в том месте, где функция была вызвана . return может возвращать переменную, константу или результат выражения (например: return variable1 - variable2 ; ). В теле функции могут находиться несколько операторов return . Тогда, работа функции завершится, если сработает какой-то один из этих операторов. Например:
Определение функции располагается в строках 28 – 34. Если пользователь введет + , сработает блок if в строке 30, а в нём соответственно сработает return d1 + d2 ; . После этого код функции уже не будет обрабатываться дальше. Компилятор вернется к выполнению main -функции.
Вы наверное заметили, что в предыдущем коде названия параметров в прототипе и в самом определении функции отличаются от имен переменных, которые передаются в функцию из main . Дело в следующем – параметры в определении и прототипе функции формальные. Когда мы передаем переменные в виде параметров, функция будет работать не с оригиналами переменных, а с их точными копиями. Эти копии создаются в оперативной памяти в момент вызова функции. Она работает с этими копиями, а по завершении работы, копии уничтожаются. Так что в прототипах вы можете использовать точные имена переменных, но функция в любом случае будет работать не напрямую с ними, а с их копиями. То есть сами переменные она не может изменить. Когда в следующих уроках вы познакомитесь с указателями и ссылками – узнаете, как можно изменить значения передаваемых переменных в теле функции.
Еще немного о прототипе: прочитав его до main , компилятор получает сведения о том, какой тип возвращаемого значения будет у функции (или она вообще не возвращает значения – имеет тип void ) и о том, какие параметры будут в неё переданы, в каком количестве и в какой последовательности.
Прототип int calculateSomeDigits ( int d1 , int d2 , char ch ) ; говорит компилятору, что функция вернет на место её вызова целое число и о том, что при вызове в нее должно быть передано два целых числа и один символ. При вызове функции, мы должны передать ей столько параметров сколько указано в её заголовке при определении.
Передавать параметры необходимо в том же порядке, как они определены в круглых скобках за именем функции. Иначе возникнут ошибки при компиляции либо программа будет работать некорректно.
Синтаксис прототипа функции:
Если параметров несколько – они должны отделяться запятой. Легче всего объявить прототип – это скопировать из определения функции первую строку (заголовок) и после закрывающей круглой скобки добавить точку с запятой.
Имена переменных-параметров в прототипе можно не указывать. Следующий прототип равнозначен тому, что выше.
На мой взгляд, все же лучше объявлять прототипы функций с указанием имён параметров. Особенно если параметров несколько и они имеют одинаковый тип. Для читаемости и понимания программы так будет лучше.
Чтобы закрепить то, о чём говорили в этой статье, надо попрактиковаться. Смотрите статью с задачами на функции в C++ . В ней вы так же найдете информацию о том, как передавать в функции массивы в виде параметров. Совет – не просто читайте, а пишите код! Желательно своими силами.
Создадим функцию, которая будет вычислять и затем возвращать значение площади треугольника.
- Вначале пишется уровень доступа, который у нас будет публичный (public)
- затем тип возвращаемых данных (double)
- и имя самой функции. (STriangle)
- во входных параметрах будет две переменные, это основание треугольника (a) и высота (h):
Ключевое слово return определяет, что вернет наша функция после её вызова. Для того чтобы передать комментарии нашей функции, воспользуемся метаданными. Для их создания достаточно написать три символа слеша подряд: /// появится следующая конструкция, которую необходимо наполнить данными:
Вызов функции
Чтобы воспользоваться методом STriangle необходимо создать экземпляр класса Program и затем через него вызвать созданную нами функцию:
В методе Main вызовем данный метод и посмотрим результат:
Результат выполнения функции
p/s: С помощью ToString() мы преобразовали выходной тип double к string.
На этом изучение методов заканчивается, следующий урок будет посвящен классам.
На первой строчке описана сигнатура функции. Сигнатура, это полное описание всех свойств функции, которое состоит из следующих частей.
Название функции
- В примере это “get_response”.
- Имя, которое мы будем писать чтобы вызвать эту функцию.
- Требования к имени функции такие же, как к имени переменной.
Параметры (аргументы) функции
- В примере это два int параметра “min_response” и “max_response”.
- Параметры, которые мы будем передавать в функцию для того, чтобы она что-то с нимим сделала.
- Параметры являются локальными переменными, которым присваисваиваются значения, переданные в функицю.
- Требования к имени параметра такие же, как к имени переменной.
- Тип параметра может быть любым.
Возвращаемое значение
- В этом примере это “int” в начале строчки.
- Значение, которое функция возвращает в то место, откуда её вызвали.
- Тип возвращаемого значения может быть любым.
- В примере функция возвращает int значение в место вызова (в месте вызова это значение записывается в массив responses).
- Очень часто переменные просто исполняются, и не возвращают никаких значний – в этом случае надо использовать тип возвращаемого значения void.
- Чтобы вернуть значение, надо написать “return ЗНАЧЕНИЕ;” (в случае void простос “return;”) – эта операция завершает выполнение функции и программа продолжает выполняться в месте вызова функции.
Так, с функцией разобрались – давай теперь разберёмся как её описать в программе.
Сначала два важных понятия:
- Определение (definition) функции – сигнатура функции (определение также называют прототипом)
- Реализация (implementation) функции – сигнатура функции + текст функции
Описать функцию можно двумя способами:
Сначала определение, потом реализация
Смотри, есть одно важное правило:
Вызов функции должен располагаться в коде после её определения.
То есть такой код не скомпилируется:
Ага, понятно – поэтому там определение сначала написано. А что со вторым вариантом?
Только реализация
Здесь реализация функции выступает сразу и определением – и мы без проблем можем вызвать её в main().
Создай функцию, которая возвращает int число 42 и выведи её значение в функции main().
Опиши функцию двумя способами: “сначала определение, потом реализация” и “только реализация”.
У начинающих мастеров владения функциями возникают проблемы такого характера:
- Как мне веруть больше одного значения?
- Почему я передаю указатель, а туда не записывается адрес на выделенную память?
- Как объединить две похожих функции?
Давай разберём эти проблемы.
Как мне веруть больше одного значения?
Как ты успел заметить, в функции можно указать только одно возвращаемое значение. Однако, это не проблема для мастера владения функциями:
Вот и пригодились указатели.
Почему я передаю указатель, а туда не записывается адрес на выделенную память?
Вот пример кода с ошибкой:
Здесь проблема в том, что параметр arr это локальная переменная функции fill_arr, которая просто скопировала себе адрес переменной A.
Из-за того что arr это вообще другая переменная, в переменную A адрес на выделенную память так и не доходит. Что можно сделать?
- Вспомнить что надо передавать адрес переменной, чтобы туда что-то записать:
- Вспомнить что существует возвращаемое значение
Если что, то в случае, когда ты просто хочешь поработать с существующим массивом внутри функции, ничего такого делать не надо – параметр функции просто скопирует адрес твоего указателя, и ты будешь работать с ним как обычно.
Как объединить две похожих функции?
Например, у тебя есть две функции – одна ищет в массиве максимальное положительное число, а другая минимальное положительное число:
Можно объединить функцию поиска в одну, добавив в функцию параметр, который будет определять максимальное или минимальное число надо найти:
Было две, стала одна – благодаря этому область поиска возможных ошибок сократилась вдвое и модифицировать код стало легче (например, если я захочу переделать алгоритм под поиск в матрице).
Процесс, в результате которого код становится удобнее читать и поддерживать, называется рефакторингом (refactoring).
Также, если у тебя какая-либо функция разрастается настолько, что не помещается даже на полтора экрана – лучше задуматься о вынесении каких-то кусков этой функции в отдельные функции. При этом не важно, что эта функция будет использоваться однократно – зато читать такой код будет намного легче (это важнее).
В предыдущей главе мы определили функцию как набор инструкций, которые выполняются последовательно. Хотя это, безусловно, правда, это определение не дает подробного понимания того, почему функции полезны. Давайте обновим наше определение: функция – это многократно используемая последовательность инструкций, предназначенная для выполнения определенной работы.
Вы уже знаете, что каждая программа должна иметь функцию с именем main (именно отсюда программа начинает свое выполнение при запуске). Однако по мере того, как программы становятся всё длиннее и длиннее, становится всё труднее управлять размещением всего кода внутри функции main . Функции позволяют разделить наши программы на небольшие модульные части, которые легче организовать, тестировать и использовать. Большинство программ используют множество функций. Стандартная библиотека C++ поставляется с множеством уже написанных функций, которые вы можете использовать, но также часто можно писать и свои собственные. Функции, которые вы пишете сами, называются пользовательскими функциями.
Рассмотрим случай, который может иметь место в реальной жизни: вы читаете книгу и вспоминаете, что вам нужно позвонить по телефону. Вы помещаете закладку в свою книгу, совершаете телефонный звонок, а когда заканчиваете телефонный звонок, вы возвращаетесь в место, которое отметили закладкой, и продолжаете читать книгу с того места, где остановились.
Функция, инициирующая вызов функции, называется вызывающей функцией или caller, а вызываемая функция – callee.
Пример пользовательской функции
Во-первых, давайте начнем с самого базового синтаксиса для определения пользовательской функции. В этом уроке все пользовательские функции (кроме main ) будут иметь следующий вид:
Мы поговорим подробнее о различных частях этого синтаксиса в нескольких следующих уроках. На данный момент идентификатор будет просто заменен именем вашей пользовательской функции. Фигурные скобки и инструкции между ними называются телом функции.
Вот пример программы, которая показывает, как определяется и вызывается новая функция:
Эта программа создает следующий вывод:
Эта программа начинает выполнение с начала функции main , и первая строка, которая будет выполняться, выводит текст Starting main() .
Вторая строка в main – это вызов функции doPrint . Мы вызываем функцию doPrint , добавляя пару скобок к имени функции, например: doPrint() . Обратите внимание: если вы забудете скобки, ваша программа может не компилироваться (а если это произойдет, функция не будет вызываться).
Предупреждение
Не забывайте при вызове функции ставить круглые скобки () после имени функции.
Поскольку был выполнен вызов функции, выполнение инструкций в main приостанавливается, и выполнение переходит к началу вызываемой функции doPrint . Первая (и единственная) строка в doPrint печатает текст In doPrint() . Когда doPrint завершается, выполнение возвращается к вызывающей функции (здесь: функция main ) и возобновляется с того места, где оно было остановлено. Следовательно, следующая инструкция, выполняемая в main , выводит на печать Ending main() .
Вызов функций более одного раза
Одна полезная особенность функций заключается в том, что их можно вызывать более одного раза. Вот программа, которая демонстрирует это:
Эта программа создает следующий вывод:
Так как doPrint вызывается из main дважды, doPrint выполняется дважды, и In doPrint() печатается дважды (один раз для каждого вызова).
Функции, вызывающие функции, вызывающие функции
Вы уже видели, что функция main может вызывать другую функцию (например, функцию doPrint в приведенном выше примере). Любая функция может вызывать любую другую функцию. В следующей программе функция main вызывает функцию doA , которая вызывает функцию doB :
Эта программа создает следующий вывод:
Вложенные функции не поддерживаются
В отличие от некоторых других языков программирования, в C++ функции не могут быть определены внутри других функций. Следующая программа не является корректной:
Правильный способ написать приведенную выше программу:
Небольшой тест
Вопрос 1
Как в определении функции называются фигурные скобки и инструкции между ними?
Вопрос 2
Что печатает следующая программа? Не компилируйте ее, просто отследите код самостоятельно.
Читайте также: