11.6 – Встраиваемые (inline) функции
Добавлено 14 июня 2021 в 04:48
Использование функций дает множество преимуществ, в том числе:
- Код внутри функции можно использовать повторно.
- Намного легче изменить или обновить код в функции (что нужно сделать один раз), чем для каждого экземпляра на месте. Дублирование кода – это рецепт для неэффективности и ошибок.
- Они упрощают чтение и понимание вашего кода, так как вам не нужно знать, как реализована функция, чтобы понять, что она делает (при условии ответственного именования функций или комментариев).
- Функции обеспечивают проверку типов, чтобы гарантировать, что аргументы вызова функции соответствуют параметрам функции (макросы, подобные функциям, этого не делают, что может привести к ошибкам).
- Функции упрощают отладку вашей программы.
Однако одним из основных недостатков функций является то, что каждый раз, когда функция вызывается, возникает определенная нагрузка на производительность.
Для больших функций и/или выполнения сложных задач накладные расходы на вызов функции обычно незначительны по сравнению с количеством времени, которое требуется для выполнения функции. Однако для небольших, часто используемых функций время, необходимое для вызова функции, часто намного больше, чем время, необходимое для фактического выполнения кода функции. Это может привести к значительному снижению производительности.
C++ предлагает способ сочетания преимуществ функций со скоростью кода, написанного на месте: встраиваемые (inline) функции. Ключевое слово inline
используется для запроса у компилятора обработки вашей функции как встраиваемой функции.
Рассмотрим следующий фрагмент:
int min(int x, int y) { return x > y ? y : x; } int main() { std::cout << min(5, 6) << '\n'; std::cout << min(3, 2) << '\n'; return 0; }
Эта программа дважды вызывает функцию min()
, что дважды добавляет накладные расходы на вызов функции. Поскольку
– такая короткая функция, она является идеальным кандидатом для встраивания:
inline int min(int x, int y) { return x > y ? y : x; }
Теперь, когда компилятор будет компилировать main()
, он создаст машинный код, как если бы main()
была написана следующим образом:
int main() { std::cout << (5 > 6 ? 6 : 5) << '\n'; std::cout << (3 > 2 ? 2 : 3) << '\n'; return 0; }
Это будет выполняться немного быстрее за счет того, что скомпилированный код будет немного больше.
Из-за возможности раздувания кода встраивание лучше всего подходит для коротких функций (например, не более нескольких строк), которые обычно вызываются внутри циклов и не имеют разветвлений. Также обратите внимание, что ключевое слово inline
является только рекомендацией – компилятор может проигнорировать ваш запрос на встраивание функции. Вероятно, так будет, если вы попытаетесь встроить длинную функцию!
Наконец, современные компиляторы сейчас очень хорошо умеют автоматически встраивать функции – в большинстве случаев лучше, чем люди. Даже если вы не отметите функцию как встраиваемую, компилятор будет встраивать функции, которые, по его мнению, приведут к увеличению производительности. Таким образом, в большинстве случаев нет особой необходимости использовать ключевое слово inline
. Позвольте компилятору обрабатывать встраиваемые функции за вас.
Лучшая практика
Помните о встраиваемых функциях, но современные компиляторы должны встраивать функции за вас по мере необходимости, поэтому нет необходимости использовать ключевое слово inline
в этом контексте.
Встраиваемые функции не подпадают под правило «одно определение в программе».
В предыдущих главах мы отмечали, что вам не следует реализовывать функции (с внешним связыванием) в файлах заголовков, потому что, когда эти заголовки включаются в несколько файлов .cpp, определение функции будет скопировано в несколько файлов .cpp. Затем эти файлы будут скомпилированы, и компоновщик выдаст ошибку, поскольку заметит, что вы определили одну и ту же функцию более одного раза.
Однако на встраиваемые функции не распространяется правило, согласно которому у вас в программе может быть только одно определение, поскольку встраиваемые функции на самом деле не приводят к компиляции реальной функции – следовательно, не будет конфликта, когда компоновщик попытается слинковать несколько файлов вместе.
На данный момент это может показаться неинтересной мелочью, но в следующей главе мы познакомимся с новым типом функции (функцией-членом), которая в значительной степени использует этот момент.
Даже со встраиваемыми функциями обычно не следует определять глобальные функции в файлах заголовков.
Оригинал статьи:
- 10.6 — Inline functions
Теги
C++ / CppinlineLearnCppДля начинающихОбучениеПрограммированиеНазад
Оглавление
Вперед
Возврат значений из функции Lua
Раздел: CronosPRO | Дата редакции: 12.07.2013 | id статьи: 1544 |
Функции могут возвращать одно или несколько значений, а также не возвращать значений вообще.
function f0() -- функция f0 не возвращает значений MsgBox ("Вызвана функция f0") end function f1() MsgBox ("Вызвана функция f1") return 1 -- функция f1 возвращает одно значение end function f3() MsgBox ("Вызвана функция f3") return 1, 2, 3 -- функция f3 возвращает три значения end f0() -- вызов функции f0 a = f1() -- вызов функции f1. a = 1 a, b, c = f3() -- вызов функции f3. a = 1, b = 2, c = 3
Если количество возвращаемых функцией значений превышает число переменных, которым эти значения должны быть присвоены, «лишние» значения отбрасываются. Если возвращаемых значений меньше, чем переменных, отсутствующие значения заменяются nil.
a = f3() -- a = 1, значения 2 и 3 отброшены a, b, c = f3() -- a = 1, b = 2, значение 3 отброшено a, b, c = f0() -- a = nil, b = nil, c = nil a, b, c = f1() -- a = 1, b = nil, c = nil
Вне зависимости от того, возвращает функция значения или нет, её можно вызвать как оператор. Все возвращаемые функцией значения в этом случае будут отброшены:
f3() -- значения, возвращенные функцией f3, отброшены
Возникают ситуации, когда функция возвращает несколько значений, а переменной требуется присвоить только одно из них. Например, функция возвращает три значения, а для работы нужно только последнее:
local a, b, c = f3()
Как не задавать лишних имён переменных?
Здесь общепринятым способом является использование переменной с именем «_» (символом подчёркивания). Это упрощает внешний вид выражения и указывает на реально используемые переменные.
local _, _, c = f3() -- нам нужно только третье возвращаемое значение
В данном случае, переменная «_» принимает первое, а потом второе возвращаемое значение.
Такой же подход часто используется в работе с итераторами:
for _, value in pairs() do ... -- нам нужно только очередное значение, ключ будет присвоен переменной _ end
Обратите внимание
Переменная с именем «_» является обычной переменной. При использовании этой переменной в других выражениях, она будет переопределяться, т. е. содержать то значение, которое было ей присвоено в последний раз:
local _, _, c = f3() -- переменная _ будет содержать второй параметр ... f4(_, true) -- первым параметром функуции f4 будет не пустое значение, а значение содеражащееся в переменной _
При использовании множественного присваивания все возвращаемые функцией значения учитываются только в том случае, если вызов функции является последним (или единственным) выражением в списке выражений справа от оператора присваивания. В противном случае в присваивании участвует только одно (первое) возвращённое функцией значение.
a, b, c, d = 5, f3() -- a = 5, b = 1, c = 2, d = 3 a, b, c, d = f3(), 5 -- a = 1, b = 5, c = nil, d = nil
Аналогичным образом учитываются результаты вызова функции, включённого в конструктор таблицы, список аргументов другой функции и в список результатов, возвращаемых оператором return.
t = {5, f3()} -- t = {5, 1, 2, 3} t = {f3(), 5} -- t = {1, 5} func (5, f3()) -- вызов функции func с аргументами 5, 1, 2, 3 func (f3(), 5) -- вызов функции func с аргументами 1, 5 return 5, f3() -- возврат значений 5, 1, 2, 3 return f3(), 5 -- возврат значений 1, 5
Функции можно возвращать из функций
. Например:function outer() function inner() return 1 end return inner end local i = outer() -- значение переменной i равно функции inner local a = i() -- вызываем функцию i, значение переменной a равно 1
Что происходит, когда мы вызываем функцию
Улучшить статью
Сохранить статью
- Уровень сложности: Средний
- Последнее обновление: 02 июл, 2021
Улучшить статью
Сохранить статью
Функция — это набор кода, который выполняет определенную задачу и может использоваться в любое время, просто вызывая ее.
При использовании нескольких вызовов функций или рекурсии очень важно знать концепцию вызова функции для лучшего понимания кода.
Теперь давайте разберемся с работой вызова функции
Прежде чем понять работу вызова функции, вам потребуются некоторые предварительные знания о выполнении программы в ЦП, стеке программы, кадре стека (записи активации).
- Стек программ: Стек программ — это стек, содержащий все вызовы функций, с нижними элементами в качестве основной функции.
- Кадр стека: Кадр стека фактически является буферной памятью, которая является элементом программного стека и содержит данные вызываемой функции, т.е.
- Адрес возврата
- Входной параметр
- Локальные переменные
- Сохранение регистров
- Указатель стека: Указатель стека — это указатель, указывающий на вершину стека программы, т. е. на самую последнюю вызванную функцию.
Теперь всякий раз, когда вызывается функция, создается новый кадр стека со всеми данными функции, и этот кадр стека помещается в стек программы, а указатель стека, который всегда указывает на вершину стека программы, указывает на кадр стека. помещается в верхнюю часть стека программ.
Серия операций при вызове функции:
- Стек Кадр помещается в стек.
- Инструкции подпрограмм выполняются.
- Стек Кадр извлекается из стека.
- Теперь счетчик программ удерживает обратный адрес.
Примечание: Инструкция POP на ассемблере удаляет вершину стека и назначает ее счетчику программ.
Разберемся на примере:
В приведенном выше примере вызова функции.
- Счетчик команд указывает на следующую ячейку памяти инструкций, т. е. 104 перед выполнением функции, тогда как 100 — это ячейка памяти вызывающей функции.
- Для восстановления адреса возврата содержимое счетчика команд помещается в стек. Теперь адрес, хранящийся в верхней части стека, равен 104.
- Вызов функции. Выполнение: Теперь счетчик программ указывает на 2000, который является начальным адресом подпрограммы. После выполнения всех последовательных инструкций в подпрограмме адрес извлекается из стека. .
- Извлечение из стека относится к удалению вершины стека и назначению счетчику команд. Теперь счетчик программ удерживает адрес возврата, т. е. 104.
Основной мотив этой статьи — понять причину помещения адреса возврата в стек. Однако в современной реализации функции недостаточно только помещения адреса возврата в стек. Нам нужно передать фактические и формальные параметры перед отправкой обратного адреса.
Синтаксис, типы и методы вызова
В C++ функция — это сегмент кода, выполняющий определенную задачу. Вы можете повторно использовать его, что означает, что вы можете выполнить его более одного раза.
Что такое функции C++?
Функция определяется как группа операторов или блок кода, выполняющий определенные задачи. Вам нужно дать определенное имя функции и написать внутри нее некоторую логику или группу информации. И затем вы можете вызвать эту функцию из основной функции. Например, вы можете назвать функцию факториалом, а внутри этой функции вы можете написать логику для нахождения факториала числа. И затем вы можете использовать его всякий раз, когда вам нужна эта функция в программе.
Зачем использовать функции в C++?
Функции используются для минимизации повторения кода, так как функция позволяет писать код внутри блока. И вы можете вызывать этот блок всякий раз, когда вам нужен этот сегмент кода, а не писать код повторно. Это также помогает разделить программу на хорошо организованные сегменты.
Теперь взгляните на синтаксис функций C++.
Синтаксис функции
Синтаксис для создания функции следующий:
Здесь тип возвращаемого значения — это тип данных значения, которое возвращает функция. Затем идет имя функции, за которым следуют необязательные параметры, что означает, что функция может содержать или не содержать параметры.
Пример:
Декларация:
Функцию можно объявить, указав тип возвращаемого значения, имя функции и аргументы в квадратных скобках. Он сообщает компилятору, что эта конкретная функция присутствует. В C++, если вы хотите определить функцию после основной функции, вам нужно сначала объявить эту функцию.
Определение:
Определение функции определяет тело функции. Объявление и определение функции можно сделать вместе, но это следует сделать до ее вызова.
Звонок:
Когда вы определяете функцию, вы сообщаете ей, что делать и использовать эту функцию; вы должны вызвать или вызвать функцию. Когда функция вызывается из основной функции, управление функцией передается вызываемой функции. И тогда эта функция выполняет свою задачу. Когда задача завершена, она возвращает управление основной функции.
Далее в этом руководстве рассматриваются типы функций C++.
Типы функций в C++
В C++ есть два типа функций
- Встроенные функции
- Пользовательские функции
Встроенные функции:
Это функции, которые уже присутствуют в C++; их определения уже предоставлены в файлах заголовков. Компилятор выбирает определение из файлов заголовков и использует их в программе.
Пользовательские функции:
Это функции, которые пользователь создает самостоятельно, при этом пользователь дает собственное определение.
Вы можете записать их следующих типов:
- Нет аргумента и нет возвращаемого значения
- Нет аргумента, но возвращается значение
- Аргумент, но нет возвращаемого значения
- Аргумент и возвращаемое значение
Без аргументов и без возвращаемого значения. В этом типе, как следует из названия, функции не передаются аргументы, и вы печатаете вывод внутри функции. Таким образом, возвращаемого значения нет.
Нет аргумента, но возвращается значение: в этом типе функция не передает аргументов, но есть возвращаемое значение.
Аргумент, но нет возвращаемого значения: в этом типе функции передаются аргументы, но нет возвращаемого значения. И он печатает вывод внутри функции.
Аргумент и возвращаемое значение: В этом типе функции передаются аргументы, а также есть возвращаемое значение.
Теперь перейдем к методам вызова функций C++.
Методы вызова функций
Вы можете вызвать или вызвать функцию двумя способами: вызов по значению и вызов по функции.
Вызов по значению:
В этом вызывающем методе вы передаете в функцию только копии переменной, а не фактический аргумент. При передаче копий переменной или аргументов любые изменения, внесенные в переменную в функции, не влияют на фактический аргумент.
Например:
В приведенной выше программе вы передаете переменные в функцию через метод вызова по значению, т. е. вы передаете копии переменных. Таким образом, это не будет отражать изменения внутри основной функции, где присутствуют фактические переменные. Но если вы напечатаете вывод в вызванной функции, то вы увидите там изменения.
Звонить по ссылке:
В этом методе вызова вы передаете адрес или ссылку аргумента, и функция получает адрес памяти аргумента. В этом случае фактическое значение переменной изменяется, или можно сказать, что оно отражает изменения обратно в фактическую переменную.
В этом примере вы передаете ссылку на переменную, используя знак & (амперсанд), а не копию переменной, поэтому фактическое значение в этом случае также изменяется.
Ниже приведен вывод приведенного выше примера.
Продвиньтесь по карьерной лестнице в качестве разработчика стека MEAN с помощью программы Full Stack Web Developer — MEAN Stack Master’s Program.