Возвращаемые значения, параметры и аргументы

Функции могут возвращать значения. После обращения к функции она может выполнить некоторые действия, а затем в качестве результата своей работы послать назад некоторое значение. Оно называется возвращаемым значением, причем тип этого значения обязательно должен быть объявлен. Таким образом, запись

int myFunction();

объявляет, что функция myFunction возвращает целочисленное значение.

В функцию можно также и посылать некоторые значения. Описание посылаемых значений называется списком параметров.

int myFunction(int someValue, float someFloat);

Это объявление означает, что функция myFunction не только возвращает целое число, но и принимает два значения в качестве параметров: целочисленное и вещественное.

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

int theValueReturned = myFunction(5,6.7);

Здесь целая переменная theValueReturned инициализируется значением, возвращаемым функцией myFunction, и что в качестве аргументов этой функции передаются значения 5 и 6,7. Тип аргументов должен соответствовать объявленным типам параметров.

Объявление и определение функций

Использование функций в программе требует, чтобы функция сначала была объявлена, а затем определена. Посредством объявления функции компилятору сообщается ее имя, тип возвращаемого значения и параметры. Благодаря определению функции компилятор узнает, как функция работает. Ни одну функцию нельзя вызвать в программе, если она не была предварительно объявлена. Объявление функции называется прототипом.

Объявление функции

Существует три способа объявления функции.

• Запишите прототип функции в файл, а затем используйте выражение с #include, чтобы включить его в свою программу.

• Запишите прототип функции в файл, в котором эта функция используется.

• Определите функцию перед тем, как ее вызовет любая другая функция. В этом случае определение функции одновременно и объявляет ее.

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

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

Во-вторых, вполне возможна ситуация, когда функции A() необходимо вызвать функцию B(), но не исключено также, что при некоторых обстоятельствах и функции B() потребуется вызвать функцию A(). Однако невозможно определить функцию A() до определения функции B() и в то же время функцию B() до определения функции A(), т.е. по крайней мере одна из этих функций обязательно должна быть предварительно объявлена.

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

Прототипы функций

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

Рис. 5.2. Составные части прототипа функции.

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

Список формальных параметров представляет собой список всех параметров и их типов, разделенных запятыми. Составные части прототипа функции показаны на рис. 5.2.

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

long Area(int, int);

Этот прототип объявляет функцию с именем Area(), которая возвращает значение типа long и принимает два целочисленных параметра. И хотя такая запись прототипа вполне допустима, это не самый лучший вариант. Добавление имен параметров делает ваш прототип более ясным. Та же самая функция, но уже с именованными параметрами, выглядит гораздо понятнее:

long Area(int length, int width);

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

Листинг 5.1. Объявление, определение и использование функции

1: // Листинг 5.1. Использование прототипов функций

2:

3: #include <iostream.h>

4: int Area(int length, int width); //прототип функции

5:

6: int main()

7: {

8: int lengthOfYard;

9: int widthOfYard;

10: int areaOfYard;

11:

12: cout << "\nHow wide is your yard? ";

13: cin >> widthOfYard;

14: cout << "\nHow long is your yard? ";

15: cin >> lengthOfYard;

16:

17: areaOfYard= Area(lengthOfYard,widthOfYard);

18:

19: cout << "\nYour yard is ";

20: cout << areaOfYard;

21: cout << " square feet\n\n";

22: return 0;

23: }

24:

