V Flashcards
Функция, которая ничего не возвращает
- При создании функции, которая не должна возвращать никаких значений, тип возвращаемого ею значения указывается как void (пустота).
- Если функция не объявлена с типом возвращаемого значения void, в теле функции необходимо наличие оператора return .
Параметры функций со значениями по умолчанию
- До сих пор мы использовали в примерах фиксированное значение числа π как константу, не предоставляя пользователю возможности его изменить. Однако пользователя функции может интересовать более точное или менее точное ее значение. Как при создании функции, использующей число π, позволить ее пользователю использовать собственное значение, а при его отсутствии задействовать стандартное?
- Один из способов решения этой проблемы подразумевает создание в функции Area ( ) дополнительного параметра для числа π и присваивание ему значения но умолчанию (default value). Такая адаптация функции Area( ) выглядела бы следующим образом:
- double Area(double radius, double Pi = 3.14);*
- Обратите внимание на второй параметр Pi и присвоенное ему по умолчанию значение 3,14. Этот второй параметр является теперь необязательным параметром (optional parameter) для вызывающей функции. Вызывающая функция все равно может вызвать функцию Area (), используя синтаксис
- Area(radius);*
- В данном случае второй параметр был проигнорирован, поэтому используется его значение по умолчанию 3,14. Но если пользователь захочет задействовать другое значение числа π, то можно сделать это, вызвав функцию Area ( ) следующим образом:
- Area (radius, Pi); // Pi определяется пользователем*
Рекурсивная функция
- В некоторых случаях функция может фактически вызывать сама себя. Такая функция называется рекурсивной (recursive function). Обратите внимание, что у рекурсивной функции должно быть четко определенное условие выхода, когда она завершает работу и больше себя не вызывает.
- При отсутствии условия выхода или при ошибке в нем выполнение программы застрянет в рекурсивном вызове функции, которая непрерывно будет вызывать сама себя, пока в конечном счете не приведет к переполнению стека и аварийному завершению приложения.
- Рекурсивные функции могут пригодиться при вычислении чисел ряда Фибоначчи: значение каждого следующего числа последовательности — это сумма двух предыдущих чисел. Таким образом, n-е значение последовательности (при п> 1) определяется следующей (рекурсивной) формулой:
Fibonacci (n) = Fibonacci (n - 1) + Fibonacci (n - 2)
Функции с несколькими операторами return
- Вы не ограничены наличием только одного оператора return в определении функции. По желанию вы можете осуществлять выход из функции в любом месте, не обязательно только в одном. В зависимости от логики и задачи приложения это может быть и преимуществом, и недостатком.
- Используйте несколько выходов из функции осторожно. Значительно проще исследовать и понять функцию, которая начинается вверху и заканчивается внизу, чем функцию, которая имеет несколько выходов в разных местах.
Перегрузка функций
- Функции с одинаковым именем и одинаковым типом возвращаемого значения, но с разными наборами параметров называют перегруженными функциями (overloaded function).
- Перегруженные функции могут быть весьма полезными, например, в приложениях, в которых имеется функция с определенным именем, которая осуществляет некоторый вывод, но может быть вызвана с различными наборами параметров. Предположим, необходимо написать приложение, которое вычисляет площадь круга и площадь поверхности цилиндра. Функция, которая вычисляет площадь круга, нуждается в одном параметре — радиусе. Вторая функция, которая вычисляет площадь поверхности цилиндра, нуждается, кроме радиуса, во втором параметре — высоте цилиндра. Обе эти функции должны возвратить данные одного типа, содержащие площадь.
Передача в функцию массива значений
Функция, которая выводит на консоль целое число, может быть представлена следующим образом:
void Displaylnteger (int number);
Прототип функции, способной отобразить массив целых чисел, должен быть немного другим:
void Displaylntegers( int[] numbers, int length);
Первый параметр указывает, что передаваемые в функцию данные являются массивом, а второй параметр указывает его длину, чтобы, используя массив, вы не вышли за его границы
Передача аргументов по ссылке
Иногда нужны функции, способные работать с исходной переменной или изменять значение так, чтобы это изменение было доступно вне функции, скажем, в вызывающей функции. В таком случае следует объявить параметр как получающий аргумент по ссылке (by reference).
Используя оператор return , функция может возвратить только одно значение. Но если функция должна возвращать вызывающей функции несколько значений, то передача аргументов по ссылке является единственным способом, обеспечивающим такой возврат информации вызывающей функции.
Как процессор обрабатывает вызовы функций
- Вызов функции, по существу, означает, что процессор переходит к выполнению следующей команды, принадлежащей вызываемой функции и расположенной в некоторой не последовательной области памяти. После выполнения команд функции поток выполнения возвращается туда, откуда был совершен переход в функцию. Для реализации этой логики компилятор преобразует вызов функции в команду процессора CALL. Данная команда включает адрес следующей команды для выполнения (это адрес команды вызываемой функции). Когда процессор встречает команду CALL, он сохраняет в стеке позицию команды, которая будет выполнена после возврата из функции, и переходит к командам в области памяти, указанной в команде CALL.
- Эта область памяти содержит команды, принадлежащие функции. Процессор выполняет их до тех пор, пока не встретит команду RET (машинный код для оператора return в программе C++). Команда RET требует от процессора извлечь из стека адрес, сохраненный во время выполнения команды CALL, и использовать его в качестве адреса команды в вызывающей функции, которой должно продолжиться выполнение программы. Таким образом, процессор возвращает выполнение вызывающей функции, и оно продолжается с того места, где было прервано вызовом функции.
Понятие стека
Встраиваемые функции
- Вызов обычной функции преобразуется в команду CALL, которая приводит к выполнению операций со стеком, переходу процессора к выполнению кода функции и т.д. Эта дополнительная работа, выполняемая невидимо для пользователя, в большинстве случаев невелика. Но что если функция очень проста, как эта?
- double GetPi( )*
- {*
- return 3.14159;*
- }*
- Накладные расходы времени на выполнение фактического вызова функции в этом случае весьма высоки по сравнению со временем, затраченным на выполнение кода функции GetPi( ). Вот почему компиляторы C++ позволяют программисту объявлять такие функции как встраиваемые (inline). Ключевое слово inline — это просьба встроить реализацию функции вместо ее вызова в место ее вызова.
- inline double GetPi( )*
- {*
- return 3.14159;*
- }*
- Большинство современных компиляторов C++ оснащены высококачественными оптимизаторами кода. Многие из них позволяют оптимизировать программу по размеру, создавая приложение минимального размера, или по скорости, обеспечивая максимальную его производительность. Первое весьма важно при разработке программного обеспечения для различных устройств наподобие мобильных, в которых не так уж много памяти. При такой оптимизации компилятор отклоняет большинство просьб о встраивании, поскольку это может увеличить размер кода.При оптимизации по скорости компилятор обычно удовлетворяет просьбы о встраивании (там, где это имеет смысл), причем делает это зачастую даже в тех случаях, когда никто его об этом не просит.
Автоматический вывод возвращаемого типа
- Вместо указания типа возвращаемого значения можно использовать ключевое слово auto и позволить компилятору самому вывести тип возвращаемого значения на основе вашего кода.
- Функции с использованием автоматического вывода типа возвращаемого значения должны быть определены (т.е. реализованы) до того, как вы будете к ним обращаться. Это связано с тем, что компилятор должен знать тип возвращаемого значения функции в точке, где она используется. Если такая функция содержит несколько операторов return, все они должны возвращать один и тот же тип. Рекурсивные вызовы должны предваряться по крайней мере одним оператором return в теле функции.
Лямбда-функции
Лямбда-функции введены стандартом С++11 и очень помогают использовать алгоритмы STL для сортировки и обработки данных. Как правило, функция сортировки требует бинарного предиката, который представляет собой функцию, сравнивающую два аргумента и возвращающую true, если первый меньше второго, и false в противном случае (тем самым определяя порядок, в котором должны находиться отсортированные элементы). Такие предикаты обычно реализуются в виде операторов класса, что требует весьма кропотливого программирования.
Почему бы не встраивать каждую функцию? Ведь это увеличит скорость выполнения, не так ли?
Все зависит от обстоятельств. Результатом встраивания всех функций будет многократное повторение их содержимого во множестве мест вызова, что приведет к увеличению объема кода. Поэтому наиболее современные компиляторы сами судят о том, какие вызовы могут быть встроены, в зависимости от настроек производительности.
У меня есть две функции, обе по имени Area. Одна получает радиус, а другая высоту. Я хочу, чтобы одна возвращала тип float, а другая тип double. Это возможно?
Для перегрузки обе функции нуждаются в одинаковом имени и одинаковом типе возвращаемого значения. В данном случае компилятор сообщит об ошибке, поскольку две функции с разными типами возвращаемых значений не могут иметь одинаковое имя.
Что такое указатель?
- Не усложняя, можно сказать, что указатель (pointer) — это переменная, которая хранит адрес области в памяти. Точно так же, как переменная типа int используется для хранения целочисленного значения, переменная указателя используется для хранения адреса области памяти
- Таким образом, указатель — это переменная, и, как и все переменные, он занимает пространство в памяти (в случае рис. — по адресу 0x101). Но особенными указатели делает то, что содержащиеся в них значения (в данном случае — 0x558) интерпретируются как адреса областей памяти. Следовательно, указатель — это специальная переменная, которая указывает на область в памяти.
- Адреса памяти обычно представлены в шестнадцатеричной записи. Это система счисления с основанием 16, т.е. использующая 16 различных символов - за 0-9 следуют символы A-F. По соглашению перед шестнадцатеричным числом записывается префикс Ох. Таким образом, шестнадцатеричное число ОхА представляет собой 10 в десятичной системе счисления; шестнадцатеричное OxF - 15; а шестнадцатеричное 0x10 - 16.