2.
Передача аргументов в функцию
3.
Значение, возвращаемое функцией
4.
Перегруженные функции, рекурсия
5.
Область видимости и класс памяти
6.
Возвращение значения по ссылке
7.
Константные аргументы функции
8.
Резюме
Функция представляет собой именованное объединение группы
операторов. Это объединение может быть вызвано из других частей программы.
Наиболее важной причиной использования функций служит
необходимость концептуализировать структуру программы.
Причиной создания концепции функций стало стремление
сократить размер программного кода. Любая последовательность операторов,
встречающаяся в программе более одного раза, будучи вынесенной в отдельную
функцию, сокращает размер программы. Несмотря на то, что функция в процессе
выполнения программы исполняется не один раз, ее код хранится только в одной
области памяти.
Следующий пример демонстрирует простую функцию, которая
печатает строку из 45 символов.
void starline(); // объявление функции (прототип)
int main()
{
starline(); // вызов функции
cout << "Тип данных
Диапазон" << endl;
starline(); //
вызов функции
cout
<< "char -128...127" << endl <<
"short-32.768...32.767" << endl
<< "int
Системно-зависимый" << endl
<< "long
-2.147.483.648...2.147.483.647" << endl;
starline(); // вызов функции
return 0;
}
void starline() //
заголовок функции
{
for(
int i=0; i<45; i++ ) //
тело функции
cout
<< '*';
cout
<< endl;
}
Программа включает в себя две функции: main() и starline().
Для добавления в программу дополнительных функций обязательными являются три
компонента: объявление функции, ее определение и вызовы.
Объявление функции. Подобно тому как нельзя использовать переменную, не сообщив
компилятору информацию о ней, нельзя и обратиться к функции, не указав в
программе ее необходимые атрибуты. Есть два способа описать функцию: объявить
или определить до первого ее вызова. Рассмотрим объявление функции.
В примере объявление функции starline() выглядит следующим образом: void starline();
Объявление функции означает, что в программе будет
содержаться код этой функции. Ключевое слово void
указывает на то, что функция не возвращает значения, а пустые скобки говорят об
отсутствии у функции передаваемых аргументов. Объявление функции заканчивается
точкой с запятой и на самом деле является обычным оператором.
Объявление функции также называют прототипом функции,
поскольку они являются неким общим представлением или описанием функций.
Информацию о функции, содержащуюся в ее объявлении также иногда называют сигнатурой
функции.
Вызов функции. Функция starline() трижды вызывается из функции main(). Все
три вызова выглядят одинаково: starline();
Вызов функции осуществляется по имени, внешне очень похож на
прототип; разница заключается лишь в том, что при вызове не указывается тип
возвращаемого значения. Вызов функции завершается точкой с запятой (;).
Выполнение оператора вызова функции инициирует выполнение самой функции.
Программа передает управление первому оператору тела функции. Затем исполняются
операторы, находящиеся в теле функции, и когда достигается закрывающая фигурная
скобка, управление передается обратно вызывающей программе.
Определение функции. Определение содержит код функции. Для функции starline()
определение выглядит следующим образом:
void starline() //
заголовок функции
{
for
( int i=0; i<45; i++ ) //
тело функции
cout
<< '*';
cout
<< endl;
}
Определение функции состоит из заголовка и тела функции.
Тело функции состоит из последовательности операторов, заключенной в фигурные
скобки. Заголовок функции должен соответствовать ее прототипу: имя функции и
тип возвращаемого ей значения должны совпадать с указанными в прототипе; кроме
того, аргументы функции, если они есть, должны иметь те же типы и следовать в
том же порядке, в каком они указывались в прототипе. Заголовок функции не
ограничивается точкой с запятой (;).
Отсутствие объявления. Вторым способом вставить функцию в программу является
ее определение, помещенное ранее первого ее вызова. В этом случае прототип функции
не используется. Изменим пример программы таким образом, что определение
функции starline() будет располагаться до первого ее вызова.
void
starline()
{
for
( int i=0; i<45; i++ )
cout
<< '*';
cout
<< endl;
}
int
main()
{
starline(); //
вызов функции
cout
<< "Тип данных Диапазон"
<< endl;
starline(); //
вызов функции
cout
<< "char -128...127" << endl << "short
-32.768...32.767" << endl
<< "int
Системно-зависимый" << endl <<"long -2.147.483.648. .2.147.483.647"
<< endl;
starline(); // вызов функции
return
0;
}
Аргументом называют единицу данных, передаваемую программой в функцию.
Аргументы позволяют функции оперировать различными значениями или выполнять
различные действия в зависимости от переданных ей значений.
Передача констант в функцию. Допустим, нужно изменить функцию
starline() таким образом, чтобы она выводила любое заданное количество
символов. В качестве аргументов для функции используется символ,
предназначенный для печати, а также число раз, которое данный символ будет
выведен на экран.
void
repchar(char, int);
int
main()
{
…..
repchar(‘*‘,
50);
…..
repchar('=',
30);
……
repchar(‘-‘,
50);
return
0;
}
void
repchar(char ch, int n)
{
for
( int i=0; i<n; i++ )
cout << ch;
cout << endl;
}
В функции repchar() в скобках указаны типы данных, которые
будут иметь передаваемые в функцию аргументы: char и int.
При вызове функции вместо аргументов указываются их
конкретные значения: При этом важно соблюдать порядок их следования. Кроме
того, типы аргументов в объявлении и определении функции также должны быть
согласованы.
Переменные, используемые внутри функции для хранения значений
аргументов, называются параметрами. В функции repchar() в
качестве параметров выступают переменные ch и n. Параметры используются
внутри функции так же, как обычные переменные.
Когда вызов функции произведен, параметры автоматически
инициализируются значениями, переданными программой.
Передача значений переменных в функцию. Рассмотрим пример, в котором вместо
констант в функцию будут передаваться значения переменных.
void
repchar(char, int);
int
main()
{
char
chin; int nin;
……
repchar(chin,
nin);
return
0;
}
void
repchar(char ch, int n)
{
for(int
i=0; i<n; i++)
cout
<< ch;
cout
<< endl;
}
В фрагменте программы переменные chin и nin функции main() передаются в качестве аргументов в функцию
repchar(): repchar(chin, nin);
Типы переменных, используемых в качестве аргументов функции,
должны совпадать с типами, указанными в объявлении и определении функции.
В данном примере в момент выполнения вызова функции значения
переменных chin и nin будут переданы функции. Функция имеет две переменные для
хранения переданных значений. Типы и имена этих переменных указаны в прототипе
при определении функции: char ch и int n. Переменные ch и n инициализируются
переданными в функцию значениями, после чего доступ к ним осуществляется так
же, как и к другим переменным функции. Способ передачи аргументов, при котором
функция создает копии передаваемых значений, называется передачей аргументов
по значению.
Когда выполнение функции завершается, она может возвратить значение
программе, которая ее вызвала. Рассмотрим пример, демонстрирующий применение
функции, получающей значение веса в фунтах и возвращающей эквивалентное
значение этого веса в килограммах:
float
funtkg(float);
int
main()
{
float
ft, kgs;
cout
<< "\nВведите вес в фунтах: ";
cin
>> ft;
kgs
= funtkg(ft);
cout << "Вес в килограммах
равен " << kgs << endl;
return
0;
}
float
funtkg( float pn)
{
float
kg = 0.453592 * pn;
return
kg;
}
Если функция возвращает значение, тип этого значения должен
быть определен. Тип возвращаемого значения указывается перед именем функции при
объявлении и определении функции.
Если функция возвращает значение, то вызов функции
рассматривается как выражение, значение которого равно величине, возвращаемой
функцией.
Оператор return. Функция funtkg() получает в качестве аргумента
значение веса, выраженное в фунтах, которое хранится в параметре pn. Значение
переменной kg затем возвращается программе с помощью оператора return kg;
Обратите внимание на то, что значение веса в килограммах
хранится как в функции funtkg(), так и в функции main(), соответственно в
переменных kg и kgs. В момент возвращения функцией значения происходит
копирование значения переменной kg в переменную kgs. Программа может получить
значение переменной kg только через механизм возврата значения; доступ к самой
переменной kg из программы невозможен.
Количество аргументов у функции может быть сколь угодно
большим, но возвращаемое значение всегда только одно. Однако есть способы,
позволяющие возвращать и несколько значений при помощи функций. Одним из таких
способов является передача аргументов по ссылке. Другой способ - вернуть
структурную переменную, в полях которой будут располагаться нужные значения.
Ссылки на аргументы. Ссылка является псевдонимом, или альтернативным именем переменной.
Одним из наиболее важных применений ссылок является передача аргументов в
функции.
Когда осуществляется передача по значению, вызываемая функция
создает новые переменные, имеющие те же типы, что и передаваемые аргументы, и
копирует значения аргументов в эти переменные. Функция не имеет доступа к
переменным-аргументам, а работает со сделанными ей копиями значений.
Разумеется, такой механизм полезен в тех случаях, когда у функции нет
необходимости изменять значения аргументов, и, соответственно, аргументы
защищены от несанкционированного доступа.
Передача аргументов по ссылке происходит по другому
механизму. Вместо того чтобы передавать функции значение переменной, ей
передается ссылка на эту переменную.
Важной особенностью передачи аргументов по ссылке является
то, что функция имеет прямой доступ к значениям аргументов. К достоинствам
ссылочного механизма также относится возможность возвращения функцией программе
не одного, а множества значений.
Передача по ссылке аргументов стандартных типов. Следующий пример демонстрирует
передачу по ссылке обычной переменной. Программа выделяет целую и дробную часть
введенного числа.
void intfrac(float, float&, float&);
// прототип
int
main()
{
float
number, intpart, fracpart;
do {
cout << "\nВведите
вещественное число:";
cin >> number;
intfrac(number, intpart, fracpart); // нахождение целой и
дробной части
cout << "Целая часть равна
" << intpart << ", дробная часть равна " <<
fracpart << endl;
}
while(number!=0.0);
return
0;
}
void
intfrac(float n, float& intp, float& fracp)
{
long
temp = static_cast<long>(n);
intp
= static_cast<float>(temp);
fracp = n – intp;
}
Функция main() запрашивает у пользователя значение типа
float. Затем программа выделяет целую и дробную часть введенного числа. Для
выделения целой части числа используется функция intfrac().Она выделяет из
числа целую часть путем преобразования значения, переданного параметру n, в
переменную типа long с использованием явного приведения типов: long temp = static_cast<long>(n);
В результате переменная temp получит целую часть введенного
числа, поскольку при преобразовании вещественного значения к целому дробная часть
будет отсечена. Затем целая часть снова преобразовывается в тип float:
intp =
static_cast<float>(temp);
Дробная часть числа представляет собой разность между
исходным числом и его целой частью.
Функция intfrac() вычисляет целую и дробную части числа, но
каким образом она теперь сможет возвратить найденные значения в функцию main()?
Ведь с помощью оператора return можно возвратить только одно значение. Проблема
решается путем использования ссылочного механизма. Заголовок функции intfrac()
выглядит следующим образом:
void intfrac(float n, float& intp, float& fracp)
Аргументы, передаваемые по ссылке, обозначаются знаком &,
который следует за типом аргумента.
Знак & означает,
что intp является псевдонимом для той переменной, которая будет передана в
функцию в качестве аргумента. Другими словами, функции intfrac(), используя имя
intp, фактически оперирует значением переменной intpart функции main(). Знак
& (амперсанд) символизирует ссылку, поэтому запись: float& intp означает, что intp является ссылкой на
переменную типа float. Точно так же fracp является ссылкой на переменную типа
float, в нашем случае - на переменную fracpart.
Если какие-либо аргументы передаются в функцию по ссылке, то
в прототипе функции необходимо с помощью знака & указывать соответствующий
параметр-ссылку:
void intfrac(float, float&, float&);
Аналогичным образом знаком & отмечаются ссылочные
параметры в определении функции. Однако обратите внимание на то, что при
вызовах функции амперсанды отсутствуют:
intfrac(number, intpart, fracpart); //амперсанды отсутствуют
При вызове функции нет смысла сообщать, будет ли передан
аргумент по значению или по ссылке.
В то время как переменные intpart и fracpart передаются в
функцию по ссылке, переменная number передается по значению. Имена intp и
intpart связаны с одной и той же областью памяти, так же как и пара имен fracp
и fracpart. Параметр n не связан с переменной, передаваемой ему в качестве
аргумента, поэтому ее значение копируется в переменную n..
Усложненный вариант передачи по ссылке. Рассмотрим пример, демонстрирующий
менее очевидное применение механизма передачи аргументов по ссылке. Представьте
ситуацию, когда программе необходимо работать с парами чисел, упорядоченными по
возрастанию. Для этого необходимо создать функцию, которая сравнивала бы между
собой значения двух переменных, и, если первое оказывается больше второго,
меняла бы их местами.
int
main()
{
void
order( int&, int& );
int
n1=99, n2=11;
int
n3=22, n4=88;
order(n1,n2);
order(n3,n4);
cout
<< "n1=" << n1 << endl;
cout
<< "n2=" << n2 << endl;
cout
<< "n3=" << nЗ << endl;
cout
<< "n4=" << n4 << endl;
return
0;
}
void
order( int& numb1, int& numb2 )
{
if
( numb1 > numb2 )
{
int
temp = numb1; numb1 = numb2; numb2 = temp;
}
}
В функции main() две пары чисел, причем первая из них не
упорядочена, а вторая - упорядочена. Функция order() обрабатывает каждую из
этих пар, после чего результат выводится на экран.
Имена numb1 и numb2 указывают на физически различные
переменные, в то время как аргументы функции могут быть и одинаковыми. При
первом вызове функции order() упорядочивается пара значений n1 и n2, а при
втором вызове - пара значений n2 и nЗ. Таким образом, действие программы заключается
в проверке упорядоченности исходных значений и, при необходимости, их
сортировке.
Ссылочный механизм напоминает устройство дистанционного
управления: вызывающая программа указывает функции переменные, которые нужно
обработать, а функция обрабатывает эти переменные, даже не зная их
настоящих имен.
Перегруженная функция выполняет различные действия, зависящие от типов данных,
передаваемых ей в качестве аргументов.
Действительно, каким образом функция распознает, какие из
действий необходимо совершить над теми или иными данными. Для того чтобы понять
суть этого механизма, разберем несколько примеров.
Переменное число аргументов функции. Вспомним функции starline() и
repchar(). Функция starline() выводила на экран линию из 45 символов *, а
функция repchar() выводила заданный символ заданное число раз, используя два
аргумента. Можно создать еще одну функцию, которая всегда печатает 45 символов,
но позволяет задавать печатаемый символ. Все эти функции выполняют схожие
действия, но их имена различны. Очевидно, что было бы гораздо удобнее
использовать одно и то же имя для всех трех функций, несмотря на то, что они
используют различные наборы аргументов. Приведем пример программы,
демонстрирующей, каким образом это можно осуществить:
void repchar();
void
repchar(char);
void
repchar(char, int);
int
main()
{
repchar();
repchar('=');
repchar('+',
30);
return
0;
}
void
repchar()
{
for(int
i=0; i<45; i++)
cout
<< '*';
cout
<< endl;
}
void
repchar(char ch)
{
for(int
i=0; i<45; i++)
cout
<< ch;
cout
<< endl;
}
void
repchar(char ch, int n)
{
for(int
i=0; i<n; i++)
cout << ch;
cout << endl;
}
В программе содержатся три функции с одинаковым именем. Каждой
из функций соответствует свое объявление, определение и вызов. Каким же образом
компилятор различает между собой эти три функции? Здесь на помощь приходит
сигнатура функции, которая позволяет различать между собой функции по
количеству аргументов и их типам. Другими словами, объявление void repchar(); и void repchar(char, int); задают
разные функции.
Компилятор C++ обрабатывает функции для каждого из таких
определений. Какая из функций будет выполнена при вызове, зависит от количества
аргументов, указанных в вызове.
Различные типы аргументов. Аналогичным образом можно определить несколько
функций с одинаковыми именами и одинаковым количеством аргументов, но
различными типами этих аргументов. Следующая программа использует перегруженную
функцию для вывода расстояния в виде числа футов и числа дюймов. Аргументом
функции может являться как структурная переменная типа Mera, так и обычная
переменная типа float. В зависимости от типа аргумента, указанного при вызове,
будет исполняться одна из двух функций.
struct
Mera
{
int
fut; float dujm;
};
void
englmera(Mera);
void
englmera(float);
int
main()
{
Mera
d1;
float
d2;
cout << "\nВведите число футов: "; cin >> d1.fut;
cout << "Введите число
дюймов: "; cin >> d1.dujm;
cout << "\n Введите длину
в дюймах: "; cin >> d2;
cout
<< "\nd1 = "; englmera(d1);
cout
<< "\nd2 = "; englmera(d2);
cout
<< endl;
return
0;
}
void
englmera(Mera dd )
{
cout
<<dd.fut <<"\'-" <<<dd.dujm
<<"\"";
}
void
englmera(float dd )
{
int
fut =static_cast<int>(dd/12);
float
dujm =dd -fut*12;
cout
<<fut <<"\’-" <<<dujm
<<"\"";
}
В программе вводятся следующие значения: первое - в виде
отдельно вводимых количества футов и количества дюймов, а второе - в виде
количества дюймов. Программа вызывает перегруженную функцию englmera() для
того, чтобы вывести первое значение, имеющее тип Mera(), и второе значение,
имеющее тип float.
Обратите внимание на то, что, несмотря на одинаковый
результат работы различных версий функции englmera(), код самих функций
различен.
Использование перегруженных функций упрощает процесс
разработки программы. Примером того, что в отсутствие перегрузки программа
становится очень сложной, может служить вычисление абсолютной величины числа с
помощью библиотечных функций C++. Для каждого типа данных должна существовать
своя функция, вычисляющая модуль. Так, для величин типа int существует функция
abs(), для величин комплексного типа - функция cabs(), для величин типа double
- функция fabs() и для величин типа long - функция labs(). С помощью перегрузки
в C++ единственная функция с именем abs() обрабатывает значения всех указанных
типов.
Как увидим позже, механизм перегрузки функций полезен при
обработке различных типов объектов.
Рекурсия. Существование функций делает возможным использование такого
средства программирования, как рекурсия. Рекурсия позволяет функции вызывать
саму себя на выполнение. Это может показаться несколько неправдоподобным, и на
самом деле зачастую обращение функции к самой себе вызывает ошибку, но при
правильном использовании механизм рекурсии может оказаться очень мощным
инструментом.
Рассмотрим пример программы для подсчета факториала числа
unsigned
long factf (unsigned long);
int
main()
{
int
n;
unsigned
long fact;
cout << "Введите целое
число :"; cin >> n;
fact = factf(n);
cout << "Факториал числа
" << n << "равен " << fact << endl;
return
0;
}
unsigned
long factf(unsigned long n)
{
if(n
> 1)
return
n * factf(n-l); else return 1;
}
Функция main() не содержит ничего необычного: в ней
производится вызов функции factf(), где в качестве аргумента используется
значение, введенное пользователем. Функция factf() возвращает функции main()
вычисленное значение факториала.
Совсем по-другому обстоит дело с содержимым функции factf().
В случае, если параметр n оказывается больше 1, происходит вызов функцией
factf() самой себя, при этом используется значение аргумента на единицу
меньшее, чем текущее значение параметра. Если функция main() вызвала функцию
factf() с аргументом, равным 5, то сначала функция factf() вызовет себя с
аргументом, равным 4, затем этот вызов обратится к функции factf() с
аргументом, равным 3, и т. д. Обратите внимание на то, что каждый экземпляр
функции хранит свое значение параметра n во время выполнения рекурсивного
вызова.
После того как функция factf() вызовет себя четырежды, пятый
вызов будет производиться с аргументом, равным 1. Функция узнает об этом с
помощью оператора if и, вместо рекурсивного вызова, возвратит четвертому вызову
значение, равное 1.
Каждая рекурсивная функция должна включать в себя условие
окончания рекурсии. В противном случае рекурсия будет происходить бесконечно,
что приведет к аварийному завершению программы. Ветвление if в функции factf()
играет роль условия, прекращающего рекурсию, как только параметр достигнет
значения, равного 1.
Будут ли все рекурсивные вызовы функции храниться в памяти?
Это не совсем так. В памяти будут находиться значения параметров для каждого из
вызовов, однако тело функции будет присутствовать в памяти в единственном
экземпляре.
Здесь рассмотрим два аспекта, касающихся взаимодействия
переменных и функций: область видимости и класс памяти. Область
видимости определяет, из каких частей программы возможен доступ к
переменной, а класс памяти - время, в течение которого переменная
существует в памяти компьютера.
Рассмотрим типы области видимости: локальная область
видимости, область видимости файла, область видимости класса.
Переменные, имеющие локальную область видимости, доступны внутри
того блока, в котором они определены.
Переменные, имеющие область видимости файла, доступны из
любого места файла, в котором они определены.
У переменных, имеющих класс памяти automatic, время жизни
равно времени жизни функции, внутри которой они определены.
У переменных, имеющих класс памяти static, время жизни равно
времени жизни всей программы.
Локальные переменные. До сих пор в наших примерах определение переменных
происходило внутри фигурных скобок, ограничивающих тело функции.
Переменные, определяемые внутри функции, называют локальными,
поскольку их область видимости ограничивается этой функцией. Иногда такие
переменные называют автоматическими, поскольку они имеют класс памяти
automatic.
Рассмотрим область видимости и классы памяти локальных
переменных. Локальная переменная не существует в памяти до тех пор, пока не
будет вызвана функция, в которой она определена. Когда управление передается в
функцию, переменные создаются и под них отводится место в памяти. После
завершения выполнения функции, переменные уничтожаются, а их значения теряются.
Название автоматического класса памяти как раз указывает на то, что переменные
автоматически создаются при входе в функцию и автоматически уничтожаются при
выходе из нее. Период времени между созданием переменной и ее уничтожением
называется временем жизни переменной. Время жизни локальной переменной равно
времени, проходящему с момента объявления переменной в теле функции до
завершения исполнения функции. В случае, если функция не вызывается, нет
необходимости резервировать память под ее локальные переменные. Удаление
локальных переменных из памяти при завершении функции позволяет распределить
освободившуюся память между локальными переменными других функций.
Область видимости переменной определяет участки программы, из
которых возможен доступ к этой переменной. Это означает, что внутри области
видимости переменной операторы могут обращаться к ней по имени и использовать
ее значение при вычислении выражений. За пределами области видимости попытки
обращения к переменной приведут к выдаче сообщения о том, что переменная
неизвестна.
Переменные, определенные внутри функции, видимы, или
доступны, только внутри функции, в которой они определены. Предположим, что в
вашей программе есть две следующие функции:
void sometime()
{
int somevar; // локальные переменные
float othervar;
somevar =10; // корректно
othervar =11; // корректно
nextvar = 12; // некорректно; nextvar
невидима в somefunc()
}
void otherfunc()
{
int nextvar; // локальная переменная
somevar = 20; // некорректно: somevar
и othervar
othervar =21; // невидимы в
otherfunc()
nextvar = 22; // корректно
}
Переменная nextvar невидима внутри функции somefunc(), а
переменные somevar и othervar невидимы внутри функции otherfunc().
Ограничение области видимости переменной обеспечивает
организованность программы и ее модульность. Можно быть уверенным, что
переменные одной функции защищены от несанкционированного доступа других
функций, поскольку во всех остальных функциях эти переменные невидимы.
Ограничение области видимости переменных является важным и для объектно-ориентированного
программирования.
Глобальные переменные. В отличие от локальных переменных, глобальные
переменные определяются вне каких-либо функций. Глобальная переменная видна из
всех функций программы, которые определены позже, чем сама глобальная
переменная. Как правило, необходимо, чтобы глобальные переменные были видимы из
всех функций, поэтому их определения располагают в начале программы.
Рассмотрим пример, демонстрирующей использование глобальных
переменных.
char
ch = 'a';
void
getachar();
void
putachar();
int
main()
{
while
( ch != '\r' )
{
getachar();
putachar();
}
cout
<< endl;
return
0;
}
void
getachar()
{ch
= getch();}
void
putachar()
{cout
<< ch;
}
Функция getachar(), считывает символы с клавиатуры, используя библиотечную функцию getch. Функция putachar(), отображает вводимые символы на экране.
Особенностью данной программы является то, что переменная ch
не принадлежит какой-либо из функций, а определяется в начале файла, перед
объявлениями функций, т.е.является глобальной.
Глобальные переменные используются в тех случаях, когда их
значения должны быть доступны одновременно нескольким функциям. Тем не менее
всеобщая доступность глобальных переменных имеет и негативную сторону: они не
защищены от несанкционированного и некорректного доступа со стороны функций. В
программах, написанных с применением объектно-ориентированного подхода,
необходимость в глобальных переменных гораздо меньше.
Инициализация. Если глобальная переменная инициализируется явно: int exvar = 199; то подобная
инициализация происходит в момент загрузки программы. Если явной инициализации
не происходит, то в момент создания этой переменной ей автоматически будет
присвоено значение, равное 0.
Область видимости и время жизни. Глобальные переменные имеют
статический класс памяти, что означает их существование в течение всего времени
выполнения программы. Память под эти переменные выделяется в начале выполнения
программы и закрепляется за ними вплоть до завершения программы. Статический
класс памяти определен для этих переменных по умолчанию.
Статические локальные переменные. Рассмотрим еще один тип переменных,
называемых статическими локальными переменными.
Статическая локальная переменная имеет такую же область
видимости, как и автоматическая: функцию, к которой принадлежит данная
переменная. Однако время жизни у статической локальной переменной совпадает со
временем жизни глобальной переменной с той разницей, что существование
статической локальной переменной начинается при первом вызове функции, к
которой она принадлежит. Далее переменная существует на всем протяжении
выполнения программы.
Статические локальные переменные используются в тех случаях,
когда необходимо сохранить значение переменной в памяти после того, как
выполнение функции будет завершено, или, другими словами, между вызовами
функций. В следующем примере функция getavg() подсчитывает среднее
арифметическое значений, полученных ею, с учетом нового переданного ей
значения. Функция запоминает количество переданных ей значений и с каждым новым
вызовом увеличивает это значение на единицу. Функция возвращает среднее
арифметическое, полученное путем деления суммы всех переданных значений на
количество этих значений.
float
getavg(float);
int
main()
{
float
data=1, avg;
while
( data != 0 )
{
cout << "Введите
число: "; cin >> data;
avg = getavg(data);
cout << "Среднее значение:
" << avg << endl;
}
return
0;
}
float
getavg(float newdata)
{
static
float total = 0;
static
int count =0;
count++;
total
+= newdata;
return
total/count;
}
Статические переменные total и count функции getavg()
сохраняют свои значения после завершения этой функции, поэтому при следующем
вызове этими значениями снова можно пользоваться.
Инициализация статических переменных происходит один раз - во
время первого вызова функции. При последующих вызовах повторной инициализации
не происходит, как это должно было бы произойти с обычными локальными
переменными.
Класс памяти. Локальные переменные и аргументы функций хранятся в стеке, а
глобальные и статические переменные находятся в динамической области памяти.
Теперь перейдем к рассмотрению еще одного аспекта
программирования на C++, который, возможно, покажется несколько нестандартным.
Подобно тому, как можно передавать в функцию аргументы с помощью ссылок, можно
и возвращать значение функции по ссылке. Одной из причин является необходимость
избежать копирования объектов большого размера. Другой причиной является
открывающаяся возможность использовать вызов функции в качестве левого операнда
операции присваивания. Приведем пример программы, иллюстрирующей данный
механизм:
int x;
int& setx();
int
main()
{
setx()
= 92;
cout
<< "х="
<< х
<< endl;
return
0;
}
int&
setx()
{
return
x;
}
Функция setx(), согласно своему прототипу, имеет тип
возвращаемого значения int& setx();
Внутри функции содержится оператор return x;, где переменная х была определена как глобальная. В
результате выполнения строки setx() = 92; переменной, возвращаемой функцией,
присваивается значение, стоящее справа от знака равенства.
Ссылочный механизм передачи аргументов в функцию позволяет
функции изменять значения аргументов. Но существуют и другие причины для
использования ссылок на аргументы функции. Некоторые переменные, используемые в
качестве аргументов функции, могут иметь большой размер; пример - структурные
переменные с большим числом полей. В этом случае его передача по ссылке
является гораздо более эффективной, поскольку в функцию передается не значение
переменной, а только ее адрес.
Предположим, что используете ссылочный механизм только из
соображений эффективности и при этом не нужно, чтобы функция имела свободный
доступ к аргументу и изменяла его значение. Для этого перед соответствующим
аргументом в прототипе функции нужно указать модификатор const. Рассмотрим пример.
void
aFunc( int& a, const int& b);
int
main()
{
int
alpha = 7; int beta = 11; aFunc(alpha, beta);
return
0;
}
void
aFunc(int& a, const int& b)
{
a = 107; // корректно
b = 111; // ошибка при попытке изменить
константный аргумент
}
В данной программе функция aFunc() не сможет изменить
значения переменной beta, потому что использован модификатор const с переменной
beta в прототипе функции и ее определении: void aFunc(int& alpha, const int& beta);.
Теперь любая попытка изменить содержимое переменной beta
функцией aFunc() повлечет за собой сообщение об ошибке от компилятора. Одним из
положений идеологии разработчиков C++ является такое: лучше обнаруживать ошибки
в программе на этапе ее компиляции, чем во время ее выполнения. Использование
модификатора const демонстрирует этот принцип в действии.
Если необходимо передать в функцию константную переменную с
помощью ссылки нужно использовать модификатор const в прототипе функции.
Определение константной переменной гарантирует, что ни функция, ни любые
операторы программы не способны изменить ее значения.
Многие библиотечные функции используют константные аргументы
подобным образом.
Функции позволяют обеспечить внутреннюю организованность
программы и сократить размер ее кода, присваивая повторяющимся фрагментам
программы имя и заменяя этим именем все повторения, встречающиеся в программе.
Объявление, или прототип функции задает общий вид функции, вызов функции
передает управление в функцию, а определение функции задает совокупность
действий, выполняемых функцией. Первая строка в определении функции называется
спецификатором функции.
Передача аргументов в функцию может осуществляться по
значению и по ссылке. В первом случае функция работает с копией значения
аргумента, во втором функции доступна сама переменная, передаваемая в качестве
аргумента.
Функция может возвращать только одно значение. Как правило,
функция производит возврат по значению, но возможен и возврат по ссылке, что
позволяет использовать вызов функции в качестве левого операнда операции
присваивания.
Перегруженная функция представляет собой группу функций,
имеющих одно и то же имя. Какая из функций выполняется при вызове, зависит от
количества указанных в вызове аргументов, а также их типов.
Встроенные функции внешне похожи на обычные функции, но при
вызовах их код вставляется непосредственно в исполняемый код программы.
Встроенные функции исполняются быстрее, но могут занимать в памяти больше
места, чем обычные функции, если только размер встроенных функций не является
очень маленьким.
Если в функции используются значения аргументов по умолчанию,
то при вызове функции не обязательно указывать значения этих аргументов. Вместо
отсутствующих значений будут использоваться значения по умолчанию.
Переменные имеют характеристику, которая называется класс
памяти. Наиболее простым и распространенным классом памяти является
автоматический. Локальные переменные имеют автоматический класс памяти: они
существуют только до тех пор, пока не завершится исполнение вызова функции.
Кроме того, эти переменные видны только внутри тела функции. Глобальные
переменные имеют статический класс памяти: время их существования определяется
временем выполнения программы. Кроме того, глобальные переменные видимы во всем
исходном файле, начиная с места их объявления. Статические локальные переменные
существуют на всем протяжении процесса выполнения программы, но область их
видимости ограничена той функцией, к которой они принадлежат.
Функция не может изменять значений тех аргументов, которые
описаны в ее прототипе с модификатором const. Переменная, определенная в
вызывающей функции как константная, автоматически защищена от изменения
функцией.