ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 31.03.2021

Просмотров: 331

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Функции, часть первая: ОСНОВЫ


В этой главе мы приступаем к углубленному рассмотрению функций. Функции — это строительные блоки C++, а потому без полного их понимания невозможно стать успешным С++-программистом. Данная тема включает рассмотрение правил действия областей видимости функций, рекурсивных функций, некоторых специальных свойств функции main(), инструкции return и прототипов функций.


Локальные переменные


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

Локальную переменную могут использовать лишь инструкции, включенные в блок, в котором эта переменная объявлена. Другими словами, локальная переменная неизвестна за пределами собственного блока кода. Следовательно, инструкции вне блока не могут получить доступ к объекту, определенному внутри блока.

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

Самым распространенным программным блоком является функция. В C++ каждая функция определяет блок кода, который начинается с открывающей фигурной скобки этой функции и завершается ее закрывающей фигурной скобкой. Код функции и ее данные — это ее "частная собственность", и к ней не может получить доступ ни одна инструкция из любой другой функции, за исключением инструкции ее вызова. (Например, невозможно использовать инструкцию goto для перехода в середину кода другой функции.) Тело функции надежно скрыто от остальной части программы, и если в функции не используются глобальные переменные, то она не может оказать никакого влияния на другие части программы, равно, как и те на нее. Таким образом, содержимое одной функции совершенно независимо от содержимого другой. Другими словами, код и данные, определенные в одной функции, не могут взаимодействовать с кодом и данными, определенными в другой, поскольку две функции имеют различные области видимости.

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



#include <iostream>

using namespace std;

void f1();

int main()

{

 char str[] = "Это - массив str в функции main().";

 cout << str << '\n';

 f1();

 cout << str << '\n';

 return 0;

}


void f1()

{

 char str[80];

 cout << "Введите какую-нибудь строку: ";

 cin >> str;

 cout << str << '\n';

}


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


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

Чтобы лучше понять вышесказанное, рассмотрим следующую программу.


/* Эта программа демонстрирует локальность переменных по отношению к блоку.


*/


#include <iostream>

#include <cstring>

using namespace std;

int main()

{

 int choice;

 cout << "(1) сложить числа или ";

 cout << "(2) конкатенировать строки?: ";

 cin >> choice;

 if(choice == 1) {

  int a, b; /* Активизируются две int-переменные. */

  cout << "Введите два числа: ";

   cin >> а >> b;

  cout << "Сумма равна " << a+b << '\n';

 }

 else {

  char s1 [80], s2[80]; /* Активизируются две строки. */

  cout << "Введите две строки: ";

  cin >> s1;

  cin >> s2;

  strcat(s1, s2);

  cout << "Конкатенация равна " << s1 << '\n';

 }

 return 0;

}


Эта программа в зависимости от выбора пользователя обеспечивает ввод либо двух чисел, либо двух строк. Обратите внимание на объявление переменных а и b в if-блоке и переменных s1 и s2 в else-блоке. Существование этих переменных начнется с момента входа в соответствующий блок и прекратится сразу после выхода из него. Если пользователь выберет сложение чисел, будут созданы переменные а и b, а если он захочет конкатенировать строки— переменные s1 и s2. Наконец, ни к одной из этих переменных нельзя обратиться извне их блока, даже из части кода, принадлежащей той же функции. Например, если вы попытаетесь скомпилировать следующую (некорректную) версию программы, то получите сообщение об ошибке.



/* Эта программа некорректна. */


#include <iostream>

#include <cstring>

using namespace std;

int main()

{

 int choice;

 cout << "(1) сложить числа или ";

 cout << "(2) конкатенировать строки?: ";

  cin >> choice;

 if(choice == 1) {

  int a, b; /* Активизируются две int-переменные. */

  cout << "Введите два числа: ";

   cin >> а >> b;

  cout << "Сумма равна " << a+b << '\n';

 }

 else {

  char s1 [80], s2 [80]; /* Активизируются две строки. */

  cout << "Введите две строки: ";

   cin >> s1;

   cin >> s2;

  strcat (s1, s2);

  cout << "Конкатенация равна " << s1 << '\n';

 }

 a = 10; // *** Ошибка ***

 // Переменная а здесь неизвестна!

 return 0;

}


Поскольку в данном случае переменная а неизвестна вне своего if-блока, компилятор выдаст ошибку при попытке ее использовать.

