Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_C++ / 06

.htm
Скачиваний:
20
Добавлен:
02.02.2015
Размер:
21.18 Кб
Скачать

06 - Функции Содержание     Предыдущее занятие     Следующее занятие

Занятие 06 Функции 1 Описание и вызов функций Функции являются основными составляющими программы. Все операторы могут располагаться только внутри функций.

Объявление функции в общем виде выглядит следующим образом:

тип_результата имя_функции(список_описаний_формальных_параметров) список_спецификаций; Параметры (аргументы) функции, указываемые в списке при определении функции, называются формальными. Параметры, указываемые при вызове функции, называются фактическими.

При вызове функции выделяется память под ее формальные параметры, затем каждому формальному параметру присваивается значение фактического параметра. Проверяется соответствие типов формальных и фактических параметров и при необходимости выполняются либо стандартные, либо определенные пользователем преобразования типов.

Список описаний формальных параметров:

тип_параметра имя_параметра, тип_параметра имя_параметра, ... Возможные спецификации будут рассмотрены ниже.

Определение функции отличается отсутствием точки с запятой после заголовка и наличием тела функции. Тело функции представляет собой составной оператор (блок).

Пример объявления функции, вычисляющей сумму двух целых чисел:

int sum(int a, int b); Пример определения той же функции:

