Краткое содержание
До сих пор каждая программа курса была одним блоком, main, в котором всё пишется там, где используется. Этот урок учит давать имя части программы и пользоваться ею, не заглядывая внутрь: это функция. Изучаются объявление, которое сообщает компилятору имя, тип возвращаемого значения и параметры; определение, которое добавляет тело; и вызов, который передаёт управление этому телу и получает возвращённое значение. Различается то, что компилятор проверяет при компиляции (что каждый вызов согласуется с объявлением), и то, что происходит при выполнении (переход к определению и возврат). Объясняется, почему достижение конца функции, которая должна вернуть значение, без return является неопределённым поведением. Выполнение прослеживается по стеку вызовов, который хранит по одному кадру на каждый незавершённый вызов. И каждая функция специфицируется контрактом, её предусловием и постусловием, который говорит, чего функция требует от вызывающего и что гарантирует взамен.
Цели обучения
- Объявлять, определять и вызывать функции с параметрами и возвращаемым значением и указывать в программе, какая часть является объявлением, а какая определением.
- Прослеживать с помощью диаграммы стека вызовов выполнение программы с функциями, которые вызывают друг друга.
- Специфицировать функцию её предусловием и постусловием и записывать вызов, нарушающий предусловие.
- Объяснять, почему функция, которая на каком-то пути не возвращает значения, приводит к неопределённому поведению, и распознавать соответствующее предупреждение компилятора.
Зачем нужны функции
Программы предыдущего раздела решали каждую задачу внутри main, и этого хватало, пока задачи были небольшими. Однако, как только программа растёт, возникают три трудности. Первая из них связана с повторением: если одно и то же вычисление нужно в трёх местах, оно пишется трижды, и ошибка в нём исправляется трижды (или в одном из мест её забывают исправить). Вторая касается чтения: блок из пятидесяти строк говорит не о том, что он делает, а о том, как он это делает, и читатель должен восстанавливать замысел по механизму. Третья касается тестирования: если всё перемешано, невозможно испытать одну часть, не выполняя остальные.
Функция есть именованный фрагмент программы, который получает данные, называемые параметрами, и выдаёт результат, называемый возвращаемым значением. Математика знает эту идею уже много веков: расстояние между двумя точками плоскости есть функция их четырёх координат, , и тот, кто вычисляет периметр треугольника, не повторяет формулу трижды, а даёт ей имя и применяет её трижды. В C++ пишут то же самое.
Программа читает координаты трёх вершин треугольника и выводит его периметр, то есть сумму длин трёх его сторон.
#include <cmath>#include <iostream>#include <print>// Расстояние между точками (x1, y1) и (x2, y2).double distance(double x1, double y1, double x2, double y2){ const double dx{x2 - x1}; const double dy{y2 - y1}; return std::sqrt(dx * dx + dy * dy);}int main(){ double ax{}; double ay{}; double bx{}; double by{}; double cx{}; double cy{}; std::cin >> ax >> ay >> bx >> by >> cx >> cy; const double perimeter{distance(ax, ay, bx, by) + distance(bx, by, cx, cy) + distance(cx, cy, ax, ay)}; std::println("Периметр: {}", perimeter);}0 0 3 0 0 4Периметр: 12Стороны треугольника с вершинами , и равны 3, 5 и 4, а их сумма равна 12.
Функция distance пишется один раз и используется трижды; main читается как определение периметра, «сумма трёх расстояний»; а distance можно протестировать отдельно, на паре точек с известным расстоянием. Тем самым получены все три вещи, которых недоставало. Общий принцип, верный для любого языка, состоит в том, что функция даёт имя идее: тот, кто ею пользуется, думает о том, что она делает, а не о том, как. Такое разделение использования и реализации называется процедурной абстракцией, и оно составляет тему всего раздела.
Загрузка содержимого…