Освой самостоятельно С++ за 21 день - Джесс Либерти 12 стр.


Блок, заключенный в фигурные скобки, начинается в строке 25, и в строке 26 сно­ва выводится значение локальной переменной x. Но в строке 28 создается новая пе­ременная с таким же именем x, которая является локальной по отношению к данному блоку. Эта переменная тут же инициализируется значением 9.

Значение последней созданной переменной x выводится на экран в строке 30. Ло­кальный блок завершается строкой 31, и переменная, созданная в строке 28, выходит за пределы видимости и удаляется из памяти.

В строке 33 на экран выводится значение той переменной x, которая была объяв­лена в строке 22. На нее никоим образом не повлияло определение новой переменной x в строке 28, и ее значение по-прежнему равно 8.

В строке 34 заканчивается область видимости функции MyFunc() и ее локальная пе­ременная x становится недоступной. Управление программой возвращается к стро­ке 15, в которой выводится значение локальной переменной -x, созданной в строке 10. Вы сами можете убедиться в том, что на нее не повлияла ни одна из одноименных переменных, определенных в функции MyFunc().

Нужно ли специально говорить о том, что эта программа была бы гораздо менее путаной, если бы все три переменные имели уникальные имена!

Операторы, используемые в функциях

Фактически на количество или типы операторов, используемых в функциях, ника­ких ограничений не накладывается. И хотя внутри функции нельзя определить другую функцию, но из одной функции можно вызывать сколько угодно других функций; именно этим и занимается функция main() почти в каждой программе C++. Более того, функции могут вызывать даже самих себя (эта ситуация рассматривается в раз­деле, посвященном рекурсии).

Хотя на размер функции в C++ также никакого ограничения не накладывается, лучше, чтобы тело функции не разрасталось до неограниченных масштабов. Многие специалисты советуют сохранять небольшой размер функций, занимающий одну страницу экрана, по­зволяя тем самым видеть всю функцию целиком. Конечно же, это эмпирическое правило, часто нарушаемое даже очень хорошими программистами, но следует помнить: чем мень­ше функция, тем она проще для понимания и дальнейшего обслуживания.

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

Подробнее об аргументах функций

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

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

Использование функций в качестве параметров функций

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

В качестве примера предположим, что у вас есть функции double(), triple(), square() и cube(), возвращающие некоторое значение. Вы могли бы записать следующую инструкцию:

Answer = (double(triple(square(cube(myValue)))));

Эта инструкция принимает переменную myValue и передает ее в качестве аргумента функции cube(), возвращаемое значение которой (куб числа) передается в качестве аргумента функции square(). После этого возвращаемое значение функции square() (квадрат числа), в свою очередь, передается в качестве аргумента функции triple(). Затем значение возврата функции triple() (утроенное число) передается как аргумент функции double(). Наконец, значение возврата функции double() (удвоенное число) присваивается переменной Answer.

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

В качестве альтернативного варианта можно было бы каждый промежуточный результат вычисления присваивать промежуточной переменной:

unsigned long myValue = 2;

unsigned long cubed = cube(myValue); // 2 в кубе = 8

unsigned long squared = square(cubed); // 8 в квадрате = 64

unsigned long tripled = triple(squared); // 64 * 3 = 192

unsigned long Answer = double(tripled); // 192 *2 = 384

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

Параметры - это локальные переменные

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

Листинг 5.5. Передача параметров как значений

1: // Листинг 5.5. Передача параметров как значений

2:

3: #include <iostream.h>

4:

5: void swap(int x, int у);

6:

7: int main()

8: {

9: int x = 5, у = 10;

10:

11: cout << "Main. Before swap, x: " << x << " у: " << у << "\n";

12: swap(x,y);

13: cout << "Main. After swap, x: " << x << " у: " << у << "\n";

14: return 0;

15: }

16:

17: void swap (int x, int у)

18: {

19: int temp;

20:

21: cout << "Swap. Before swap, x: " << x << " у: " << у << "\n";

22:

23: temp = x;

24: x = у;

25: у = temp;

26:

27: cout << "Swap. After swap, x: " << x << " у: и << у << "\n";

28:

29: }