25: int Area(int yardLength', int yardWidth)

26: {

27: return yardLength * yardWidth;

28: }

Результат:

How wide is your yard? 100

How long is your yard? 200

Your yard is 20000 square feet

Анализ: Прототип функции Area() объявляется в строке4. Сравните прототип с определением функции, представленным в строке 25. Обратите внима­ние, что их имена, типы возвращаемых значений и типы параметров полностью сов­падают. Если бы они были различны, то компилятор показал бы сообщение об ошиб­ке. Единственное обязательное различие между ними состоит в том, что прототип функции оканчивается точкой с запятой и не имеет тела.

Обратите также внимание на то, что имена параметров в прототипе — length и width — не совпадают с именами параметров в определении: yardLength и yardWidth. Как упоминалось выше, имена в прототипе не используются; они просто служат опи­сательной информацией для программиста. Соответствие имен параметров прототипа именам параметров в определении функции считается хорошим стилем программиро­вания; но это не обязательное требование.

Аргументы передаются в функцию в порядке объявления и определения парамет­ров, но без учета какого бы то ни было совпадения имен. Если в функцию Area() первым передать аргумент widthOfYard, а за ним — аргумент lengthOfYard, то эта функ­ция использует значение widthOfYard для параметра yardLength, а значение lengthOfYard — для параметра yardWidth. Тело функции всегда заключается в фигурные скобки, даже если оно состоит только из одной строки, как в нашем примере.

Определение функции

Определение функции состоит из заголовка функции и ее тела. Заголовок подобен прототипу функции за исключением того, что параметры в данном случае именованные и в конце заголовка отсутствует точка с запятой.

Рис. 5.3. Заголовок и тело функции

Тело функции представляет собой набор выражений, заключенных в фигурные скобки. Заголовок и тело функции показаны на рис. 5.3.

Синтаксис прототипа функции:

тип_возврата имя_функции ([тип [имя_параметра]...]);

{

выражения;

}

Прототип функции сообщает компилятору тип возвращаемого значения, имя функции и список параметров. Наличие параметров не обязательно, но если они все-таки имеются, в прототипе должны быть объявлены их типы. Имена параметров перечислять необязательно. Строка прототипа всегда оканчивается точкой с запятой (;). Определение функции должно соответствовать своему прототипу по типу возвращаемого значения и списку параметров. Оно должно содержать имена всех параметров, а тело определения функции должно заключаться в фигурные скобки. Все выражения внутри тела функции должны оканчиваться точкой с запятой, кроме заголовка функции. Который оканчивается закрывающей круглой скобкой.

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

Для каждой функции задается тип возвращаемого значения. Если он явно не определен. по умолчанию устанавливается тип возврата lnt. Старайтесь всегда указывать тип возвращаемого значения в явном виде. Если функция не возвращает никакого значения, то в качестве типа возвращаемого значения используйте void.

Примеры прототипов функций:

long FindArea(long length, long width); // возвращает значение типа long, имеет два параметра

void PrintMessage(int messageNumber); // возвращает значение типа void, имеет один параметр

int GetChoice(); // возвращает значение типа int, не имеет параметров

BadFunction(); // возвращает значение типа int, не имеет параметров

Примеры определений функций:

long FindArea(long l, iong w)

{

return 1 * w;

}

void PrintMessage(int whichMsg)

{

if (whichMsg == 0)

cout << "Hello.\n";

if (whichMsg == 1)

cout << "Goodbye.\n";

if (whlchMsg > 1)

cout << "I'm confused.\n";

}

Выполнение функций

При вызове функции ее выполнение начинается с выражения, которое стоит пер­вым после открывающей фигурной скобки ({). В теле функции можно реализовать ветвление, используя условный оператор if (и некоторые другие операторы, которые рассматриваются на занятии 7). Функции могут также вызывать другие функции и даже самих себя (о рекурсии речь пойдет ниже в этой главе).

Локальные переменные

В функции можно не только передавать значения переменных, но и объявлять пе­ременные внутри тела функции. Это реализуется с помощью локальных переменных, которые называются так потому, что существуют только внутри самой функции. Ко­гда выполнение программы возвращается из функции к основному коду, локальные переменные удаляются из памяти.

Локальные переменные определяются подобно любым другим переменным. Пара­метры, переданные функции, гоже считаются локальными переменными, и их можно использовать как определенные внутри тела функции. В листинге 5.2 представлен пример использования параметров функции и переменных, локально определенных внутри функции.

Листинг 5.2. Использование локальных переменных u параметров функции

1: #include <iostream.h>

2:

3: float Convert(float);

4: int main()

5: {

6: float TempFer;

7: float TempCel;

8:

9: cout << "Please enter the temperature in Fahrenheit: ";

10: cin >> TempFer;

11: TempCel = Convert(TempFer);

12: cout << "\nHere's the temperature in Celsius: ";

13: cout << TempCel << endl;

14: return 0;

15: }

16:

17: float Convert(float TempFer)

18: {

19: float TempCel;

20: TempCel = ((TempFer - 32) * 5) / 9;

21: return TempCel;

22: }

Результат:

Please enter the temperature in Fahrenheit: 212

Here's the temperature in Celsius: 100

Please enter the temperature in Fahrenheit: 32

Here's the temperature in Celsius: 0

Please enter the temperature in Fahrenheit: 85

Here's the temperature in Celsius: 25.4444

Анализ: В строках 6 и 7 объявляются две переменные типа float: одна (TempFer) для хранения значения температуры в градусах по Фаренгейту, а другая (TempCel) — в градусах по Цельсию. В строке 9 пользователю предлагается ввести температуру по Фаренгейту, и это значение затем передается функции Convert().

После вызова функции Convert() программа продолжает выполнение с первого выражения в теле этой функции, представленного строкой 19, где объявляется локальная переменная, также названная TempCel. Обратите внимание, что эта локальная переменная — не та же самая переменная TempCel, которая объявлена в строке 7. Эта переменная существует только внутри функции Convert(). Значение, переданное в качестве параметра TempFer, также является лишь переданной из функции main() локальной копией одноименной переменной.

В функции Convert() можно было бы задать параметр FerTemp и локальную переменную CelTemp, что не повлияло бы на работу программы. Чтобы убедиться в этом, можете ввести новые имена и перекомпилировать программу.

Локальной переменной TempCel присваивается значение, которое получается в результате выполнения следующих действий: вычитания числа 32 из параметра TempFer, умножения этой разности на число 5 с последующим делением на число 9. Результат вычислений затем возвращается в качестве значения возврата функции, и в строке 11 оно присваивается переменной TempCel функции main(). В строке 13 это значение выводится на экран.

В нашем примере программа запускалась трижды. В первый раз вводится значение 212, чтобы убедиться в том, что точка кипения воды по Фаренгейту (212) сгенерирует правильный ответ в градусах Цельсия (100). При втором испытании вводится значение точки замерзания воды. В третий раз — случайное число, выбранное для получения дробного результата.

В качестве примера попробуйте запустить программу снова с другими именами переменных, как показано ниже.

Должен получиться тот же результат.

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

Обычно область видимости переменных очевидна по месту их объявления, но некоторые исключения все же существуют. Подробнее об этом вы узнаете при рассмотрении циклов в занятии 7.

1: #include <iostream.h>

2:

3: float Convert(float);

4: int main()

5: {

6: float TempFer;

7: float TempCel;

8:

9: cout << "Please enter the temperature in Fahrenheit: ";

10: cin >> TempFer;

11: TempCel = Convert(TempFer);

12: cout << "\nHere's the temperature in Celsius: ";

13: cout << TempCel << endl;

14: return 0;

15: }

16:

17: float Convert(float Fer)

18: {

19; float Cel;

20; Cel = ((Fer - 32) * 5) / 9;

21: return Cel;

22: }

Обычно с использованием переменных в функциях не возникает больших проблем, если ответственно подходить к присвоению имен и следить за тем, чтобы в пределах одной функции не использовались одноименные переменные.

Глобальные переменные

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

Локальные переменные, имена которых совпадают с именами глобальных переменных, не изменяют значений последних. Однако такая локальная переменная скрывает глобальную переменную. Если в функции есть переменная с тем же именем, что и у глобальной, то при использовании внутри функции это имя относится к локальной переменной, а не к глобальной. Это проиллюстрировано в листинге 5.3.

Листинг 5.3. Демонстрация использования ело глобальных и локальных переменных

1: #include <iostream.h>

2: void myFunction(); // прототип

3:

4: int x = 5, у = 7; // глобальные переменные

5: int main()

6: {

7:

8: cout << "x from main: " << x << "\n";

9: cout << "у from main; " << у << "\n\n";

10: myFunction();

11: cout << "Back from myFunction!\n\n";

12: cout << "x from main: " << x << '\n";

13: cout << "y from main: " << y << "\n";

14: return 0;

15: }

16:

17: void myFunction()

18: {

19: int y = 10;

20:

21: cout << "x from myFunction: " << x << "\n";

22: cout << "y from myFunction: " << y << "\n\n";

23: }

Результат:

x from main: 5

y from main: 7

x from myFunction: 5

y from myFunction: 10

Back from myFunction!

x from main: 5

y from main: 7

Анализ: Эта простая программа иллюстрирует несколько ключевых моментов, связанных с использованием локальных и глобальных переменных, на которых часто спотыкаются начинающие программисты. В строке 4 объявляются две глобальные переменные — x и у. Глобальная переменная x инициализируется значением 5, глобальная переменная у — значением 7.

В строках 8 и 9 в функции main() эти значения выводятся на экран. Обратите внимание, что хотя эти переменные не объявляются в функции main(), они и так доступны, будучи глобальными.

При вызове в строке 10 функции myFunction() выполнение программы продолжается со строки 18, а в строке 19 объявляется локальная переменная у, которая инициализируется значением 10. В строке 21 функция myFunction() выводит значение переменной x. При этом используется глобальная переменная x, как и в функции main(). Однако в строке 22 при обращении к переменной у на экран выводится значение локальной переменной у, в то время как глобальная переменная с таким же именем оказывается скрытой.

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

Наши рекомендации