int sum(int a, int b) { int c = a + b; return c; // Можно обойтись без переменной с: return a + b; } Вызов функции может быть осуществлен в выражении, в котором допустимо использование значения типа возвращаемого результата. При вызове функции указывается ее имя и список фактических параметров без указания их типов:

int x = 4; int y = 5; int z = sum(x, y); int t = sum(1, 3); Функция может не иметь параметров:

int zero() { return 0; } При вызове такой функции также нужно использовать скобки:

cout << zero(); Инструкция return обеспечивает механизм завершения работы функции. Если оператор return сопровождается некоторым выражением, значение этого выражения становится возвращаемым значением функции.

Функция может не возвращать никакого результата. Для обозначения этого используется тип void. В этом случае в теле функции return может отсутствовать. Если инструкция return присутствует, то после нее не должно быть ни какого выражения. Такую функцию можно вызвать только отдельным оператором.

Функции-подстановки, или встроенные, объявлены с модификатором inline:

inline int min(int a, int b) { return a < b ? a : b; } Если функция объявлена встроенной, то компилятор подставляет в точку вызова ее тело. Спецификация inline служит подсказкой транслятору. Из-за наличия взаиморекурсивных вызовов функций-подстановок, а также функций-подстановок, рекурсивность которых зависит от входных данных, нельзя утверждать, что каждый вызов функции-подстановки действительно реализуется подстановкой ее тела.

Целесообразно с модификатором inline описывать самые простые функции, многократная подстановка которых не существенно повлияет на размеры программы.

2 Передача параметров. Возвращаемое значение По умолчанию в С++ параметры передаются по значению, т.е. значения фактических параметров копируются в память, отведенную для формальных параметров. При этом значения, с которыми работает функция - это ее собственные локальные копии фактических параметров и их изменение на эти параметры не влияет. При окончании функции эти локальные значения теряются. Таким образом, при передаче по значению содержимое фактических параметров не изменяется:

void f(int k) { k++; // k = 2; } int main() { int k = 1; f(k); cout << k; // k = 1; return 0; } Передача в функции параметров по значению может оказаться неудобной, например, когда в качестве параметра передается большой объект и нужно экономить время на копирование и память в стеке, либо необходимо сохранить изменения значений параметров после выхода из функции. В этом случае можно передавать в функцию указатели, т. е. в функцию передается адрес того объекта, который необходимо изменить, а внутри функции происходит разыменование параметра-указателя:

void f(int *k) { (*k)++; // Скобки обязательны! } int main() { int k = 1; f(&k); // Передача адреса k cout << k; // k = 2; return 0; } При передаче параметров по ссылке каждый формальный параметр, который необходимо изменить, описывают с типом ссылка. Поскольку ссылка - это другое имя объекта и все действия, которые производятся над ссылкой, являются действиями над самим объектом, то при передаче параметра-ссылки внутри функции мы имеем дело с самим объектом, а не с его локальной копией. Это означает, что действия, произведенные в функции над формальными параметрами проводят к изменению фактических. Использование ссылок в данном случае удобнее, чем использование указателей для тех же целей:

void f(int &k) { k++; } int main() { int k = 1; f(k); cout << k; // k = 2; return 0; } При передаче параметров по ссылке в качестве фактического параметра следует передавать объект, имеющий адрес (переменную). Можно однако описать параметр-ссылку с модификатором const. Такой параметр нельзя изменять в функции. В этом случае в качестве фактического параметра можно использовать константу или выражение (rvalue):

int f(const int &k) { return k; } Если в качестве параметра функции указан массив, то передается указатель на его первый элемент. Это означает, что фактический параметр типа T[] преобразуется к типу T*, и затем передается. Поэтому присваивание элементу формального параметра-массива изменяет этот элемент. Массивы отличаются от других типов тем, что они не передаются и не могут передаваться по значению. В вызываемой функции размер передаваемого массива неизвестен. Его нужно передавать отдельно.

По умолчанию результат, возвращаемый функцией, передается по значению, т.е. значение локальной переменной копируется в ту переменную, которой присваивается значение функции при ее вызове (или во временную переменную, если функция используется в выражении). При возврате большого объекта целесообразно возвращать ссылку или указатель для предотвращения копирования. Возврат ссылки или указателя может привести к тому, что возвращенный объект может быть изменен. Вызов функции в этом случае может стоять даже в левой части операции присваивания:

#include <iostream> #include <stdlib.h> using namespace std; const int n = 3; int someArray[] = {1, 2, 3}; int &a(int i) { if (i < 0 || i >= n) { cout << "Wrong index " << i << "\n"; exit(-1); } return someArray[i]; } int main() { a(1) = 250; // здесь someArray == {1, 250, 3} cout << a(3); // Ошибка! return 0; } В приведенном выше примере функция exit(), объявленная в заголовочном файле stdlib.h, вызывает досрочное завершение прогарммы с заданным кодом.

Чтобы предотвратить случайные изменения объекта с помощью возвращенной ссылки или указателя, возвращаемое значение нужно описывать как ссылку или указатель на константу :

const int &a(int i); Так как память, выделенная локальным переменным, освобождается по окончании функции, то адрес такой переменной (или ссылка на нее) никогда не должен передаваться за пределы ее области видимости.

3 Перегруженные имена функций Перегрузкой имени называется его использование для обозначения разных операций над разными типами.

Объявления функций могут встречаться в тексте программы несколько раз с одинаковыми типами, но различными именами параметров. Если в двух описаниях списки формальных параметров и тип возвращаемого значения полностью совпадают, то считается, что второе объявление просто повторяет первое. Если списки формальных параметров совпадают, но типы возвращаемых значений различны, то второе объявление считается ошибочным переопределением первого и дает ошибку при трансляции. Если списки формальных параметров двух функций различаются числом параметров или их типами, то эти две функции считаются перегрузкой одной функции и должны иметь разные определения.

Перегруженные функции используются в тех случаях, если несколько функций выполняет одно и то же действие над объектами разных типов и удобно дать одинаковые имена всем этим функциям:

int max(int, int); // Выбор максимального из двух целых чисел int max(const int *, int); // Выбор максимального элемента массива int max(const List&); // Выбор максимального элемента списка Для того, чтобы определить, какую именно функцию следует вызывать, сравниваются количество и типы фактических параметров, указанные в вызове, с количеством и типами формальных параметров всех описаний функций с данным именем. В результате вызывается та функция, у которой формальные параметры наилучшим образом сопоставились с параметрами вызова, или выдается ошибка если такой функции не нашлось. Правила применяются в следующем порядке по убыванию их приоритета:

Точное сопоставление: сопоставление произошло без всяких преобразований типа или только с неизбежными преобразованиями (например, имени массива в указатель, имени функции в указатель на функцию и типа T в const T).

Сопоставление с использованием стандартных целочисленных преобразований (т.е. char в int, short в int, беззнаковых char и short в int), а также преобразований float в double. Такое преобразование называется расширением типа.

Сопоставление с использованием стандартных преобразований (например, int в double, unsigned в int).

Сопоставление с использованием пользовательских преобразований.

Сопоставление с использованием эллипсиса ... в описании функции (функции в стиле С с переменным числом параметров). Данный вариант описания функций считается устаревшим и сохранен в С++ для совместимости с ранее написанными программами. Использование таких описаний нецелесообразно.

Вызов функции с несколькими параметрами разрешается с помощью применения правил сопоставления к каждому параметру по очереди. Выбирается та функция, для которой хотя бы для одного параметра сопоставление будет лучше, чем для всех остальных перегруженных функций, а также разрешение всех остальных параметров будет не хуже, чем для всех остальных перегруженных функций.

Если найдены два сопоставления по самому приоритетному правилу, то вызов считается неоднозначным, а значит ошибочным. На разрешение неопределенности при перегрузке не влияет порядок описаний рассматриваемых функций, а типы возвращаемых функциями значений вообще не учитываются. Невозможно различить две функции, одна из которых принимает параметр типа T, а другая - типа ссылка на T, либо типа const T.

4 Параметры по умолчанию Значение параметра по умолчанию – это значение, которое разработчик считает подходящим в большинстве случаев употребления функции, хотя и не во всех. Оно освобождает программиста от необходимости уделять внимание каждой детали интерфейса функции. Значения по умолчанию для одного или нескольких параметров функции задаются с помощью того же синтаксиса, который употребляется при инициализации переменных.

int f(double a, double b = 0, double c = 1) { //... } Данную функцию можно вызывать либо с одним, либо с двумя, либо с тремя параметрами:

double x = 0.1; double y = 0.2; double z = 0.3; cout << f(x) << f(x, y) << f(x, y, z); Фактические параметры сопоставляются с формальными параметрами позиционно (в порядке следования), и значения по умолчанию могут использоваться только для подстановки вместо отсутствующих последних параметров. Параметры по умолчанию задаются в списке последними.

По соглашению значение задается в объявлении функции, которое размещается в общедоступном заголовочном файле (описывающем интерфейс), а не в ее определении. Если же указать его в определении, это значение будет доступно только для вызовов функции внутри исходного файла, содержащего это определение. Можно объявить функцию повторно и таким образом задать дополнительные параметры по умолчанию.

5 Примеры программ 5.1 Разделение программы на функции В большинстве случаев программы могут быть разделены на относительно независимые части. В частности, в программах расчетного характера всегда можно выделить части, связанные с вводом (подготовкой) данных, непосредственно расчетом и выводом в файл или на экран. Допустим, необходимо реализовать программу, вычисляющую сумму квадратов натуральных чисел от 1 до n. Первый вариант программы не разделен на функции:

#include <iostream> using namespace std; int main() { int n; cout << "Input n:"; cin >> n; int y = 0; for (int i = 1; i <= n; i++) y += i * i; cout << "y = " << y; cin.get(); cin.get(); return 0; } Простейший вариант разбиения на фунции можно реализовать с использованием глобальных переменных:

#include <iostream> using namespace std; int n; int y = 0; void read() { cout << "Input n:"; cin >> n; } void calc() { for (int i = 1; i <= n; i++) y += i * i; } void write() { cout << "y = " << y; cin.get(); cin.get(); } int main() { read(); calc(); write(); return 0; } Поскольку использование глобальных переменных не приветствуется, взаимодействие с функциями можно осуществлять через параметры:

#include <iostream> using namespace std; int read() { int n; cout << "Input n:"; cin >> n; return n; } int calc(int n) { int y = 0; for (int i = 1; i <= n; i++) y += i * i; return y; } void write(int y) { cout << "y = " << y; cin.get(); cin.get(); } int main() { int n = read(); int y = calc(n); write(y); return 0; } Можно также обойтись без переменных в функции main():

... int main() { write(calc(read())); return 0; } 5.2 Использование рекурсии Функция может вызывать саму себя. Такой прием называется рекурсией. Рекурсия может быть прямой и косвенной. При использовании прямой рекурсии вызов функции находится в ней самой. Косвенная рекурсия осуществляется через другую функцию. Иногда рекурсию можно использовать вместо циклов. В следуюшем примере вычисляется сумма квадратов натуральных чисел:

#include <iostream> using namespace std; double sum(int n) { if (n <= 1) return 1; else return n * n + sum(n - 1); } int main() { cout << sum(5); return 0; } Неправильное использование рекурсии может привести к переполнению программного стека.

6 Задания на самостоятельную работу Задание 1 Реализовать задания предыдущих занятий, разделив части программы на отдельные функций.

Задание 2 Реализовать функцию вычисления суммы, которая может вызываться без параметров (возвращает 0), с одним параметром (возвращает значение параметра), с двумя, тремя и четырьмя параметрами (возвращает сумму параметров). Реализовать программу, которая демонстрирует все варианты вызова функций.

Задание 3 Реализовать функции вычисления абсолютной величины, возвращающие целое значение для целого аргумента и вещественное значение для вещественного аргумента. Реализовать программу, которая демонстрирует работу функций.

Задание 4 Реализовать программу вычисления целой степени с помощью рекурсивной функции.

Задание 5 Реализовать программу вычисления факториала с помощью рекурсивной функции.

 

Содержание     Предыдущее занятие     Следующее занятие

 

© 2001 - 2006 Иванов Л.В.

Соседние файлы в папке OOP_C++