Как вернуть значение из функции c
Перейти к содержимому

Как вернуть значение из функции c

  • автор:

Как вернуть значение из функции c

Функция может иметь результат — некоторое значение, которое оно возвращает во внешний код. Для возвращения результата функция применяет оператор return , которые имеет следующие способы использования:

Первая форма может использоваться в тех функциях, которые не возвращают никакого значения, то есть имеют в качестве возвращаемого типа void .

Вторая форма применяется, если функция имеет в качестве возвращаемого типа любой тип, кроме void . Выражение после оператора return представляет возвращаемое значение.

В прошлой теме была определена функция sum , которая в качестве возвращаемого типа имела тип void , то есть по факту ничего не возвращает:

Хотя эта функция не использует явно оператор return , но компилятор сам его добавляет автоматически в конец функции:

Эти оба определения фактически равнозначны.

Но также функция может возвращать какое-то конкретное значение. Например, изменим функцию sum, чтобы она возвращала сумму чисел:

Теперь функция sum в качестве возвращаемого типа имеет тип int . Если тип функции отличен от void , то функция обязательно должна возвращать значение данного типа. Для возвращения значения применяется оператор return , после которого указывается возвращаемое значение.

В данном случае функция sum возвращает сумму параметров:

Если функция возвращает какое-либо значение, то при ее вызове мы можем получить это значение в переменную:

Прототип функции sum будет выглядеть так:

Логика возвращения результата может быть более сложной. Например:

В данном случае функция calculate() принимает три параметра. Первые два параметра представляют числа, над которыми выполняется арифметическая операция. А третий параметр представляет знак операции. И в зависимости от знака операции функция возвращает либо сумму, либо разность чисел. Или число 0, если операция не поддерживается.

C# Return – How to leave a function/method and return values

If you’re a seasoned programmer you’ll know that the c# return statement is used to exit a method, optionally passing a value (a return parameter) back to the calling function.