Результат:

Main. Before swap, x: 5 y 10

Swap. Before swap, x: 5 y: 10

Swap. After swap, x: 10 y: 5

Main. After swap, x: 5 y: 10

Анализ: В программе инициализируются две переменные в функции main(), а затем их значения передаются в функцию swap(), которая, казалось бы, должна поменять их значения. Однако после повторной проверки этих переменных в функции main() оказывается, что они не изменились.

Эти переменные инициализируются в строке 9, а отображение их значений на эк­ране выполняется в строке 11. Затем вызывается функция swap(), и эти переменные передаются ей в качестве аргументов.

Выполнение программы переносится в функцию swap(), где в строке 21 снова выводятся значения тех же, уже знакомых нам переменных. Как и ожидалось, их значения от передачи в функцию не изменились. В строках 23-25 переменные ме­няются своими значениями, что подтверждается очередной проверкой в строке 27. Но это положение сохраняется лишь до тех пор, пока программа не вышла из функции swap().

Затем управление программой передается строке 13, принадлежащей функции main(), которая показывает, что переменные получили назад свои исходные значения и все изменения, произошедшие в функции, аннулированы!

Напомним, что в данном случае переменные передаются в функцию swap() как значения, т.е. в функции swap() были созданы копии этих значений, которые являют­ся локальными по отношению к этой функции. Обмен значениями, выполненный в строках 23-25, был реализован на этих локальных переменных, но это никак не по­влияло на переменные, оставшиеся в функции main().

На занятиях 8 и 10 вы узнаете альтернативные способы передачи параметров функциям, которые позволят изменять исходные переменные в функции main().

Подробнее о возвращаемых значениях

Функции возвращают либо реальное значение, либо значение типа void, которое служит сигналом для компилятора, что никакое значение возвращено не будет.

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

return 5;

return (x > 5);

return (MyFunction());

Все приведенные выше выражения являются правомочными установками возврата функций, если исходить из того, что функция MyFunction() сама возвращает некоторое значение. Второе выражение, return (x > 5), будет возвращать false, если x не больше 5, или true, если x больше 5. Таким образом, если в возврате задается логическое выражение, то возвращаются не значения переменной x, а логические значения false или true (ложь или истина).

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

Однако функция может содержать несколько операторов return. Эта идея иллюстрируется в листинге 5.6.

Листинг 5.6. Использование нескольких операторов return

1: // Листинг 5.6. Использование нескольких

2: // операторов return в теле Функции

3:

4: #include<iostream.h>

5:

6: int Doubler(int AmountToDouble);

7:

6: int main()

9: {

10:

11: int result = 0:

12: int input;

13:

14: cout << "Enter а number between 0 and 10,000 to double: ":

15: cin >> input;

16:

17: cout << "\nBefore doubler is called... ";

18: cout << "\ninput: " << input << M doubled: " << result << "\n";

19:

20: result = Doubler(input);

21:

22: cout << "\nBack from Doubler...\n";

23: cout << "\ninput: " << input << " doubled: " << result << "\n";

24:

25:

26: return 0;

27: }

28:

29: int 0oubler(int original)

30: {

31: if (original <= 10000)

32: return original * 2;

33: else

34: return -1;

35: cout << "Vou can't get here!\n";

36: }

Результат:

Enter a number between 0 and 10,000 to double: 9000

Before doubler is called...

input: 9000 doubled: 0

Back from doubler...

input: 9000 doubled: 18000

Enter a number between 0 and 10.000 to double: 11000

Before doubler is called...

input: 11000 doubled: 0

Back from doubler...

input: 11000 doubled: -1

Анализ: В строках 14 и 15 программа предлагает пользователю ввести число и coхраняет его в переменной input. В строке 18 отображается только что введенное число вместе со значением локальной переменной result. В строке 20 вызывается функция Doubler() и введенное значение передается ей как параметр. Результат выполнения функции присваивается локальной переменной result, и в строке 23 снова выводятся значения тех же переменных.

