Урок 7. Функции Печать
Автор: Андрей   
02.11.2009 18:50

Предисловие
Объявления функций
Определения функций
Аргументы функций

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

Объявления функций
Часто результатом работы функции являются данные какого-либо типа. Поэтому при объявлении функции мы определяем, какого типа данные она должна возвращать (либо указываем, что она не должна ничего возвращать), а в коде непосредственно реализуем передачу возвращаемых данных.
Также функция может принимать данные в качестве аргументов. В этом случае мы указываем типы и количество этих аргументов. Также мы можем указать имена аргументов (например, для лучшей читаемости кода), однако если объявление функции не является определением (см. ниже), компилятор проигнорирует эти имена.
Таким образом, объявление функции будет выглядеть следующим образом: вначале указывается тип возвращаемого значения, затем имя функции, затем в скобках указание аргументов. Указание void в качестве возвращаемого значения означает, что функция не возвращает значения. В языке Pascal аналогами таких функций являются процедуры (procedure), в то время как все остальные функции C++ - это function.
Если функция не принимает никаких аргументов, то после ее имени следуют просто пустые круглые скобки. Скобки являются указанием на то, что данная определяемая сущность является именно функцией, а не переменной такого же типа.
Вот несколько примеров объявления функции:

char GetDigit();
bool IsPositive(double num);
void Draw(MyPolygon, MyColor);

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

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

double sum(double, double);
double sum(double a, double b)
 {
 return a + b;
 }
double myabs(double a)
 {
 if (a >= 0) return a;
 else return -a;
 }
void PrintDate(unsigned dd, unsigned mm, int yy)
 {
 if(dd <= 31 && mm <= 12)
  std::cout << dd << "." << mm << "." << yy;
 }

Инструкция return возвращает значение и завершает выполнение функции. Тело функции, возвращающей значение обязано содержать инструкцию return с указанием выражения или аргумента соответствующего типа, или типа, который может быть с помощью неявного преобразования приведен к типу возвращаемого значения. Если функция не возвращает значения, в ней может присутствовать инструкция return, не содержащая никаких выражений (например, чтобы завершить выполнение функции в середине тела функции при наступлении определенных условий). При этом функция main является исключением - имея тип int она может не возвращать никакого значения или возвращать нулевое значение - это означает успешное завершение работы программы. При возвращении ненулевого значения завершение считается аварийным.
Функция myabs может быть реализована также альтернативными эквивалентными способами:

double myabs2(double a)
 {
 if (a >= 0) return a;
 return -a;
 }
double myabs3(double a)
 {
 return a >= 0 ? a : -a;
 }

Значение выражения, использованного в функции myabs3 описано здесь.

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

void f(int arg)
 {
 arg += 2;
 std::cin << arg << '\n';
}
int main()
{
int i = 3;
f(3);
std::cin << i << '\n';
}

Эта программа выведет сначала число "5", затем - "3". При вызове функции f() была создана переменная типа int, которой было передано в качестве начального значения число 3 - значение переменной i. Затем эта локальная переменная была увеличена и выведена на экран, а после завершения работы функции была уничтожена. Переменная i при этом никак не поменялась.
Однако, кроме использованной здесь передачи аргументов по значению в C++ существует возможность передачи аргументов по ссылке. Ссылка - это альтернативное имя некоего объекта. Производя операции над ссылкой, мы на самом деле производим операции над объектом, на который она ссылается. Т.е. ссылка всегда указывает на ту же область памяти, что и объект, значением которого она была проинициализирована. Типом ссылки на объект типа T является T&. Например,

void g()
 {
 int i = 3;
 int& r = i;
 i += 2; //i == 5
 r += 4; //i == 9
 }

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

void h(int i, int& r)
 {
 i++;
 r++;
 }
int main()
{
int a = 1;
int b = 3;
h(i, j);
}

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

struct LargeStruct
 {
 int Field1;
 std::string Field2;
 //...
 };
bool IsEqual(const LargeStruct& s1, const LargeStruct& s2)
 {
 return (s1.Field1 == s2.Field1 && s1.Field2 = s2.Field2 && /*...*/ );
 }

В этом случае компилятор заметит попытку модификации s1.Field2, вызванную опечаткой (= вместо ==) и укажет нам на нее.

Обновлено 23.11.2009 15:15