С самоучитель. Уроки по с ravesli com 2 предисловие
Скачать 2.56 Mb.
|
Как работают параметры и возвращаемые значения функций? Используя параметры и возвращаемые значения, мы можем создавать функции, которые могут принимать и обрабатывать данные, а затем возвращать результат обратно в caller. Например, простая функция, которая принимает два целых числа и возвращает их сумму: #include // Функция add() принимает два целых числа в качестве параметров и возвращает их сумму // Значения a и b определяет caller int add ( int a , int b ) { return a + b ; } // Функция main() не имеет параметров int main () { std :: cout << add ( 7 , 8 ) << std :: endl ; // аргументы 7 и 8 передаются в функцию add() return 0 ; } При вызове функции add(), параметру a присваивается значение 7, а параметру b — 8. Затем функция add() вычисляет их сумму и возвращает результат обратно в main(). Затем уже результат выводится на экран. Результат выполнения программы выше: 15 Ещё примеры Рассмотрим ещё несколько вызовов функций: #include ravesli.com 85 int add ( int a , int b ) { return a + b ; } int multiply ( int c , int d ) { return c * d ; } int main () { std :: cout << add ( 7 , 8 ) << std :: endl ; // внутри функции add(): a = 7, b = 8, значит a + b = 15 std :: cout << multiply ( 4 , 5 ) << std :: endl ; // внутри функции multiply(): c = 4, d = 5, значит c * d = 20 // Мы можем передавать целые выражения в качестве аргументов std :: cout << add ( 2 + 3 , 4 * 5 ) << std :: endl ; // внутри функции add(): a = 5, b = 20, значит a + b = 25 // Мы можем передавать переменные в качестве аргументов int x = 4 ; std :: cout << add ( x , x ) << std :: endl ; // будет 4 + 4 std :: cout << add ( 1 , multiply ( 2 , 3 )) << std :: endl ; // будет 1 + (2 * 3) std :: cout << add ( 1 , add ( 2 , 3 )) << std :: endl ; // будет 1 + (2 + 3) return 0 ; } Результат выполнения программы выше: 15 20 25 8 7 6 С первыми двумя вызовами всё понятно. В третьем вызове, параметрами являются выражения, которые сначала нужно обработать. 2 + 3 = 5 и результат 5 присваивается переменной a. 4 * 5 = 20 и результат 20 присваивается переменной b. Результатом выполнения функции add(5, 20) является значение 25. ravesli.com 86 Следующая пара относительно легкая: int x = 4 ; std :: cout << add ( x , x ) << std :: endl ; // будет 4 + 4 Здесь уже a = x и b = x. Поскольку x = 4, то add(x, x) = add(4, 4). Результат — 8. Теперь рассмотрим вызов посложнее: std :: cout << add ( 1 , multiply ( 2 , 3 )) << std :: endl ; // будет 1 + (2 * 3) При выполнении этого стейтмента процессор должен определить значения параметров a и b функции add(). З параметром a всё понятно — мы передаём значение 1 (a = 1). А вот чтобы определить значение параметра b, нам необходимо выполнить операцию умножения: multiply(2, 3), результат — 6. Затем add(1, 6) возвращает число 7, которое и выводится на экран. Короче говоря: add(1, multiply(2, 3)) => add(1, 6) => 7 Последний вызов может показаться немного сложным из-за того, что параметром функции add() является другой вызов add(): std :: cout << add ( 1 , add ( 2 , 3 )) << std :: endl ; // будет 1 + (2 + 3) Но здесь всё аналогично примеру выше. Перед тем как процессор вычислит внешний вызов функции add(), он должен обработать внутренний вызов функции add(2, 3). add(2, 3) = 5. Затем процессор обрабатывает функцию add(1, 5), результатом которой является значение 6. Затем 6 передаётся в std::cout. Короче говоря: add(1, add(2, 3)) => add(1, 5) => 6 ravesli.com 87 Тест 1. Что не так со следующим фрагментом кода? #include ( int a , int b ) { return a * b ; } int main () { std :: cout << multiply ( 7 , 8 ) << std :: endl ; return 0 ; } 2. Какие здесь есть две проблемы? #include ( int a , int b ) { int product = a * b ; } int main () { std :: cout << multiply ( 5 ) << std :: endl ; return 0 ; } 3. Какой результат выполнения следующей программы? #include ( int a , int b , int c ) { return a + b + c ; } int multiply ( int a , int b ) { return a * b ; } int main () { std :: cout << multiply ( add ( 3 , 4 , 5 ), 5 ) << std :: endl ; return 0 ; } ravesli.com 88 4. Напишите функцию doubleNumber(), которая принимает целое число в качестве параметра, умножает его на 2, а затем возвращает результат обратно в caller. 5. Напишите полноценную программу, которая принимает целое число от пользователя (используйте cin), удваивает его с помощью функции doubleNumber() с предыдущего задания, а затем выводит результат на экран. ravesli.com 89 Урок №14. Почему функции полезны и как их эффективно использовать? Теперь, когда мы уже знаем, что такое функции и зачем они нужны, давайте более подробно рассмотрим то, почему они так полезны. Зачем использовать функции? Начинающие программисты часто спрашивают: «А можно ли обходиться без функций и весь код помещать непосредственно в функцию main()?". Если вашего кода всего 10-20 строчек, то можно, если же серьёзно, то функции предназначены для упрощения кода, а не для его усложнения. Они имеют ряд преимуществ, которые делают их чрезвычайно полезными в нетривиальных программах. Структура. Как только программы увеличиваются в размере/сложности, сохранять весь код внутри main() становится трудно. Функция - это как мини-программа, которую мы можем записать отдельно от головной программы, не заморачиваясь при этом об остальных частях кода. Это позволяет разбивать сложные задачи на более мелкие и простые, что кардинально снижает общую сложность программы. Повторное использование. После объявления функции, её можно вызывать много раз. Это позволяет избежать дублирования кода и сводит к минимуму вероятность возникновения ошибок при копировании/вставке кода. Функции также могут использоваться и в других программах, уменьшая объём кода, который нужно писать с нуля каждый раз. Тестирование. Поскольку функции убирают лишний код, то и тестировать его становится проще. А так как функция - это самостоятельная единица, то нам достаточно протестировать её один раз, чтобы убедиться в её работоспособности, а затем мы ravesli.com 90 можем её повторно использовать много раз без необходимости проводить тестирование (до тех пор, пока не внесём изменения в эту функцию). Модернизация. Когда нужно внести изменения в программу или расширить её функционал, то функции являются отличным вариантом. С их помощью можно внести изменения в одном месте, чтобы они работали везде. Абстракция. Для того, чтобы использовать функцию, нам нужно знать её имя, данные ввода, данные вывода и где эта функция находится. Нам не нужно знать, как она работает. Это очень полезно для написания кода, понятного другим (например, стандартная библиотека С++ и всё, что в ней находится, создана по этому принципу). Каждый раз, при вызове std::cin или std::cout для ввода или вывода данных, мы используем функцию из стандартной библиотеки C++, которая соответствует всем указанным выше концепциям. Эффективное использование функций Одной из наиболее распространённых проблем, с которой сталкиваются новички, является понимание того, где, когда и как эффективно использовать функции. Вот несколько основных рекомендаций при написании функций: Код, который появляется более одного раза в программе, лучше переписать в виде функции. Например, если мы получаем данные от пользователя несколько раз одним и тем же способом, то это отличный вариант для написания отдельной функции. Код, который используется для сортировки чего-либо, лучше записать в виде отдельной функции. Например, если у нас есть список вещей, которые нужно отсортировать - пишем функцию ravesli.com 91 сортировки, куда передаём несортированный список и откуда получаем отсортированный. Функция должна выполнять одно (и только одно) задание. Когда функция становится слишком большой, сложной или непонятной – её следует разбить на несколько подфункций. Это называется рефакторинг кода. При изучении C++ вам предстоит написать много программ, которые будут включать следующие три подзадания: Получение данных от пользователя. Обработка данных. Вывод результата. Для простых программ (менее, чем 30 строчек кода) частично или все из этих трёх подзаданий можно записать в функции main(). Для более сложных программ (или просто для практики) каждое из этих трёх подзаданий является хорошим вариантом для написания отдельной функции. Новички часто комбинируют обработку ввода и вывод результата в одну функцию. Тем не менее, это нарушает правило "одного задания". Функция, которая обрабатывает значение, должна возвращать его в caller, а дальше уже пускай caller сам решает, что ему с ним делать. ravesli.com 92 Урок №15. Локальная область видимости Как мы уже знаем из предыдущих уроков, при выполнении процессором стейтмента int х; создаётся переменная. Возникает вопрос: «Когда эта переменная уничтожается?". Область видимости переменной определяет, кто может видеть и использовать переменную во время её существования. И параметры функции, и переменные, которые объявлены внутри функции, имеют локальную область видимости. Другими словами, эти переменные используются только внутри функции, в которой они объявлены. Локальные переменные создаются в точке объявления и уничтожаются, когда выходят из области видимости. Рассмотрим следующую программу: #include ( int a , int b ) // здесь создаются переменные a и b { // a и b можно видеть/использовать только внутри этой функции return a + b ; } // здесь a и b выходят из области видимости и уничтожаются int main () { int x = 7 ; // здесь создаётся и инициализируется переменная x int y = 8 ; // здесь создаётся и инициализируется переменная y // x и y можно использовать только внутри функции main() std :: cout << add ( x , y ) << std :: endl ; // вызов функции add() с a = x и b = y return 0 ; } // здесь x и y выходят из области видимости и уничтожаются Параметры a и b функции add() создаются при вызове этой функции, используются только внутри неё и уничтожаются при завершении выполнения этой функции. Переменные x и y функции main() можно использовать только внутри main() и они также уничтожаются при завершении выполнения функции main(). ravesli.com 93 Для лучшего понимания разберём детальнее ход выполнения этой программы: выполнение начинается с функции main(); создаётся переменная x функции main() и ей присваивается значение 7; создаётся переменная y функции main() и ей присваивается значение 8; вызывается функция аdd() с параметрами 7 и 8; создаётся переменная a функции add() и ей присваивается значение 7; создаётся переменная b функции add() и ей присваивается значение 8; выполняется операция сложения чисел 7 и 8, результатом чего является значение 15; функция add() возвращает значение 15 обратно в caller (в функцию main()); переменные функции add() a и b уничтожаются; main() выводит значение 15 на экран; main() возвращает 0 в операционную систему; переменные функции main() x и y уничтожаются. Всё! Обратите внимание, если бы функция add() вызывалась дважды, параметры a и b создавались и уничтожались бы также дважды. В программе с большим количеством функций, переменные создаются и уничтожаются часто. ravesli.com 94 Локальная область видимости предотвращает возникновение конфликтов имён Из примера выше понятно, что переменные x и y отличаются от переменных a и b. Теперь давайте рассмотрим следующую программу: #include ( int a , int b ) // здесь создаются переменные a и b функции add() { return a + b ; } // здесь a и b функции add() выходят из области видимости и уничтожаются int main () { int a = 7 ; // здесь создаётся переменная a функции main() int b = 8 ; // здесь создаётся переменная b функции main() std :: cout << add ( a , b ) << std :: endl ; // значения переменных a и b функции main() копируются в переменные a и b функции add() return 0 ; } // здесь a и b функции main() выходят из области видимости и уничтожаются Здесь мы изменили имена переменных x и y функции main() на a и b. Программа по-прежнему работает корректно, несмотря на то, что функция add() также имеет переменные a и b. Почему это не вызывает конфликт имён? Дело в том, что a и b функции main() являются локальными переменными, функция add() не может их видеть (и наоборот). Ни add(), ни main() не знают, что они имеют переменные с одинаковыми именами! Это значительно снижает возможность возникновения конфликта имён. Любая функция не должна знать или заботиться о том, какие переменные находятся в другой функции. Это также предотвращает возникновение ситуаций, когда одни функции могут непреднамеренно (или намеренно) изменять значения переменных других функций. Правило: Имена, которые используются внутри функции (включая параметры), доступны/видны только внутри этой же функции. ravesli.com 95 Тест Какой результат выполнения следующей программы? #include ( int a ) { int b = 5 ; std :: cout << "doMath: a = " << a << " and b = " << b << std :: endl ; a = 4 ; std :: cout << "doMath: a = " << a << " and b = " << b << std :: endl ; } int main () { int a = 6 ; int b = 7 ; std :: cout << "main: a = " << a << " and b = " << b << std :: endl ; doMath ( a ); std :: cout << "main: a = " << a << " and b = " << b << std :: endl ; return 0 ; } |