В строке 31, относящейся к функции Doubler(), значение переданного параметра сравнивается с числом 10 000. Если окажется, что оно не превышает 10 000, функция возвращает удвоенное значение исходного числа. Если оно больше 10 000, функция возвращает число -1 в качестве сообщения об ошибке.

Выражение в строке 35 никогда не будет достигнуто, потому что при любом значении переданного параметра (большем 10 000 или нет) возврат из функции будет осуществлен либо в строке 32, либо в строке 34, но в любом случае до строки 35. Хороший компилятор сгенерирует предупреждение, что это выражение не может быть выполнено, и хороший программист должен принять соответствующие меры!

Вопросы и ответы

В чем состоит разница между объявлениями int main() и void main() и какое из них лучше использовать? Ведь оба варианта работают одинаково хорошо, поэтому стоит ли применять первый вариант int main(){ return 0; }?

Оба объявления будут работать с большинством компиляторов, но только вариант int main() является ANSI-совместимым, следовательно, только объявление int main() гарантирует работу программы.

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

И хотя вы вряд ли будете использовать возвращаемое значение, стандарт ANSI требует его присутствия.

Значения параметров, используемые по умолчанию

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

long myFunction(int);

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

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

long myFunction (int x = 50);

Этот прототип нужно понимать следующим образом. Функция myFunction возвращает значение типа long и принимает параметр типа int. Но если при вызове этой функции аргумент предоставлен не будет, используйте вместо него число 50. А поскольку в прототипах функций имена параметров не обязательны, то последний вариант объявления можно переписать по-другому:

long myFunction (int = 50);

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

long myFunction (int x)

Если при вызове этой функции аргумент не устанавливается, то компилятор присвоит переменной x значение 50. Имя параметра, для которого в прототипе устанавливается значение по умолчанию, может не совпадать с именем параметра, указываемого в заголовке функции: значение, заданное по умолчанию, присваивается по позиции, а не по имени.

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

long myFunction (int Param1, int Param2, int Param3);

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

Листинг 5.7. Использование значений, заданных по умолчанию для параметров функций

1: // Листинг 5.7. Использование стандартных

2: // значений параметров

3:

4: #include <iostream.h>

5:

6: int VolumeCube(int length, int width = 25, int height = 1);

7:

8: int main()

9: {

10: int length = 100;

11: int width = 50;

12: int height = 2;

13: int volume;

14:

15: volume = VolumeCube(length, width, height);

16: cout << "First volume equals: " << volume << "\n";

17:

18: volume = VolumeCube(length, width);

19: cout << "Second time volume equals: " << volume << "\n";

20:

21: volume = VolumeCube(length);

22: cout << "Third time volume equals: " << volume << "\n";

23: return 0;

24: }

25:

26: VolumeCube(int length, int width, int height)

27: {

28:

29: return (length * width * height);

30: }

Результат:

First volume equals: 10000

Second time volume equals: 5000

Third time volume equals: 2500

Анализ: В прототипе функции VolumeCube() B строке 6 объявляется, что функция принимает три параметра, причем последние два имеют значения, устанавливаемые по умолчанию.

Эта функция вычисляет объем параллелепипеда на основании переданных размеров. Если значение ширины не передано, то ширина устанавливается равной 25, а высота - 1. Если значение ширины передано, а значение высоты нет, то по умолчанию устанавливается только значение высоты. Но нельзя передать в функцию значение высоты без передачи значения ширины.

В строках 10-12 инициализируются переменные, предназначенные для хранения размеров параллелепипеда по длине, ширине и высоте. Эти значения передаются функции VolumeCube() в строке 15. После вычисления объема параллелепипеда результат выводится в строке 16.

В строке 18 функция VolumeCube() вызывается снова, но без передачи значения для высоты. В этом случае для вычисления объема параллелепипеда используется значение высоты, заданное по умолчанию, и полученный результат выводится в строке 19.

При третьем вызове функции VolumeCube() (строка 21) не передается ни значение ширины, ни значение высоты. Поэтому вместо них используются значения, заданные по умолчанию, и полученный результат выводится в строке 22.

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

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

Назад Дальше