Глава 12
В этой главе вы узнаете...
Visual C++ позволяет разбивать программу на более мелкие составляющие путем объединения логически связанных между собой выражений и присвоения им какого-то имени. Такие выделенные в отдельную группу выражения называются функциями. (Иногда функции называют подпрограммами или процедурами. В данной книге в основном будет использоваться термин функция, однако другие два термина также корректны.)
Функции могут иметь различную область видимости. Глобальные функции могут быть вызваны из любой части программы. Библиотечные функции могут вызываться самыми разными программами. Однако большинство создаваемых вами функций будут, скорее всего, использоваться в пределах только одного объекта. Такие функции, называемые функциями-членами, подробно рассматриваются в главе 17.
Можно также использовать ранее созданные функции для построения новой функции.
Этот прием позволяет значительно упростить процесс написания, чтения и редактирования кодов программ. В этой главе описывается, как использовать функции для разбивки большой программы на небольшие и понятные составляющие.
Если вы просматривали предыдущие главы, то наверняка обратили внимание на приводимые в качестве примеров коды программ, В главе 3 упоминалось правило, согласно которому все выражения должны заканчиваться точкой с запятой (;). Но как известно, из любого правила есть исключения, и подтверждением тому могут быть некоторые выражения в приводимых ранее примерах, после которых точка с запятой не ставилась.
Итак, общее правило звучит так:
Вверх
Определение функции начинается с набора ее имени и пары круглых скобок (). (Позже, когда вы познакомитесь с аргументами, в упомянутых скобках можно будет определить список этих самых аргументов.) Далее следуют выражения, из которых собственно и состоит функция.
Правила присвоения функциям имен в точности совпадают с правилами присвоения имен переменным, которые были рассмотрены в главе 8. Итак, вот код, которым определяется функция:
Например, следующим кодом создается функция, отображающая на экране текст "Hello
World".
Теперь каждый раз, когда вы хотите, чтобы функция была выполнена, наберите ее имя и сразу за ним пару пустых скобок. Это действие называется вызовом функции. Функция может быть вызвана неограниченное количество раз.
#include "stdafx.h"
//Hang out until the user is finished
// This is the entry point for this application
//Hang out until the user is finished
Вверх
Функция может требовать для своего выполнения передачи ей значений аргументов. Для каждого аргумента (их называют также параметрами) должно быть определено имя и тип данных. Функции, работа которых зависит от передаваемых значений аргументов, более универсальны и могут многократно использоваться в процессе выполнения программы. Для каждой функции можно определить любое количество аргументов любого типа данных.Вот как определяются аргументы:
Например, приведенная ниже функция вычисляет факториал числа. Число, представляемое переменной n, передается функции в качестве значения аргумента. Далее это значение используется функцией при проведении вычислений.
Каждый раз, когда потребуется отобразить на экране значение факториала какого-нибудь числа, просто вызовите эту функцию. Например, ниже приведен код цикла, выполняющего три итерации. На каждой итерации программа запрашивает число, для которого нужно вычислить факториaл, и затем вызывает функцию Factorial , которая отображает нужное значение на экране.
//Вызов функции с передачей ей значения аргумента
Так же просто создается функция, требующая значений нескольких аргументов.
Например, следующая функция отображает результат, вычисленный по формуле foo*n!, где значения переменных fоо и n являются аргументами этой функции:
Вверх
Все функции, рассмотренные ранее, выполняли какие-то действия (например, вычисляли факториал и отображали полученное значение на экране). Однако функции могут также возвращать некоторое значение в качестве собственного результата. Эта возможность очень полезна, поскольку позволяет использовать функции при построении выражений. Например, можно использовать математические библиотечные функции, такие как Math :: Cos(), внутри выражений, подобных этому: 3*Math ::Cos(angle).
Вы можете создавать собственные функции, возвращающие результат. Например, можете создать функцию, просматривающую базу данных и возвращающую имя клиента, совершившего наибольшее количество покупок. Или можете создать функцию, возвращающую в качестве результата общую сумму совершенных за последний месяц сделок.
Чтобы функция возвращала какой-то результат, нужно выполнить два условия:
Ниже приведен пример функции, вычисляющей факториал и возвращающей его в качестве своего значения. Эта функция работает так же, как и предыдущая, за исключением того, что полученное значение не отображается на экране, а возвращается как результат функции.
Поскольку теперь функция Factorial возвращает результат, вы можете использовать ее при построении выражений. В некоторых случаях это может быть очень полезной возможностью. Вот, например, код, где функция Factorial вызывается функцией WriteLine, которая сразу же отображает полученное значение на экране:
Console::WriteLine(S"Факториал числа
{0} равен
{1}",
Функции, возвращающие значения, могут быть использованы в любом месте, где могут
Возвращаемые функциями результаты могут также быть использованы как значения аргументов, передаваемые другим функциям. Например, чтобы вызвать функцию Factorial, ей нужно передать в качестве аргумента числовое значение типа integer. Поскольку сама функция также возвращает значение типа integer, оно может быть использовано и как аргумент этой функции. Ниже показан код, в результате выполнения которого на экране отображается значение факториала от факториала числа.
А этим кодом определяется, будет ли факториал заданного числа больше числа 72:
Вверх
После того как функция завершает свою работу, все значения всех переменных, объявленных внутри этой функции, теряются. После этого восстановить информацию, которая хранилась как значения этих переменных, уже невозможно. Если же она вам еще нужна, ее необходимо вернуть с использованием слова return как результат этой функции.
Предположим, например, что вам нужно узнать имя самого высокооплачиваемого сотрудника вышей opганизации. Если вы хотите только отобразить это имя на экране и больше никогда к нему не возвращаться, наберите команду вывода на экран внутри функции и не возвращайте никакого результата. Но, если это имя должно быть как-то использовано за пределами функции, например при создании какого-нибудь отчета, верните его как результат функции.
Сохранить информацию можно и без использования ключевого слова return . Делается это с помощью так называемых глобальных переменных, которые объявляются до начала работы функции main . Свое название эти переменные получили благодаря возможности их использования в любом месте программы. Если вернуться к нашему примеру, то имя самого высокооплачиваемого сотрудника может быть сохранено как значение одной из глобальных переменных. Значения глобальных переменных не пропадают после завершения работы какой-либо отдельной части программы, поэтому они могут использоваться для сохранения информации, полученной во время выполнения функции.
Однако, к сожалению, использование глобальных переменных может сделать коды программы похожими на спагетти (так называют коды, которые трудно прочитать и понять, как они работают). Если вы будете использовать глобальные переменные внутри функций, понять, что произойдет в результате выполнения кодов программы, будет крайне сложно, так как вам придется просматривать подряд все коды программы. Это нельзя считать хорошей практикой, так как в идеале вы должны без просмотра каждой отдельной строки функции суметь точно определить, значения каких аргументов ей нужно передать и что произойдет в результате ее выполнения. Это важно, поскольку в этом случае вы можете понимать назначение и
использовать функции без необходимости вникать во все нюансы выполнения отдельных кодов.
Большинство сложных для обнаружения логических ошибок возникают из-за неаккуратного использования глобальных переменных. Поэтому намного предпочтительнее возвращать результат функции, чем использовать внутри нее глобальные переменные. Если необходимо вернуть много значений, воспользуйтесь структурой или ссылкой на структуру.
Вверх
В этом разделе мы вновь вернемся к программе, вычисляющей факториал заданного числа. Но теперь программа будет состоять из нескольких функций. Когда будете просматривать коды программы, обратите внимание, что, хотя теперь она выглядит несколько сложнее, коды функции main стали намного проще. Ведь теперь они состоят всего лишь из четырех выражений (если не считать комментариев).
В программе кроме функции main используются еще две функции: Factorial и
GetNumber. Функция Factorial вычисляет факториал заданного числа. Она принимает в качестве аргумента число, для которого нужно вычислить факториал, и возвращает в качестве результата факториал этого числа. Функция GetNumber используется для получения от пользователя числа, для которого нужно вычислить факториал.
Функция main снова и снова использует функцию GetNumber для получения от пользователя новых чисел. Получив очередное число, функция main вызывает функцию
Factorial для вычисления факториала и отображения его на экране. Это продолжается до тех пор, пока пользователь не наберет число 0 (нуль).
Обратите внимание функция main определяет, когда нужно остановиться:
Помните, что цикл while использует выражение как условие для выполнения или невыполнения следующей итерации. Итерация выполняется, если проверяемое выражение истинно. В нашем случае выражение вызывает вначале функцию GetNumber для получения числа от пользователя. Затем полученное число присваивается переменной nNumber. Далее возможны два варианта. Если пользователь набирает число 0, выполнение цикла прекращается, поскольку этому числу соответствует логическое значение false и выражение воспринимается как ложное. Если же пользователь набирает любое положительное число, оно присваивается переменной nNumber и выполняется итерация цикла. Этот способ использования цикла while довольно часто можно видеть в программах C++. (Если вы тестируете программу или просто любите создавать проблемы, можете ввести отрицательное число, тогда
программа выдаст неправильный результат. Чтобы избежать этого, можете добавить код, который будет проверять вводимые пользователем числа и просить ввести число заново, если оно окажется отрицательным.)
Итак, вот новый код программы, вычисляющей факториал:
//Функция вычисляет и возвращает значение факториала
//Функция просит пользователя набрать число,
//С этой точки начинается выполнение программы
Вверх
Если программа содержит функции, они обычно определяются до того, как будут использованы. Поэтому, если вы начнете читать коды программы от начала и до конца строка за строкой, вам вначале придется проштудировать множество разрозненных деталей и только в конце увидеть, для чего они используются и в какой последовательности выполняются.
Приведем несколько советов, которые могут заметно упростить этот процесс.
Если вас удивляет тот факт, что переменным внутри функций присваиваются те же имена, что и переменным, используемым за пределами этих функций, не переживайте - в главе 15 этот вопрос рассмотрен подробно.
Вверх
Если функция вызывает саму себя, это называется рекурсией. Рекурсия очень часто применяется в тех случаях, если одну и ту же задачу намного проще выполнить для меньшего числа исходных элементов.
Предположим, например, что необходимо отсортировать большой набор чисел.
Это
классическая задача, часто рассматриваемая в книгах по программированию. Возможно, предлагаемое здесь решение с помощью использования рекурсии вам покажется не вполне очевидным, но, в отличие от реальной жизни, в контексте программирования именно этот подход считается одним из самых оптимальных.) Отсортировать большой набор чисел крайне сложно. Самый простой способ сделать это — просмотреть весь список и найти наименьшее число, перенести его в новый список, который будет итоговым, и затем продолжать этот процесс до тех пор, пока все числа не будут перенесены в этот новый список. Недостаток метода состоит в том, что один и тот же список приходится просматривать снова и снова, а это
занимает много времени. Так что проблема сортировки чисел не так проста, как это может вначале показаться, и именно поэтому ей посвящены целые книги.
Чтобы ускорить процесс сортировки, используют рекурсию, разбивая при этом исходный большой список на более мелкие. Предположим, например, что вместо сортировки одного большого списка вам нужно правильно объединить вместе два небольших уже отсортированных списка. В действительности сделать это довольно просто.
Вы спросите, как это делается? Назовем список, который начинается с меньшего значения, списком А. Второй список назовем списком Б. Итоговый список назовем В. Объединить списки А и Б так, чтобы в итоговом списке все значения также были отсортированы, можно следующим образом. Возьмите первый (наименьший) элемент списка А и перенесите его в список В.
Затем сравните второй элемент списка А с первым элементом списка Б. Если он также будет меньше, чем наименьший элемент списка Б, перенесите его в список В. Продолжайте это до тех пор, пока элемент списка Б не окажется меньше, чем элемент списка А. Тогда перенесите в список В элемент списка Б. Теперь сравните следующий элемент списка Б с наименьшим (он будет наименьшим из оставшихся) элементом списка А и снова меньший из этих двух элементов перенесите в список В. Продолжайте этот процесс до тех пор, пока все элементы из списков А и Б
Теперь возникает вопрос, как из одного большого неотсортированного списка сделать два меньших, но отсортированных (чтобы потом получить один общий отсортированный список). Можно разбить большой список пополам и отсортировать каждую половину. Но как отсортировать половину списка? Ее можно также разбить пополам и отсортировать половину от половины. Если продолжать делить списки пополам, рано или поздно они будут разбиты на списки, состоящие из одного или двух элементов. Понятно, что такие списки отсортировать проще простого.
Задача вычисления факториала числа, которая рассматривалась ранее в главе, также иногда решается путем использования рекурсии. Приведенная ниже функция Factorial во многом похожа на свою предшественницу, но здесь для вычисления факториала вместо цикла for функция вызывает саму себя со значением аргумента, равным числу п-1. Другими словами, используется тот факт, что формулу вычисления факториала n ! =n* (n-1) * (n-2 ) . . .
//Функция вычисляет и возвращает значение факториала,
//Функция просит пользователя набрать число,
//С этой точки начинается выполнение программы
//Hang out until the user is finished
return 0;
Вверх
Чтобы определить, что функция может принимать любое количество параметров, наберите в списке аргументов три точки (...). Например, приведенный ниже код сообщает компьютеру, что функции может быть передано произвольное количество значений аргументов, и уже ее заботой будет определить их тип и разобраться, что делать с ними дальше.
Как правило, использовать при определении списка аргументов три точки (...) — плохая идея, поскольку в последующем вы случайно можете передать этой функции совершенно не те данные, которые ей нужны. Это может повлечь за собой самые непредсказуемые последствия. И хотя вы могли видеть, что в некоторых библиотечных функциях, используемых неуправляемыми программами, вместо списка аргументов фигурируют именно эти три точки, старайтесь не пользоваться этим приемом при создании собственных функций.
Вверх
Всем или некоторым аргументам функции можно присвоить значения, используемые по
Определение значений, используемых по умолчанию, полезно тогда, когда функции используют аргументы, значения которых имеет смысл специально указывать только в особых случаях. Тот, кто будет впоследствии вызывать эти функции, может спокойно проигнорировать аргументы с установленными по умолчанию значениями, что не приведет к сбою в работе этих функций. Однако, если в этом возникнет необходимость, таким аргументам можно передать нужные в данный момент значения.
Назад |
Начало урока |
Вверх |
Вперед
Создание функций
void functionname()
void PrintHW()
Console::WriteLine(S"Hello World");
Как и в случае со структурами, функции должны быть определены до того, как будут использованы, что показано в приведенном ниже примере. Это программа HelloWorld, которую вы уже видели ранее, но здесь код, заставляющий программу ожидать, пока пользователь не нажмет на клавишу
// HelloWorld4
// Uses a function for waiting for user input
#using <mscorlib.dll>
using namespace System;
void HangOut()
{
Console::ReadLine();
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
Console::WriteLine(L"Hello World");
HangOut();
return 0;
Использование аргументов
void function_name (data_ type1 arq1 , data_ type2 arg2, . . . )
//Вычисление и отображение факториала числа
void Factorial(int nNumber)
{
int i; //Используется для отсчета итераций
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for (i = 1; i<= nNumber; i++)
{
//Отображение полученного результата
Console::WriteLine(nResult.ToStting
());
int nNumber;
int i ;
//Цикл, выполняющий три итерации
for (i=0; i<3; i++)
{
Console::WriteLine("What is the number?");
nNumber = Int32 :: Parse (Console::ReadLine());
Factorial(nNumber);
//Функция, использующая значения двух аргументов
viod Fooctorial (int nFoo, int nNumber)
{
int i;
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for (i=1; i<=nNumber; i++)
{
//Умножение факториала на число foo
nResult *= nFoo;
//Отображение полученного результата
Console::WriteLine(nResult.ToString() ) ;
Функции, которые возвращают результат
который будет иметь возвращаемое функцией значение;
//Вычисление факториала числа
void Factorial (int nNumber)
{
int i; //Используется для отсчета итераций
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for (i=1; i<=nNumber; i++)
{
//Полученное значение возвращается как результат
return nResult;
nNumber = Int32::Parse(Console::ReadLine());
Factorial(nNumber).ToString());
быть использованы значения того же типа, что и возвращаемые функциями результаты. Так, если функция возвращает числовое значение типа integer, ее можно использовать в любом месте, где можно подставить число типа integer. Например, в таком выражении:
nMyNurober = 3*Factorial(nNumber);
nNumber = Int32::Parse(Console::ReadLine()) ;
Console::WriteLine(Factorial(Factorial(nNumber)).ToString());
if ((Factorial(nNumber) > 72)
{
Плюсы и минусы глобальных переменных
И снова вернемся к факториалам
while (nNumber = GetNumber())
//Вычисление факториала до тех пор, пока
//пользователь не наберет число 0
#include "stdafx.h"
#using <mscorlib.dil>
using manespace System;
int Factorial(int nNumber)
{
int i; //Используется для отсчета итераций
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for (i=l; i<=nNumber; i++)
{
//Возвращение результата
return nResult;
//а затем возвращает это число как результат
int GetNumber()
{
Console::WriteLine(S"Введите число");
nNumber = Int32::Parse(Console::ReadLine())
return nNumber;
#ifdef _UNICODE
int wmain (void)
else
int main (void)
#endif
{
получение чисел ст пользователя до тех пор,
//пока он не наберет число О
while (nNumber = GetNumber())
{
//числа. При этом используется значение,
//возвращаемое функцией Factorial
Console::WrineLine(S"Факториал числа
{0} равен
{nNumber.ToString(),
Factorial,(nNumber).ToString
());
//Окончание выполнения программы
Console::WriteLine(S"Bye");
return О;
Чтение программ, содержащих функции
Рекурсия: спасибо мне, что я есть у меня
не будут перенесены в список В. Возможно, на бумаге этот путь кажется несколько запутанным, но поверьте, что это намного быстрее и эффективнее, чем снова и снова просматривать один и тот же список. Попробуйте реализовать этот процесс на компьютере, и вы в этом убедитесь.
Далее, имея отсортированные списки из одного-двух элементов, двигайтесь в обратном направлении, объединяя списки так, чтобы порядок сортировки не нарушался. На последнем шаге у вас будут два больших отсортированных списка, объединив которые вы получите необходимый результат. Таким образом, используя рекурсию, вы упрощаете задачу, разбивая ее на небольшие подобные подзадачи.
Продемонстрируем сказанное на маленьком примере.
Ура! Получилось!
1 3 7 5 9 2 7
1375 927
13 75 92 7
13 5 7 29 7
13 57 279
1 2 3 5 7 7 9
Код, реализующий этот процесс, может выглядеть приблизительно так:
СписокЧисел
Sort (СписокЧисел)
{
{
if (КоличествоЭлементов(СписокЧисел) == 2)
{
return СортированныйСписок;
//Если обрабатывается этот код, значит, список
//состоит из более чем двух элементов. Он разбивается
//пополам, и функция Sort вызывается снова
Объединить(Sort(первая половина списка чисел),
Sort(первая половина списка чисел));
можно представить в виде n! = n*((n-1)!).
Разумеется, (n-1)! равен (n-1)*((n-2)!) . Таким образом, функция, вычисляющая факториал для какого-то числа, может делать это путем умножения этого числа на результат, возвращаемый ею же для числа, на единицу меньшего.
//Использование рекурсии для вычисления факториала
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
//используя при этом метод рекурсии.
//Факториал числа 1 равен 1. Это просто.
//Если число не равно 1, вызывается функция Factorial
//для числа, меньшего на единицу
int Factorial(int nNumber)
{
//а затем возвращает это число как результат
int GetHumber()
{
Console::WriteLine(S"Введите число");
nNumber = Int32::Parse(Console::ReadLine())
return nNumber;
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
//Получение чисел от пользователя до тех пор,
//пока он не наберет число 0
while (nNumber = GetNumber())
{
//числа. При этом используется значение,
//возвращаемое функцией Factorial
Console::WriteLine(S"Факториал числа
{0} равен
{1}",
nNumber.ToString(),
Console::WriteLine(S"Bye");
Console::WriteLine(S"Hit the enter key to stop the program");
Console::ReadLine();
Если тип аргументов не определен . . .
int factorial (...)
Значение, установленные по умолчанию
умолчанию. Предположим, например, что есть функция, названная именем foo, для работы которой требуются значения трех аргументов типа integer: a, b и с. Предположим также, что почти при каждом вызове функции в использовании аргумента с не будет необходимости. В этом случае уместно присвоить аргументу с значение, используемое по умолчанию. Это значение будет использоваться каждый раз, когда при вызове функции аргументу с не будет передаваться никакое другое значение. Другими словами, вызывая функцию, вы можете набрать foo ( 1 , 2 ) ; это будет означать, что аргументу а присваивается значение 1, аргументу b — значение 2, а аргумент с будет использовать значение, присвоенное по умолчанию. Если
же вы наберете fоо (1, 2, 3) , это будет означать, что в данном случае а = 1, b = 2, а с = 3.
Чтобы присвоить значения, используемые по умолчанию, укажите их при определении
функции в списке аргументов:
int too(int a, int b, int с = 3)
Содержание