But since you’re here, I’m guessing you’re new enough to C # (or programming in general) to benefit from a deeper dive into what a function is, what we mean by a returning from a function and how we go about returning parameters. So, let’s get into it!

          Example 1 – Simple Return

          This example function just writes “Hello World” to the console then exits, but the interesting part is the return statement, this calls an end to the function and would be where a value could be returned (see returning a value example below). This method is declared as returning void, i.e. it doesn’t return anything, so we put nothing after the return statement.

          Example 2 – Implied Return

          The docs tell us that “If the method is a void type, the return statement can be omitted.”; this means that we can leave a function (i.e. return from a function) without explicitly using the return statement, but only in a function that returns void (i.e. doesn’t return anything).

          Example 3 – Returning a value

          Besides the blatant homage to xkcd, this example shows a function which is called GetRandomNumber and we declare that it will return an int. The body of the function (the bit between the to curly braces) is a single line which return an integer, the integer ‘4’ to be exact.

          What happens if you declare a function as returning an int, but return something else, like a string? or don’t return anything at all? Either way, you’ll get a compile time error: something like “Cannot implicitly convert type ‘string’ to ‘int’” or “An object of a type convertible to ‘int’ is required” respectively.

          Using a returned value

          What do we mean by returning a value? It means that the code that calls the function receives a value back when it calls itm and can make use of that value. This allows us to subdivide our code into re-useable blocks (functions) and to use the results of those blocks.

          Example 4 – Early Return

          So far, all of our examples functions have returned at the end, but there’s not reason you can’t return earlier in the function:

          As you can see from the output of this example, the function ends when it hits the return statement, meaning that the second Console.WriteLine statement is not hit, and the phrase “This will not be written” isn’t written to the console.

          This is a fairly contrived example, but this pattern can come in very useful if you need to stop processing early, e.g.:

          This function writes the value passed in, followed either by “is even” or “is odd” depending on the value passed in. The interesting use of the return statement is that, if the value is even, we use a return statement to exit form the function early. This means we can omit the else clause from our if statement, safe in the knowledge that we’ll never print both “is even” and “is odd” for the same value. Choosing to return early instead of using an else statement can make your code easier to read by reducing the amount of indentation in the rest of your code.

          Unreachable Code Detected

          This is a compile time warning and it means that the compiler has found some code that is unreachable, there is no way that the code could be executed. This is usually caused by an early return statement, for example:

          Usually the issue is a lot more subtle that this. If you do ever get this warning it’s always worth digging in and finding out the problem, it could save you hours of debugging later.

          Return Multiple Values

          In C# a method/function can either one value or no values, it can’t return two or more values. However, there’s nothing in the rules to say that the value returned can’t itself be a group or list of things. Returning a tuple of values is the closest thing I know of to returning multiple values from a function in C#.

          Returning a Tuple

          My favourite way to return multiple things from a single function is to return them as a tuple. Tuples are a relatively new feature in C#, but are well worth learning about for situations such as these:

          Returning multiple values as a tuple has the advantage (over an array, or list) that the items do not need to be the same type:

          This is a contrived example (as you can get the name from the Color object) but there have been plenty of occasions where being able to return two or more things of different types from the same function has gotten me out of a jam.

          That said, this counts as a “code smell”, it often suggests you’re doing something wrong. either that you’re methods are not specific enough or that you should be passing around a class rather than a collection of objects. That said, it sometimes is appropriate to return a tuple, but consider getting someone with experience to review your code to make sure you’re not doing anything daft.

          Returning a List (or IEnumerable)

          Yield Return

          The above example (Returning a List) brings us nicely on to yield return. This lets us return a list (technically and IEnumerable) of objects, but with a much cleaner syntax, by yield returning each list item individually. This means we don’t have to declare a list variable and add to it, making the example much cleaner:

          I personally, love how neat the yield return version of this example is, and I try and use a yield return whenever it makes sense. Some IDEs like Visual Studio 2019 will prompt you to use yield return where appropriate.

          Other similar statements

          C# Break – leaving a loop

          In the same way that a return statement can be used to leave.a method/function, we can use a break statement to leave a loop, such as a while loop:

          This is a contrived example because we could have put our condition into the while() loop, but I hope you can see that the break statement causes us to leave the while loop when it gets executed. Unlike a return statement, we can’t include a value after a break.

          C # Continue – finishing one iteration of a loop

          What if you don’t want to exit a loop completely, but you do want to stop processing and move on to the next cycle in the loop? That’s where the continue statement comes in.

          In this example, hitting 40 doesn’t cause the while loop to stop running, but it does skip the rest of that iteration, meaning the yield return never runs for 40 and so 40 is never included in the output. If you’re not familiar with yield return, it might be worth re-reading that section above.

          What does “Not all code paths return a value” mean?

          This is a topic that deserves it’s own post, but for now I’ll explain that when a function is declared as returning a value, all paths through that code must return a value. If this rule is violated, you’ll get a compile time error:

          That was a pretty basic example (the function is empty, it’s clearly missing a return statement. But let’s take a look at a more involved example:

          This program calls itself recursively if the number passed in is even, but if the number is not even – what happens? execution continues to the end of the function, the ‘>’ without returning anything. This is a path through the code that doesn’t return a value, which is not allowed for a function that is declared as returning an int, so we get a compile time error.

          How do we fix it? It’s necessary to make sure that all paths through the function result in a value being returned:

          It’s worth noting that this doesn’t make sure your program will always return a value. In fact, the Collatz conjecture (that every positive integer plugged into the above recursive function will always end up returning 1) is, as yet, unproven; so it’s conceivable to enter a value that ends up with the program never stopping (or more likely overflowing). What the compile time check is doing is ensuring there are no obvious gaps in your code where you’ve forgotten to return a value, it leaves the rest up to you!

          Conclusion

          The return statement is an integral part of the C# programming language, we’ve seen to how to use it to leave a function, when it can be omitted, and how to use it to return values, how to return multiple values, what yield return does and when it’s useful, and much more besides. I hope you’ve enjoyed this deep dive into statement that most people take for granted, and I hope you learned something along the way.

          If you want a whole post on “Not all code paths return a value”, if something is still not clear to you, or you just want to show me some love, let me know in the comments!

          Leave a Reply Cancel reply

          About This Site

          I’m a Senior C# Developer at a hedge fund in London, UK. I started this blog to help others learn from my mistakes, while pushing the limits of my own knowledge. You can also expect some SQL and devops – particularly kubernetes.

          Урок №12. Функции

          Вы уже знаете, что каждая программа должна содержать функцию main() (с которой она и начинает свое выполнение). Тем не менее, большинство программ используют и много других функций.

          Функции

          Функция — это последовательность стейтментов для выполнения определенного задания. Часто ваши программы будут прерывать выполнение одних функций ради выполнения других. Вы делаете аналогичные вещи в реальной жизни постоянно. Например, вы читаете книгу и вспомнили, что должны были сделать телефонный звонок. Вы оставляете закладку в своей книге, берете телефон и набираете номер. После того, как вы уже поговорили, вы возвращаетесь к чтению: к той странице, на которой остановились.

          Программы на языке C++ работают похожим образом. Иногда, когда программа выполняет код, она может столкнуться с вызовом функции. Вызов функции — это выражение, которое указывает процессору прервать выполнение текущей функции и приступить к выполнению другой функции. Процессор «оставляет закладку» в текущей точке выполнения, а затем выполняет вызываемую функцию. Когда выполнение вызываемой функции завершено, процессор возвращается к закладке и возобновляет выполнение прерванной функции.

          Функция, в которой находится вызов, называется caller, а функция, которую вызывают — вызываемая функция, например:

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

          Starting main()
          In doPrint()
          Ending main()

          Эта программа начинает выполнение с первой строки функции main(), где выводится на экран следующая строка: Starting main() . Вторая строка функции main() вызывает функцию doPrint(). На этом этапе выполнение стейтментов в функции main() приостанавливается и процессор переходит к выполнению стейтментов внутри функции doPrint(). Первая (и единственная) строка в doPrint() выводит текст In doPrint() . Когда процессор завершает выполнение doPrint(), он возвращается обратно в main() к той точке, на которой остановился. Следовательно, следующим стейтментом является вывод строки Ending main() .

          Обратите внимание, для вызова функции нужно указать её имя и список параметров в круглых скобках () . В примере, приведенном выше, параметры не используются, поэтому круглые скобки пусты. Мы детально поговорим о параметрах функций на следующем уроке.

          Правило: Не забывайте указывать круглые скобки () при вызове функций.

          Возвращаемые значения

          Когда функция main() завершает свое выполнение, она возвращает целочисленное значение обратно в операционную систему, используя оператор return.

          Функции, которые мы пишем, также могут возвращать значения. Для этого нужно указать тип возвращаемого значения (или «тип возврата»). Он указывается при объявлении функции, перед её именем. Обратите внимание, тип возврата не указывает, какое именно значение будет возвращаться. Он указывает только тип этого значения.

          Затем, внутри вызываемой функции, мы используем оператор return, чтобы указать возвращаемое значение — какое именно значение будет возвращаться обратно в caller.

          Рассмотрим простую функцию, которая возвращает целочисленное значение:

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

          Первый вызов функции return7() возвращает 7 обратно в caller, которое затем передается в std::cout для вывода.

          Второй вызов функции return7() опять возвращает 7 обратно в caller. Выражение 7 + 3 имеет результат 10 , который затем выводится на экран.

          Третий вызов функции return7() опять возвращает 7 обратно в caller. Однако функция main() ничего с ним не делает, поэтому ничего и не происходит (возвращаемое значение игнорируется).

          Примечание: Возвращаемые значения не выводятся на экран, если их не передать объекту std::cout. В последнем вызове функции return7() значение не отправляется в std::cout, поэтому ничего и не происходит.

          Тип возврата void

          Функции могут и не возвращать значения. Чтобы сообщить компилятору, что функция не возвращает значение, нужно использовать тип возврата void. Взглянем еще раз на функцию doPrint() из вышеприведенного примера:

          Эта функция имеет тип возврата void, который означает, что функция не возвращает значения. Поскольку значение не возвращается, то и оператор return не требуется.

          Вот еще один пример использования функции типа void:

          В первом вызове функции returnNothing() выводится Hi! , но ничего не возвращается обратно в caller. Точка выполнения возвращается обратно в функцию main(), где программа продолжает свое выполнение.

          Второй вызов функции returnNothing() даже не скомпилируется. Функция returnNothing() имеет тип возврата void, который означает, что эта функция не возвращает значения. Однако функция main() пытается отправить это значение (которое не возвращается) в std::cout для вывода. std::cout не может обработать этот случай, так как значения на вывод не предоставлено. Следовательно, компилятор выдаст ошибку. Вам нужно будет закомментировать эту строку, чтобы компиляция прошла успешно.

          Возврат значений функцией main()

          Теперь у вас есть понимание того, как работает функция main(). Когда программа выполняется, операционная система делает вызов функции main() и начинается её выполнение. Стейтменты в main() выполняются последовательно. В конце функция main() возвращает целочисленное значение (обычно 0 ) обратно в операционную систему. Поэтому main() объявляется как int main() .

          Почему нужно возвращать значения обратно в операционную систему? Дело в том, что возвращаемое значение функции main() является кодом состояния, который сообщает операционной системе об успешном или неудачном выполнении программы. Обычно, возвращаемое значение 0 (ноль) означает что всё прошло успешно, тогда как любое другое значение означает неудачу/ошибку.

          Обратите внимание, по стандартам языка C++ функция main() должна возвращать целочисленное значение. Однако, если вы не укажете return в конце функции main(), компилятор возвратит 0 автоматически, если никаких ошибок не будет. Но рекомендуется указывать return в конце функции main() и использовать тип возврата int для функции main().

          Еще о возвращаемых значениях

          Во-первых, если тип возврата функции не void, то она должна возвращать значение указанного типа (использовать оператор return). Единственно исключение — функция main(), которая возвращает 0 , если не предоставлено другое значение.

          Во-вторых, когда процессор встречает в функции оператор return, он немедленно выполняет возврат значения обратно в caller и точка выполнения также переходит в caller. Любой код, который находится за оператором return в функции — игнорируется.

          Функция может возвращать только одно значение через return обратно в caller. Это может быть либо число (например, 7 ), либо значение переменной, либо выражение (у которого есть результат), либо определенное значение из набора возможных значений.

          Но есть способы обойти правило возврата одного значения, возвращая сразу несколько значений, но об этом детально мы поговорим на соответствующем уроке.

          Наконец, автор функции решает, что означает её возвращаемое значение. Некоторые функции используют возвращаемые значения в качестве кодов состояния для указания результата выполнения функции (успешно ли выполнение или нет). Другие функции возвращают определенное значение из набора возможных значений. Кроме того, существуют функции, которые вообще ничего не возвращают.

          Повторное использование функций

          Одну и ту же функцию можно вызывать несколько раз, даже в разных программах, что очень полезно:

          Функции. Передача аргументов по значению и по ссылке

          Язык 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 содержит адрес):

          Кроме того, следует знать, что функция может возвращать адрес.

          Важно понять механизм так называемой передачи аргументов по ссылке, т.к. это понимание пригодится при изучении массивов и строк. Использовать указатели при работе с простыми типами данных не стоит. Лучше возвращать из функции значение, чем менять локальные переменные одной функции с помощью кода другой функции. Функции должны быть достаточно автономными.

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

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