C++ позволяет разделить код программ на части, называемые функциями. Сами функции могут быть записаны и отлажены отдельно от остального кода программы.
Возможность разбивать программу на части с последующей отладкой каждой функции в отдельности существенно снижает сложность создания больших программ. Этот подход является, по сути, простейшей формой инкапсуляции.
Функцией называют логически обособленный блок кода C++, имеющий следующий вид:
<тип возвращаемого значения> name (<аргументы функции>)
{
//...
return <выражение>
}
Аргументами функции называются значения, которые можно передать ей при вызове. В возвращаемом значении указывается результат, который функция возвращает по окончании работы. Например, в вызове функции возведения в квадрат square (10) знчение 10 – это аргумент, а возвращаемое значение будет равно 100.
Фигурные скобки при написании функции должны быть расположены таким образом, чтобы очевидными были границы блока программного кода.
Пример:
void myFunction ()
{
// тело функции
}
Так нельзя:
И аргументы, и возвращаемое значение функции необязательны. Если какой-либо элемент отсутствует, вместо него используется ключевое слово void. Если вместо списка аргументов используется void, то при вызове функция не получает никаких аргументов. Если же возвращаемый тип функции – void, то вызывающая программа не получает от функции никакого значения.
Функцию можно расположить в отдельном заголовочном файле или в том же файле, где находится функция main(). В этом случае описание функции должно располагаться до функции main(). Если размеры программы и функции небольшие, то этот вариант расположения является приемлемым:
Если же функция достаточно объемная, или этих функций несколько, то можно перед main() прописать прототипы функций, а сами функции описать после main():
Прототипом функции называется объявление функции, не содержащее тела функции, но указывающее имя функции, арность (количество аргументов – унарный, бинарный и т.д.), типы аргументов и возвращаемый тип данных.
Таким образом, существует 3 варианта описания функций:
- в заголовочных файлах;
- перед main();
- после main(), с использованием прототипов функций.
Ранее была рассмотрена программа, которая суммировала числа. Эта программа содержала 2 цикла. Во вложенном цикле суммировалась последовательность введенных пользователем чисел. Вложенный цикл был включен во внешний цикл, который повторял процесс, пока пользователь не вводил сумму чисел, равную 0:
Программа, демонстрирующая работу вложенных циклов:
Разделение этих двух циклов (внешнего и вложенного) может сделать программу более простой и наглядной. Далее показано, как можно упростить программу с помощью создания функции sumSequence (). Удобно создать эту функцию сразу в отдельном – заголовочном – файле. Чтобы создать заголовочный файл, следует обратиться к главному меню:
Далее нужно изменить имя заголовочного файла. Оставить указанное Расположение без изменений:
Нажать Добавить, и файл появится в папке с проектом. В окне редактора исходного кода откроется вкладка с заголовочным файлом:
После того как функция будет определена в заголовочном файле, main() будет иметь следующий вид:
В третьей строке int sumSequence (void)
- int – тип возвращаемого значения
- (void) или просто () – в функцию не передаются какие-либо аргументы.
Результат работы программы не изменится.
Итак, функции могут возвращать значение любого стандартного типа, например, int, double или char. Функция, которая ничего не возвращает, известна как void-функция.
Пример:
Инструкция return в void-функциях является необязательной.
Локальные, глобальные и статические переменные
Переменные, объявленные внутри любой функции, в том числе внутри функции main(), называются локальными. В следующем примере переменная r является локальной по отношению к функции fn(). В функции fn() про эту переменную ничего не известно.
Пример:
До вызова fn() переменной w не существует. После окончания работы ее содержимое навсегда теряется. Доступ к ней имеет только функция fn(), остальные функции не могут ее использовать:
А глобальная переменная q существует на протяжении работы всей программы и в любой момент доступна всем функциям:
Статическая переменная является чем-то средним между локальной и глобальной. Она создается, когда программа при выполнении достигает описания переменной (когда происходит первый вызов функции). Статическая переменная тоже доступна только из функции, в которой она объявлена. Но, в отличие от локальной переменной, статическая переменная продолжает существовать и после окончания работы функции.
Пример:
Если в функции fn() статической переменной присваивается какое-то значение, то оно сохранится до следующего вызова fn().
Директива #define
Константы, определенные в программе, обрабатываются на этапе компиляции. Если константу определить в разделе #define, то препроцессор на самом первом этапе обработки программы (до компиляции) пройдет по всему коду и заменит определенную в #define константу на ее значение.
Следующий код
для компилятора после препроцессорной обработки будет выглядеть так:
Макрос
С помощью директивы #define функцию можно организовать в виде макроса (пробел между S и (…) не допускается):
- Написать void-функцию, которая спрашивает у пользователя пароль, выполняет проверку и выводит результат. В качестве правильного пароля использовать q1w2e3r4.
- Написать void-функцию, которая спрашивает у пользователя 2 числа, проверяет делитель на ноль и сама выводит результат.
- Написать void-функцию, которая спрашивает у пользователя число и показатель степени числа, вычисляет результат и выводит его на экран.
- Написать программу с функцией проверки введенного пользователем пароля. Использовать тип возвращаемого значения string.
- Изменить программу с функцией проверки введенного пользователем пароля: использовать тип возвращаемого значения bool.
- Написать программу, в которой функция принимает от пользователя 2 числа, сравнивает полученные числа и возвращает в main() значение ‘>’, ‘<‘ или ‘=‘. Вывод данных производится в функции main().
- Написать программу, в которой функция принимает от пользователя 3 числа, вычисляет их среднее арифметическое и возвращает результат в функцию main(). Вывод результата производится в функции main() с округлением до сотых.
Функции без аргументов используются редко, т.к. связь с такими функциями односторонняя, т.е. осуществляется только посредством возвращаемых значений. Аргументы функций позволяют установить двустороннюю связь – через передаваемые параметры и возвращаемые значения. Аргументами функции называют значения, которые передаются функции во время вызова.
Функции с одним аргументом
В следующем примере определяется и используется функция square(), которая возвращает квадрат переданного в качестве аргумента числа типа double:
Можно из вызываемой функции вызвать очередную функцию. В этом случае очередная вызываемая функция должна быть описана до момента ее вызова:
Либо можно использовать прототипы функций. В этом случае порядок расположения прототипов и самих функций неважен. Та же программа с использованием прототипов функций:
Функции с несколькими аргументами
Можно передать в функцию любое количество аргументов:
Во втором листинге переменная x, описанная в функции main(), и переменная x, описанные в функции sum(), – это не одна и та же переменная. В функции sum() для x указывается тип данных. Указание типа данных обозначает объявление новой переменной.
- Фактические параметры - это идентификаторы отправляемых данных для функции.
- Формальные параметры - это идентификаторы входных данных для функции.
В качестве аргументов в функцию можно передавать значения. Но нежелательно, т.к. это будут "магические числа" и значения:
Встроенные функции
Основная идея встраиваемых функций в том, чтобы ускорить программу ценой занимаемого места. После того, как с помощью ключевого слова inline определена встроенная функция, то всякий раз, когда будет вызываться эта функция, компилятор будет заменять вызов функции фактическим кодом из функции. Такая функция выполняется быстрее, поскольку от процессора не требуется осуществлять переход к телу функции.
Если стоит вызов функции, то процессор будет переходить к ней, будет пересылать данные через стек для оперирования этими данными внутри функции, а если идет сразу код функции, то не будет перехода и пересылки.
Функции-члены (методы), определенные непосредственно внутри класса, по умолчанию считаются встроенными (подставляемыми, inline) потому что большинство методов, определенных внутри класса, довольно малы, а такие маленькие функции являются главными кандидатами на подстановку.
Однако при этом программы, использующие встроенные функции, занимают больше места, поскольку копии таких встраиваемых функций определяются один раз, а подставляются вместо каждого вызова.
Пример встроенной функции:
#include
using namespace std;
inline void world()
{
cout << “Hello, World” << endl;
}
int main ()
{
world(); // вызов встроенной функции
system (“pause”);
}
При этом компилятор может проигнорировать ключевое слово inline, если функция слишком объемная (с циклами, операторами выбора или условными операторами). Также компилятор может сделать функцию встроенной без использования inline, если посчитает, что функция достаточно мала для этого.
C++ позволяет программистам называть несколько разных функций одним и тем же именем. Эта возможность называется перегрузкой функций (function overloading).
1. Выполнить по образцу.
Две функции в одной программе не могут иметь одинаковых имен, т.к. компилятор C++ не сможет их различить. Однако используемое компилятором внутреннее имя функции включает число и типы аргументов (но не возвращаемое значение). Впишите следующие функции перед функцией main():
void myFunction (void) {}
void myFunction (int n) {}
void myFunction (double d) {}
void myFunction (int n1, int n2) {}
Нажмите F5, чтобы запустить отладку. Все эти функции являются разными и не вызывают ошибок компиляции.
2. Выполнить по образцу.
Тип аргумента void указывать не обязательно. Поэтому
void myFunction (void) {}
и
void myFunction () {}
вызывают одну и ту же функцию.
Впишите перед функцией main() эти две функции.
Нажмите F5, чтобы запустить отладку. Прочитайте сообщение об ошибке.
3. Выполнить по образцу.
Сигнатура функции – это полное имя функции. В сигнатуру функции не входит тип возвращаемого значения.
Впишите следующие две функции перед функцией main().
int someFunction (int n){};
double someFunction (int n){};
Нажмите F5, чтобы запустить отладку. Прочитайте сообщение об ошибке.
Обе функции имеют одинаковые имена (сигнатуры) и поэтому не могут использоваться в одной программе.
Полным именем (сигнатурой) этих функций является someFunction (int).
Допустим, нужна программа, которая будет вычислять сумму: целых чисел, вещественных и т.д. Пришлось бы для каждого типа данных писать отдельную функцию (делать перегрузку функции). Вместо этого можно создать шаблон функции, которая будет принимать параметры любого типа, в теле функции выполнять над ними указанные действия и возвращать полученное значение. Для создания шаблона функции используются ключевые слова template (англ.) – шаблон и typename (ключевое слово из языка C) – название типа. Тип данных параметров в этой программе, передаваемых в шаблонную функцию, должен быть одинаковым, потому что тип T не может быть одновременно разными типами.
Если же в шаблонную функцию требуется передавать параметры с разными типами данных, то нужно описать нужное количество шаблонных типов следующим образом (для возвращаемого значения придется выбрать один из типов):
или
Еще один вариант:
Вместо typename (язык C) можно использовать ключевое слово class из языка C++. Разницы в них нет, результат будет таким же, но в объявлении параметров принято использовать для всех параметров либо class, либо typename:
Рекурсия – это функция, вызывающая сама себя.
Например, можно организовать повторный ввод пароля с помощью рекурсии.
Еще один вариант алгоритма той же программы с использованием рекурсии (пример шизокода).
Результат здесь будет таким же.
Любую рекурсию можно заменить циклом. Любой цикл можно заменить рекурсией.
Циклический алгоритм работает быстрее. Рекурсивный алгоритм может выглядеть более компактным.
1. Выполнить по образцу.
Написать следующую программу:
Закомментировать строку system(“pause”);. Нажать F5 для запуска отладки. Попробуйте устно объяснить поведение программы.
2. Написать программу с использованием рекурсии для поиска наибольшего общего делителя двух чисел. Числа вводит пользователь.
3. Написать рекурсивную функцию для вычисления факториала натурального числа.
4. Преобразовать предыдущую программу таким образом, чтобы вместо рекурсии в функции использовался цикл с предусловием.