Теоретический материал (С++)
Функции (часть 1)
В предыдущем листке была задача вычисления числа сочетаний из n элементов по k, для чего необходимо вычисление факториалов трех величин: n, k и n-k. Для этого можно сделать три цикла, что приводит к увеличению размера программы за счет трехкратного повторения похожего кода. Вместо этого лучше сделать одну функцию, вычисляющую факториал любого данного числа n и трижды использовать эту функцию в своей программе. Соответствующая функция может выглядеть так:
int factorial (int n)
{
int f=1,i;
for(i=2;i<=n;++i)
{
f=f*i;
}
return f;
}
Этот текст должен идти до основной программы, то есть до функции main().
Первая строчка этого примера является описанием нашей функции. factorial – идентификатор,
то есть имя нашей функции. После идентификатора в круглых скобках идет список параметров,
которые получает наша функция. Список состоит из перечисленных через запятую типов параметров и их
идентификаторов. В нашем случае список состоит из одной величины n, имеющей тип int:
наша функция вычисляет факториал целого числа. Функция вычисляет целочисленную величину,
поэтому функция будет возвращать значение типа int, что указывается до идентификатора функции.
Функция может не возвращать никакого значения, тогда в качестве типа возвращаемого значения должно быть
указано слово void.
Далее идет тело функции в фигурных скобках. Внутри функции вычисляется значение факториала числа n
и оно сохраняется в переменной f. Функция завершается инструкцией return f, которая
завершает работу функции и возвращает значение переменной f. Инструкция return может встречаться
в произвольном месте функции, ее исполнение завершает работу функции и возвращает указанное значение в место вызова.
Если функция не возвращает значения, то инструкция return используется без возвращаемого значения, также
в функциях, не возвращающих значения, инструкция return может отсутствовать.
Функция должна быть описана до начала основной программы. Сама же основная программа, как можно догадаться,
также является функцией с именем main, не получающая никаких параметров и возвращающее значение типа int.
Теперь мы можем использовать нашу функцию factorial в основной программе нахождения числа сочетаний:
int main ()
{
int n,k;
cin>>n>>k;
cout<<factorial(n)/(factorial(k)*factorial(n-k))<<endl;
return 0;
}
В этом примере мы трижды вызываем функцию factorial для вычисления трех факториалов:
factorial(n), factorial(k), factorial(n-k).
Мы также можем объявить функцию binomial, которая принимает два целочисленных параметра
n и k и вычисляет число сочетаний из n по k:
int binomial (int n, int k)
{
return factorial(n)/(factorial(k)*factorial(n-k));
}
Тогда в нашей основной программе мы можем вызвать функцию binomial для нахождения числа сочетаний:
cout<<binomial(n,k)<<endl;
Поскольку в этом случае функция main вызывает функцию binomial, а функция binomial
вызывает функцию factorial, а каждая функция должна быть описана до ее вызова из другой функции,
то порядок описания функций в программе должен быть такой:
int factorial (int n)
int binomial (int n, int k)
int main ()
Вернемся к задаче нахождения наибольшего из двух или трех чисел. Напишем функцию, находящую максимум из двух данных чисел:
double max (double a, double b)
{
if (a>b)
return a;
else
return b;
}
Теперь мы можем реализовать функцию max, находящую максимум трех чисел:
double max (double a, double b, double c)
{
return max( max(a,b), c);
}
В данном примере написаны две различные функции max: первая с двумя параметрами,
вторая с тремя параметрами. Несмотря на то, что функции имеют одинаковые имена, по количеству
передаваемых параметров ясно, какая из них имеется в виду. В нашем случае функция
max (double a, double b, double c) дважды вызывает функцию max для двух чисел:
сначала, чтобы найти максимум из a и b, потом чтобы найти максимум из этой
величины и c.
Упражнения
- (A) Напишите функцию
int min (int a, int b, int c, int d), находящее наименьшее из четырех данных чисел. Функцияmainдожна считывать четыре числа с клавиатуры, вызывать функциюmin, выводить результат ее работы на экран. - (B) Напишите функцию
double power (double a, int n), вычисляющую значение an . Функцияmainдолжна считывать числаaиn, вызывать функциюpower, выводить результат ее работы на экран. - (C) Напишите функцию
bool Xor (bool x, bool y), реализующую функцию "Исключающее ИЛИ" двух логических переменных x и y. ФункцияXorдолжна возвращатьtrue, если ровно один из ее аргументовxилиy, но не оба одновременно равныtrue. Функцияmainв программе должна запрашивать значения переменныхxиy, (два числа, равных 0 или 1), вызывать функциюXor(x,y)и выводить результат на экран. - (D) Напишите "функцию голосования"
bool Election(bool x, bool y, bool z), которая возвращает то значение (trueилиfalse), которое среди значений ее аргументовx,y,zвстречается чаще. Функцияmainдолжна запрашивать три числа, равных 0 или 1, вызывать функциюElectionи выводить результат на экран. - (E) Напишите функцию
bool IsPrime (int n), возвращающуюtrue, если натуральное число n>1 простое, иfalse, если составное.Функция
mainдолжна запрашивать число с клавиатуры, вызывать функциюIsPime, выводить строкуprime, если число простое илиcomposite, если число составное.Указание: число является составным, если оно имеет натуральный делитель, отличный от 1 до n. Программа должна проверить делимость числа n на все числа от 2 до n-1 и вернуть
falseпри нахождении нетривиального делителя. Для того, чтобы проверить, что число n делится на число d нацело, необходимо сравнить остаток от деления n на d с нулем.
Рекурсия
Эпиграф:
void ShortStory()
{
cout<<"У попа была собака, он ее любил."<<endl;
cout<<"Она съела кусок мяса, он ее убил,"<<endl;
cout<<"В землю закопал и надпись написал:"<<endl;
ShortStory();
}
Как мы видели выше, функция может вызывать другую функцию. Но функция также может вызывать и саму себя! Рассмотрим это на примере функции вычисления факториала. Хорошо известно, что 0!=1, 1!=1. А как вычислить величину n! для большого n? Если бы мы могли вычислить величину (n-1)!, то тогда мы легко вычислим n!, поскольку n!=n(n-1)!. Но как вычислить (n-1)! ? Если бы мы вычислили (n-2)!, то мы сможем вычисли и (n-1)!=(n-1)(n-2)!. А как вычислить (n-2)! ? Если бы... В конце концов, мы дойдем до величины 0!, которая равна 1. Таким образом, для вычисления факториала мы можем использовать значение факториала для меньшего числа. Это можно сделать и в программе на C++:
int factorial (int n)
{
if (n==0)
return 1;
else
return n*factorial(n-1);
}
Подобный прием (вызов функцией самой себя) называется рекурсией, а сама функция называется рекурсивной.
Рекурсивные функции являются мощным механизмом в программировании. К сожалению, они не всегда эффективны (об этом речь пойдет позже). Также часто использование рекурсии приводит к ошибкам, наиболее распространенная из таких ошибок – бесконечная рекурсия, когда цепочка вызовов функций никогда не завершается и продолжается, пока не кончится свободная память в компьютере. Пример бесконечной рекурсии приведен в эпиграфе к этому разделу. Две наиболее распространенные причины для бесконечной рекурсии:
- Неправильное оформление выхода из рекурсии. Например, если мы в программе вычисления факториала
забудем поставить проверку
if (n==0), тоfactorial(0)вызоветfactorial(-1), тот вызоветfactorial(-2)и т.д. - Рекурсивный вызов с неправильными параметрами. Например, если функция
factorial(n)будет вызыватьfactorial(n), то также получиться бесконечная цепочка.
Поэтому при разработке рекурсивной функции необходимо прежде всего оформлять условия завершения рекурсии и думать, почему рекурсия когда-либо завершит работу.
Упражнения
- (B) Напишите рекурсивную функцию возведения в степень, пользующуюся следующим свойством: an=a*an-1.
- (F) Напишите функцию возведения в степень, которая работала бы как для положительных, так и для отрицательных значений n: a-n=1/an.
- (G) Напишите функцию быстрого возведения в степень, которая пользовалась бы следующими свойствами: an=(an/2)2 при четном n, an=a*an-1 при нечетном n. Подумайте, сколько умножений выполнит эта функция для вычисления an?
- (H)
Последовательность Фибоначчи определена следующим образом:
φ0=1, φ1=1,
φn=φn-1+φn-2
при n>1. Начало ряда Фибоначчи выглядит следующим образом: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... Напишите функцию
int phi(int n), которая по данному натуральному n возвращает φn. Функцияnдолжна считывать значение n и выводить значение n-го числа Фибоначчи. - (I) Для биномиальных коэффициентов (числа сочетаний из n по k) хорошо известна рекуррентная формула: Cnk=Cn-1k-1+Cn-1k. Вычислите значение Cnk, пользуясь этой формулой и учитывая, что Cn0=Cnn=1.
- (J)
Головоломка "Ханойские башни" состоит из трех колышков,
пронумерованных числами 1, 2, 3. На колышек 1 надета пирамидка из n дисков
различного диаметра в порядке возрастания диаметра. Диски можно перекладывать с одного колышка
на другой по одному, при этом диск нельзя класть на диск меньшего диаметра.
Необходимо переложить всю пирамидку с колышка 1 на колышек 2 за минимальное число
перекладываний.
Напишите программу, которая решает головоломку – для данного числа дисков n печатает последовательность перекладываний в формате
"Disk 1 move from 1 to 2"(диск 1 переложить c колышка 1 на колышек 2), печатая по одной инструкции в строке. Диски пронумерованы числами от 1 до n в порядке возрастания диаметров.Программа должна вывести минимальный (по количеству произведенных операций) способ перекладывания пирамидки.

Указание: подумайте, как переложить пирамидку из одного диска? Из двух дисков? Из трех дисков? Из четырех дисков? Напишите функцию
void move (int n, int x, int y), которая перекладывает пирамидку высотыnс колышка номерxна колышек номерy.