Если имя переменной, объявленной во внутреннем блоке, совпадает с именем переменной, объявленной во внешнем блоке, то "внутренняя" переменная переопределяет "внешнюю" в пределах области видимости внутреннего блока. Рассмотрим пример.


#include <iostream>

using namespace std;

int main()

{

 int i, j;

 i = 10;

 j = 100;

 if(j > 0) {

  int i; // Эта переменная i отделена от внешней переменной i.

  i = j /2;

  cout << "Внутренняя переменная i: " << i << '\n';

 }

 cout << "Внешняя переменная i: " << i << '\n';

 return 0;

}


Вот как выглядят результаты выполнения этой программы.


Внутренняя переменная i: 50


Внешняя переменная i: 10

Здесь переменная i, объявленная внутри if-блока, переопределяет, или скрывает, внешнюю переменную i. Изменения, которым подверглась внутренняя переменная i, не оказывают никакого влияния на внешнюю i. Более того, вне if-блока внутренняя переменная i больше не существует, и поэтому внешняя переменная i снова становится видимой.

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

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

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

Как упоминалось выше, несмотря на то, что локальные переменные обычно объявляются в начале своего блока, это не является обязательным. Локальные переменные можно объявить в любом месте блока, главное, чтобы это было сделано до их использования. Например, следующая программа вполне допустима.


#include <iostream>

using namespace std;

int main()

{

 cout << "Введите число: ";

 int a; // Объявляем одну переменную.

 cin >> a;

 cout << "Введите второе число: ";


 int b; // Объявляем еще одну переменную.

 cin >> b;

 cout << "Произведение равно: " << а*Ь << '\n';

 return 0;

}


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


Объявление переменных в итерационных инструкциях и инструкциях выбора

Переменную можно объявить в разделе инициализации цикла for или условном выражении инструкций if, switch или while. Переменная, объявленная в одной из этих инструкций, имеет область видимости, которая ограничена блоком кода, управляемым этой инструкцией. Например, переменная, объявленная в инструкции цикла for, будет локальной для этого цикла, как показано в следующем примере.


#include <iostream>

using namespace std;

int main()

{

 // Переменная i локальная для цикла for.

 for(int i=0; i<10; i++) {

  cout << i << " ";

  cout << "в квадрате равно " << i * i << "\n";

 }

 // i = 10; // *** Ошибка *** -- i здесь неизвестна!

 return 0;

}


Здесь переменная i объявляется в разделе инициализации цикла for и используется для управления этим циклом. А за пределами цикла переменная i неизвестна.

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

Важно! Утверждение о том, что переменная, объявленная в разделе инициализации цикла for, является локальной по отношению к этому циклу или не является таковой, изменилось со временем (имеется в виду время, в течение которого развивался язык C++). Первоначально такая переменная была доступна после выхода из цикла for. Однако стандарт C++ ограничивает область видимости этой переменной рамками цикла for. Но следует иметь в виду, что различные компиляторы и теперь по-разному "смотрят" на эту ситуацию.

Если ваш компилятор полностью соблюдает стандарт C++, то вы можете также объявить переменную в условном выражении инструкций if, switch или while. Например, в следующем фрагменте кода


if(int х = 20) {

 cout << "Это значение переменной х: ";

 cout << х;

}


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



Формальные параметры


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

Область видимости параметра ограничивается рамками его функции.

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

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


Глобальные переменные


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

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

Использование глобальных переменных демонстрируется в следующей программе. Как видите, переменные count и num_right объявлены вне всех функций, следовательно, они— глобальные. Из обычных практических соображений лучше объявлять глобальные переменные поближе к началу программы. Но формально они просто должны быть объявлены до их первого использования. Предлагаемая для рассмотрения программа— всего лишь простой тренажер по выполнению арифметического сложения. Сначала пользователю предлагается указать количество упражнений. Для выполнения каждого упражнения вызывается функция drill(), которая генерирует два случайных числа в диапазоне от 0 до 99. Пользователю предлагается сложить эти числа, а затем проверяется ответ. На каждое упражнение дается три попытки. В конце программа отображает количество правильных ответов. Обратите особое внимание на глобальные переменные, используемые в этой программе.


// Простая программа-тренажер по выполнению сложения.

#include <iostream>

#include <cstdlib>

using namespace std;

void drill();

int count; // Переменные count и num_right — глобальные.

int num_right;

int main()

{

 cout << "Сколько практических упражнений: ";