Во всех рассмотренных до сих пор примерах параметры функций передавались
по значению. При вызове из функции "main()" вызываемой функции передаются ко-
пии указанных переменных. Например, в программе 2.1 функции "area(...)" переда-
ются текущие значения переменных "this_length" и "this_width". Затем функция
"area(...)" сохраняет переданные значения в собственных локальных переменных, и
именно эти переменные участвуют в последующих вычислениях внутри функции.
3.1 Передача параметров по значению
Функции, принимающие параметры по значению, "безопасны" в том смысле,
что они не могут случайно изменить переменные вызывающей функции (т.е. у функ-
ций нет скрытых побочных эффектов). Большинство функций проектируются именно
таким образом.
Программа 3.1 поясняет, почему важно гарантировать "сохранность" перемен-
ных вызывающей функции. Эта программа должна выводить на экран факториал и
корень из числа, введенного пользователем:
Введите положительное число:
Факториал 4! равен 24, а квадратный корень из 4 равен 2.
Для извлечения квадратного корня применяется библиотечная функция
"sqrt(...)". Библиотечной функции для вычисления факториала нет, поэтому при-
дется написать собственную функцию (вычисляющую для любого положительного
целого числа n значение n!=(1*2*3*...*n)).
ГЛАВНАЯ ФУНКЦИЯ:
int whole_number;
cout << "Введите положительное число:\n";
cin >> whole_number;
cout << "Факториал " << whole_number << "! равен ";
cout << factorial(whole_number);
cout << ", а квадратный корень из " << whole_number;
cout << " равен " << sqrt(whole_number) << ".\n";
return 0;
// КОНЕЦ ГЛАВНОЙ ФУНКЦИИ
ФУНКЦИЯ ДЛЯ ВЫЧИСЛЕНИЯ ФАКТОРИАЛА:
int product = 1;
for (; number > 0; number--)
product *= number;
return product;
}
// КОНЕЦ ФУНКЦИИ
Программа 3.1.
Если бы функция "factorial(...)" изменяла переменную вызывающей
функции, то программа 3.1 выдавала бы следующий ответ (формально правильный,
но по смыслу задачи некорректный):
Введите положительное число:
Факториал 4! равен 24, а квадратный корень из 0 равен 0.
3.2 Передача параметров по ссылке
Все-таки иногда бывает необходимо, чтобы функция изменила значение пере-
данного ей параметра. Рассмотрим программу 2.1. С 10-й по 14-ю строку в ней вы-
полняется запрос размеров прямоугольника, а затем вычисляется его площадь.
При структурном программировании независимые по смыслу части программы
принято оформлять в виде отдельных функций. Для получения данных от пользова-
теля создадим функцию "get_dimensions". В данном случае необходимо, чтобы эта
функция изменяла значения переменных "this_length" и "this_width" (переданных ей
в качестве параметров) – помещала в них значения, введенные пользователем с кла-
виатуры. Изменение параметров функции возможно при передаче параметров по
ссылке. У таких параметров в заголовке функции после имени типа указывается сим-
вол "&".
#include<iostream.h>
int area(int length, int width);
void get_dimensions(int& length, int& width);
// ГЛАВНАЯ ФУНКЦИЯ:
int main()
{
int this_length, this_width;
get_dimensions(this_length, this_width);
cout << "Площадь прямоугольника с размерами ";
cout << this_length << "x" << this_width;
cout << " равна " << area(this_length, this_width) << "\n";
return 0;
}
// КОНЕЦ ГЛАВНОЙ ФУНКЦИИ
// ФУНКЦИЯ ВВОДА РАЗМЕРОВ ПРЯМОУГОЛЬНИКА:
void get_dimensions(int& length, int& width)
{
cout << "Введите длину: ";
cin >> length;
cout << "Введите ширину: ";
cin >> width;
cout << "\n";
}
// КОНЕЦ ФУНКЦИИ
// ФУНКЦИЯ ВЫЧИСЛЕНИЯ ПЛОЩАДИ:
int area(int length, int width)
{
return length*width;
}
// КОНЕЦ ФУНКЦИИ
Программа 3.2.
Функция "get_dimensions" изменяет значения параметров "this_length" и
"this_width", но не возвращает никакого значения (т.е. не является "функцией" в ма-
тематическом смысле). Этот факт отражается и в прототипе, и в определении функ-
ции – в качестве возвращаемого значения указан тип "void" ("пустой" тип).
4. Полиморфизм и перегрузка функций
Одним из характерных свойств объектно-ориентированного языка, в том числе
и Си++, является полиморфизм – использование одного имени для выполнения раз-
личных действий над различными объектами. Применительно к функциям это назы-
вается перегрузкой. Для основных операций Си++ перегрузка уже встроена в язык:
например, у сложения существует только одно имя, "+", но его можно применять для
сложения как целых, так и вещественных значений. Эта идея расширяется на обра-
ботку операций, определенных пользователем, т.е., функций.
Перегруженные функции имеют одинаковые имена, но разные списки парамет-
ров и возвращаемые значения (см. программу 4.1).
#include<iostream.h>
int average(int first_number, int second_number,
int third_number);
int average(int first_number, int second_number);
// ГЛАВНАЯ ФУНКЦИЯ:
int main()
{
int number_A = 5, number_B = 3, number_C = 10;
cout << "Целочисленное среднее чисел " << number_A << " и ";
cout << number_B << " равно ";
cout << average(number_A, number_B) << ".\n\n";
cout << "Целочисленное среднее чисел " << number_A << ", ";
cout << number_B << " и " << number_C << " равно ";
cout << average(number_A, number_B, number_C) << ".\n";
return 0;
}
// КОНЕЦ ГЛАВНОЙ ФУНКЦИИ
// ФУНКЦИЯ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО
// ЗНАЧЕНИЯ 3-Х ЦЕЛЫХ ЧИСЕЛ:
int average(int first_number, int second_number,
int third_number)
{
return ((first_number + second_number + third_number)/3);
}
// КОНЕЦ ФУНКЦИИ
// ФУНКЦИЯ ДЛЯ ВЫЧИСЛЕНИЯ ЦЕЛОЧИСЛЕННОГО СРЕДНЕГО
// ЗНАЧЕНИЯ 2-Х ЦЕЛЫХ ЧИСЕЛ:
int average(int first_number, int second_number)
{
return ((first_number + second_number)/2);
}
// КОНЕЦ ФУНКЦИИ
Программа 4.1.
Программа 4.1. выводит на экран сообщения:
Целочисленное среднее чисел 5 и 3 равно 4.
Целочисленное среднее чисел 5, 3 и 10 равно 6.
5. Процедурная абстракция и "хороший" стиль программирования
Функции помогают применять для разработки программ структурный метод
проектирования " сверху вниз ". При этом решаемая задача делится на подзадачи (и за-
тем на под-подзадачи и т.д.). Для решения каждой подзадачи программист реализует
отдельную функцию, при этом ему не нужно знать детали реализации остальных
функций.
Чтобы функцией мог воспользоваться другой программист, она должна иметь
осмысленное имя и комментарий с описанием назначения функции, ее параметров и
возможных возвращаемых значений.
Опытные программисты на начальных этапах разработки часто применяют
пустые функции (заглушки), которые содержат только оператор возврата значения со-
ответствующего типа. Эти функций облегчают отладку главной функции или просто
функции более высокого уровня.
Выделение в решаемой задаче функций методом "сверху вниз" часто называет-
ся функциональной или процедурной абстракцией. При проектировании независимых
друг от друга функций широко применяется передача параметров по значению и ло-
кальные переменные внутри функций. После реализации программист может рас-
сматривать подобные функции как "черные ящики". Для их использования знать де-
тали реализации не обязательно.
6. Модульное программирование
Помимо метода "сверху вниз", вторым важным методом структурного проек-
тирования является метод модульного программирования. Он предполагает разделе-
ние текста программы на несколько файлов, в каждом из которых сосредоточены не-
зависимые части программы (сгруппированные по смыслу функции).
В программах на Си++ часто применяются библиотечные функции (например,
"sqrt(...)"). Для использования большинства функций, в том числе и библиотечных,
необходимы два файла (в скобках примеры даны для "sqrt(...)"):
• Заголовочный файл ("math.h") с прототипом функции ("sqrt(...)" и многих
других математических функций). Поэтому в программах, вызывающих
"sqrt(...)", есть строка "#include <math.h>", а не явное объявление этой
функции.
• Файл реализации (для пользовательских функций это файлы с исходным
текстом на Си++, а библиотечные функции обычно хранятся в скомпилиро-
ванном виде в специальных библиотечных файлах, например,
"libcmtd.lib"). Файлы реализации пользовательских функций (обычно с
расширением ".cpp") содержат определения этих функций.
Разделение исходного текста на заголовочные файлы и файлы реализации по-
казано в программе 6.1, которая выполняет те же действия, что и программа 4.1. Те-
перь программа состоит из трех файлов: главного файла, заголовочного файла с опи-
саниями двух функций расчета среднего значения, и соответствующего файла реали-
зации.
В главном файле содержится следующий текст:
#include <iostream.h>
#include "averages.h"
int main()
{
int number_A = 5, number_B = 3, number_C = 10;
cout << "Целочисленное среднее чисел " << number_A << " и ";
cout << number_B << " равно ";
cout << average(number_A, number_B) << ".\n\n";
cout << "Целочисленное среднее чисел " << number_A << ", ";
cout << number_B << " и " << number_C << " равно ";
cout << average(number_A, number_B, number_C) << ".\n";
return 0;
}