Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Основные правила работы с функциями: примеры и ограничения использования функций в различных языках программирования

Содержание:

Введение

Для данной работы была выбрана тема «Основные правила работы с функциями: примеры и ограничения использования функций в различных языках программирования». Я нахожу данную тему значимой, так как функции помогают избавиться от множества проблем и ошибок в программировании.

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

Для достижения цели выделим следующие задачи:

- определить что такое функция;

- обосновать надобность функций, их достоинства и недостатки;

- определить основные характеристики функций;

- определить языки программирования для рассмотрения конкретных примеров функций;

- привести примеры функций и их использования в различных языках программирования;

- определить ограничения использования функций в различных языках программирования;

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

Источники, использованные в данной курсовой работе, являются в большей мере книгами, но также были использованы и учебники. Годы написания источников варьируются от 2002 до 2014. Авторами книг и учебников являются специалисты в области программирования, преподаватели, а также известные в it-среде теоретики и практики. Кроме того, используемые источники изданы в издательствах с хорошей репутацией, проверенной временем. В некоторых дисциплинах, связанных с программированием, эти источники считаются фундаментальными.

1. Функция в программировании

1.1. Определение функции, основные понятия

В программировании функция – это обособленная часть кода, которая позволяет выполнить определенное действие. Функция является отличным инструментом для декомпозиции сложной задачи, состоящей из нескольких шагов. Иными словами, с помощью выделения функций можно наглядно показать из каких смысловых частей состоит программа [7].

Говоря о функциях, важно определить 2 понятия: объявление функции и вызов функции. Объявление функции – это занесение в пространство имен программы имени функции, определение ее параметров и тела (или, говоря иначе, алгоритма действий). Вызов функции – это использование функции, при котором непосредственно выполняется ее код. Функции можно вызывать внутри других функций. Также функция может вызывать в своем теле себя же, такое явление называется рекурсией, а функция рекурсивной [6].

Если программа состоит из набора функций и их вызовов, то говорят, что такая программа создана с помощью процедурного подхода. При процедурном подходе программа подразделяется на главную функцию и вспомогательные. Для того, чтобы вспомогательные функции запустились, их нужно вызвать в главной. На рисунке 1 представлено дерево вызовов такой программы (Main – главная функция, FuncA, FuncB… – вспомогательные функции) [9].

C:\Users\IAmBanana\Desktop\cplusfunction005.png

Рисунок 1. Дерево вызовов

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

1.2. Характеристики функции

Рассмотрим основные характеристики функций:

1) Имя. Имя функции отражает ее непосредственное назначение. Обычно имя функции состоит из глагола, который описывает основное действие функции, или фразы, содержащей этот глагол. Например, имя функции подсчета среднего значения – computeAverage. Имя функции не должно быть длинным, оно должно быть кратким и понятным. Также существуют правила наименования функций, которые отличаются в зависимости от языка программирования. Например, в С название функции не должно начинаться с цифры или знака препинания, кроме нижнего подчеркивания. Если не следовать этим правилам, то код не скомпилируется, и программа не сможет быть запущена. Следует отметить, что в некоторых языках программирования есть возможность объявить анонимную функцию, то есть функцию, у которой нет имени (например, в JavaScript) [6].

2) Тело. Тело функции состоит из набора команд и выражений, которые определяют, что делает функция. Говоря иначе, тело функции отражает ее алгоритм действий [7].

3) Входные параметры. Входные параметры функции – это данные, которые передаются в функцию при ее вызове и используются в теле функции. Входные параметры не обязательны – функция может не иметь их. Также в некоторых языках программирования параметрам можно задавать начальные значения. В таком случае, если при вызове функции ей не было передано значение параметра, у которого задано начальное значение, то ошибки не произойдет [9].

4) Возвращаемое значение. Возвращаемое значение функции – это данные, которая функция возвращает в результате своего выполнения. Возвращаемое значение формируется на основе входных параметров и/или других данных, получаемых в теле функции. У функции может не быть выходного значения или их может быть несколько [6].

На рисунке 2 схематично представлена работа функции.

C:\Users\IAmBanana\Desktop\functional_programming.png

Рисунок 2. Схема функции

1.3. Преимущества и недостатки использования функций

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

1) Использование функций позволяет избегать дублирования кода, а также ошибок, связанных с ним. Таким образом, если в алгоритме ошибка, то ее будет достаточно исправить один раз. Кроме того, для хоть сколько-нибудь большого количества вызовов написание одной функции будет гораздо экономнее по времени и трудозатратам, чем простое копирование и корректировка кода [4].

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

3) При отсутствии дублирования кода и уменьшении его объема повышается читаемость кода. А чем выше читаемость, тем легче поддерживать код.

4) При помощи функций задача легче и нагляднее разбивается на подзадачи, что упрощает дальнейшее решение [5].

5) Функции дают возможность тестировать части программы независимо друг от друга. Кроме того, за счёт повышения атомарности (разбития на части) проще локализовать и найти ошибку [4].

6) Функции позволяют не «засорять» пространство имен переменных, так как локальные переменные «живут» только во время выполнения функции. Например, если в функции func1 объявлена переменная с названием i, то в функции func2 тоже можно объявить переменную с таким же названием и конфликта не будет. Также не тратится память на хранение значений переменных, которые не нужны вне функции [5].

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

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

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

2. Анализ языков программирования для приведения примеров использования функций

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

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

- уровню – низкого или высокого уровня;

- типизации – типизированные и не типизированные языки [6].

Рассмотрим данные типы языков программирования и их примеры.

2.1. Языки низкого и высокого уровня

Под «уровнем» в данном случае понимается уровень приближенности вычислений и в целом языка к непосредственно аппаратным процессам. Таким образом, язык низкого уровня – язык, взаимодействующий, например, с регистрами памяти, и оперирующий командами, свойственными архитектуре процессора. Очевидным случаем такого языка является машинно-ориентированный язык программирования Assembler. Все его команды являются адаптированными для человека командами процессора. Данный язык программирования имеет множество версий, синтаксис которых различен и зависит от архитектуры процессора, на работу с которым направлена конкретная версия [7].

Безусловным достоинством подобных языков является быстродействие – в силу максимальной близости инструкций к машинным, время проведение любых операций минимально [9]. Однако, это влечёт за собой следующие недостатки:

- отсутствие безопасности – при работе с практически не защищёнными проверками памятью и процессором велика не только вероятность ошибки, но и ее критичность – набор неверных инструкций может необратимо повредить аппаратную часть;

- сложная читаемость и большой объем кода – следствие того факта, что фактически программист должен оперировать машинными инструкциями, а значит, объём кода даже для простейшей операции может быть очень большим;

- из-за сильной зависимости языка от платформы переносимость программ практически не существует [6].

Язык высокого уровня, соответственно, хоть и имеет меньшую скорость работы, более безопасен как для программиста, так и для компьютера; кроме того, куда более приближен к человеческому языку, прост для восприятия и при выполнении того же действия потребует на порядки меньшее количество команд. Кроме того, выполнение программы, написанной на языке высокого уровня, не настолько зависимо от архитектуры процессора, на котором ведутся вычисления [9].

Условно языком высокого уровня можно считать С, однако он так же допускает прямую работу с памятью и ряд других возможностей, поэтому его следует отнести скорее к языкам «среднего» уровня. Более явными примерами языков высокого уровня являются С++, Java, JavaScript и т.д.

2.2. Типизированные и не типизированные языки программирования

Говоря о типизированных и не типизированных языках, корректнее будет говорить не только о наличии типизации, но и о видах типизации. Типизация бывает:

- статическая;

- динамическая;

- слабая;

- сильная;

- явная;

- неявная;

- отсутствующая [7].

Язык программирования, не имеющий типизации – это уже описанный ранее Assembler. Ответственность за данные ложится полностью на программиста, сам язык никак не регулирует и не отличает обычные числа от символов или, например, шестнадцатеричных чисел. Это увеличивает сложность написания программы, но повышает эффективность работы конечного продукта, кроме того, так как все данные представлены последовательностью бит и отличаются только длиной, которую для своих данных так же регулирует «вручную» программист, можно производить любые операции над любыми видами данных [1].

При статической типизации на этапе компиляции (сборки) программы проходит учёт и проверка всех переменных и типов данных. При динамической типизации эти проверки осуществляются непосредственно во время выполнения программы. Следовательно, языки программирования со статической типизацией всегда работают быстрее, но языки программирования с динамической типизацией более универсальны при работе с данными и более удобны при разработке [7].

При сильной типизации разные типы данных не могут смешиваться в выражениях и не приводятся к одному и тому же типу данных автоматически. Соответственно, при слабой типизации наоборот – можно, например, сравнивать число со строкой. Сильная типизация более надёжна и быстрее слабой, а слабая – более удобна при программировании и гораздо короче в записи.

При явной типизации программист явно указывает типы всех переменных, которые он использует, что повышает читаемость кода, при неявной – программа самостоятельно разбирается с этим вопросом, что повышает устойчивость кода, если тип данных, допустим, поменялся, но сами данные все те же и результат требуется тот же [8].

Одним из языков с явной, статической и достаточно сильной типизацией является С++. Динамическая, слабая и неявная типизация характерна в том числе для языка программирования JavaScript.

2.3. Выбор языков программирования для приведения примеров использования функций

Из анализа типов языков программирования следует, что для того, чтобы наиболее полно рассмотреть примеры и ограничения функций в разных языках программирования, следует выбрать языки программирования Assembler (низкоуровневый язык программирования без типизации), С++ (высокоуровневый язык программирования со статической, сильной и явной типизацией) и JavaScript (высокоуровневый язык программирования с динамической, слабой и неявной типизацией).

3. Примеры и ограничения использования функций

3.1. Примеры и ограничения использования функций в C++

В языке программирования С++ функция – один из самых важных компонентов. Согласно особенностям языка, функция:

- имеет явно указанный тип, соответствующий значению, которая она возвращает. В случае, если функция ничего не возвращает, она имеет тип void;

- каждая функция имеет список параметров, которые ей передаются. Данный список может быть пустым. Он обязательно берётся в круглые скобки;

- начало функции обозначено открывающейся фигурной скобкой. Конец функции обозначен закрывающейся фигурной скобкой [3];

- в каждой программе существует функция main() – главная функция программы, а так же точка входа в программу и выхода из неё. Это значит, что вся работа программы начинается и заканчивается с фигурными скобками функции main() ;

- объявление функции делится на 2 части: прототип и непосредственно реализация. Сначала должен указываться прототип, а только потом писать реализацию. Прототип состоит из типа возвращаемого значения, имени функции и списка параметров в круглых скобках. В конце прототипа должна стоять точка с запятой. Обычно прототипы функций выносят в заголовочный .h файл, который подключают в .cpp файле, где указывается реализация функции;

- объявление функции должно происходить раньше ее использования [8].

Итак, рассмотрим основные примеры применения функций в С++.

В С++ можно задать функцию типа void []. На рисунке 3 изображен пример программы, которая состоит из двух функций – main() и _functionVoid(int). В функцию _functionVoid(int) передается один целочисленный параметр, который отображает номер вызова функции. Результат работы программы приведен на рисунке 4.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 3. Пример функции на С++, которая ничего не возвращает

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 4. Результат работы программы

Рассмотрим работу функций, которые имеют тип и возвращают значение [3]. В примере на рисунке 5 есть функция _functionSumm(int, int). В нее передается два целочисленных параметра, сумму которых она возвращает. Для этого в начале функции в переменную целочисленного типа sum записывается сумма переданных чисел (строка 13). Затем эта переменная передается обратно в функцию main() при помощи оператора return (строка 16). Результат работы программы приведен на рисунке 6.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 5. Пример функции на С++, которая возвращает значение

Рисунок 6. Результат работы программы

С++ допускает вычисление возвращаемого параметра прямо в операторе return [8]. В следующем примере (см. рисунок 7) функция _functionDiv(double, double) в строке 14 сначала приводит тип целочисленной переменной firstNumber к дробному числу, затем делит это дробное число на целочисленную secondNumber, после чего отправляет полученное значение в main(). Результат работы программы представлен на рисунке 8.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 7. Пример функции на С++, возвращаемое значение которой формируется после ключевого слова return

Рисунок 8. Результат работы программы

Итак, базовые примеры функций с разными типами данных рассмотрены. Обратим внимание на тот факт, что С++ имеет такую особенность, как область видимости. Данные внутри программы на языке С++ могут быть локальными, то есть существующими и видимыми только в пределах функции, в которой они были объявлены, или глобальными, то есть видимыми всей программе [8]. В обычных условиях обращение внутри функции к глобальным и локальным переменным никак не отличается. Но может возникнуть ситуация, когда локальная и глобальная переменная имеют одно и то же имя, как в программе на рисунке 9. В таком случае для обращения к локальной переменной используется ее имя, а для обращения к глобальной задается явный указатель области видимости, в нашем случае «::» - это значит, что имя переменной, к которой мы обращаемся, видно во всем текущем файле [3]. В силу особенностей работы языка С++ при возникновении такой ситуации необходимо учитывать ограничения областей видимости в работе функции. Результат работы программы представлен на рисунке 10.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 9. Пример локальной и глобальной переменных в функции C++

Рисунок 10. Результат работы программы

Кроме всего прочего, в С++ существует значение по умолчанию для параметра функции [3]. На рисунке 11 видно, что для параметра x функции localAndGlobalArgumentsCollision(int) задано значение по умолчанию 1 (строка 11). Далее в коде (строки 28-29) видно, что в цикле, в котором вызывается функция, значение параметра передается только при четных значениях переменной цикла i, во всех остальных случаях функция localAndGlobalArgumentsCollision(int) вызывается без аргументов. Тем не менее, по результату работы программы на рисунке 12 видно, что при незаданном значении параметра функции используется параметр по умолчанию, во всех остальных случаях он замещается переданным значением.

В С++ так же имеются лямбда-функции или лямбда-выражения. Лямбда-функции – это функции, имеющие особенное объявление, которые можно хранить в переменных, передавать как параметры других функций или использовать, как независимые функции внутри функций [8]. На рисунке 13 объявляется переменная lambdaFunction типа auto, в которую записывается лямбда-функция типа void без параметров (строка 15). Далее переменная с функцией вызывается как обычная функция (строка 17).

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 11. Пример параметров функции с начальным значением на С++

Рисунок 12. Результат работы программы

Код, приведенный на рисунке 13, эквивалентен коду на рисунке 14, с тем лишь отличием, что в данном случае лямбда-функция не присваивается переменной, она вызывается сразу после того, как объявлена. Результат работы обеих программ виден на рисунке 15.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 13. Пример лямбда-функции на С++

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 14. Пример лямбда-функции на С++

Рисунок 15. Результат работы программы

Рассмотрим пример, в котором в лямбда-функцию передаются аргументы [8]. На рисунке 16 виден аналог рассмотренной ранее функции _functionSumm(int, int), которая вычисляет сумму двух целочисленных значений. В цикле данная лямбда-функция объявляется и сразу же вызывается пять раз (строка 17).

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 16. Пример передачи аргументов в лямбда-функцию на С++

При этом лямбда-функция способна также и возвращать значение [8]. Переделаем предыдущий пример так, чтобы лямбда-функция возвращала сумму чисел-параметров и напечатаем на экран то, что она вернет после вызова (рисунок 17). Фактически, текущий и предыдущий пример тоже эквивалентны. Результат работы обеих программ приведен на рисунке 18.

Рисунок 17. Пример возвращения значения лямбды-функции на С++

Рисунок 18. Результат работы программы

Теперь рассмотрим случай, при котором лямбда-функция сама выступает параметром для другой функции. На рисунке 19 видно, что объявленная прямо в вызове функции lambdaFunction(func, int) лямбда-функция передается туда, как параметр (строка 24). Следует отметить, что для этой передачи понадобилось объявить отдельный тип данных для функции с целочисленным параметром (строка 5). После получения лямбда-функции в качестве параметра lambdaFunction(func, int) вызывает ее затем как обычную функцию [3]. Результат работы программы представлен на рисунке 20.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 19. Пример на С++, когда лямбда-функция выступает в качестве параметра функции.

Рисунок 20. Результат работы программы

С++ представляет для функций такую опцию, как перегрузка. Перегрузка функций – явление, при котором имеются несколько функций с разными типами данных, наборами параметров, а также, возможно, и алгоритмами [3].

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

Снова рассмотрим пример с _functionSumm на рисунке 21, однако на этот раз создадим два аналога этой функции – первый с типом double и такими же типами параметров (строка 5), а второй типа int и такими же типами параметров (строка 12). Внутри функций настроим сообщения так, чтобы они выводили на экран тип данных, с которым работает функция (строка 7, строка 14). Затем создадим два набора данных – две пары чисел, одну целочисленного типа (строка 27) и другую дробного (строка 28). Затем вызовем для обоих наборов данных функцию _functionSumm (строки 29-30). Как видно на рисунке 22, нужная функция для определенного типа данных подобралась автоматически.

Однако если убрать функцию int _functionSumm(int, int), результат будет иным (рисунок 23) – оба раза вызовется функция для расчёта суммы дробных чисел. Это произошло потому, что типы int и double могут конвертироваться автоматически один в другой, так как оба являются численными типами. Если попытаться вызвать ту же функцию для строк – произойдет ошибка [3].

Таким образом, С++ подбирает для набора данных наиболее подходящий вариант функции из существующих с учетом ограничений, свойственных для языка в силу строгой типизации.

Рисунок 22. Результат работы функции с перегрузкой

Рисунок 23. Результат работы функции без перегрузки

Рисунок 21. Пример перегрузки на С++

3.2. Примеры и ограничения использования функций в Assembler

В языке программирования Ассемблер имеются процедуры и функции. Здесь следует учитывать тот факт, что единственное отличие процедур от функций – это отсутствие в первых возвращаемого значения [1]. Таким образом, в данной работе следует рассматривать как процедуры, так и функции, поскольку, если учитывать формальное определение функции в целом, процедуры технически тоже являются функциями.

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

Для Ассемблера преимущественно используются два наиболее распространенных сообщения о вызове – stdcall и cdecl. По факту два этих соглашения не слишком различны – они отличаются в очистке стека от переданных функции параметров. Согласно сdecl, очистка стека осуществляется вызывающей стороной, согласно stdcall – очисткой стека должна заниматься сама функция. Первое характерно для С-подобных языков, второе более свойственно для Windows API [1].

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

Код процедуры выделяется из общего потока инструкций тем, что в начале следует макрос proc, а завершает весь набор макрос endp – это макросы [11]. На рисунке 24 представлена процедура на ассемблере.

Рисунок 24. Процедура на ассемблере

Как видно на рисунке 24, в начале следует макрос proc, затем идет имя функции/процедуры, и после – перечисление всех параметров через запятую.

Тип передаваемых параметров можно задавать и явно (см. рисунок 25).

Рисунок 25. Процедура на ассемблере с указанием типа передаваемых параметров

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

Вызов процедуры можно осуществить несколькими способами – как напрямую по имени процедуры, так при вызове участка памяти, в котором сохранен адрес процедуры [1]. На рисунке 26 представлены разные вариации вызова процедуры. Следует отметить, что во всех случаях процедура вызывается при помощи макроса call, который по сути является простым переходом на адрес, в котором хранится процедура – с одной поправкой: он сохраняет в памяти адрес текущей инструкции. Это сделано для того, чтобы после выполнения процедуры программа вернулась ровно в то место, откуда была вызвана функция – макрос ret читает сохраненный в памяти адрес и совершает переход [1].

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

- передача параметров через регистры;

- передача параметров через общую область памяти;

- передача аргументов через стек.

Проще всего передавать данные через регистр – не требуется дополнительных переменных, выделения памяти, функция или процедура сразу может обратиться по нужному адресу памяти и получить данные. Вместе с тем, количество регистров невелико, как и размеры регистров, и необходимо всегда помнить, какая информация в каком регистре находится [1].

Рисунок 26 Разные вариации вызова процедуры на Ассемблер

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

Чаще всего используется передача данных через стек. Стек – это своеобразная «стопка» памяти, в которую укладываются данные. Получить данные обратно можно только с вершины стека.

В ассемблере микропроцессор имеет особые регистры для работы со стеком – ESS для «дна» стека, ESP для вершины стека, а также EBP на случай, если нужен произвольные доступ к стеку [1]. Пример процедуры с аргументами в стеке рассмотрен на рисунке 27.

Рисунок 27 Пример процедуры с аргументами в стеке на Ассемблер

Стек при этом выглядит так, как показано на рисунке 28.

C:\Users\IAmBanana\Desktop\Безымянный.png

Рисунок 28. Стек

В начале программы была обозначена архитектура процессора, для которого предназначены инструкции, а также обозначено, что размер одного элемента стека – четыре байта. Затем описана процедура proc_1, которая, используя регистр «дна» стека ESP и регистр произвольного доступа EBP получает доступ к данным в стеке «снизу вверх» вместо того, чтобы брать элементы последовательно «сверху вниз». Процедура main записывает в стек три числа и затем вызывает процедуру proc_1 [11].

3.3. Примеры и ограничения использования функций в JavaScript

Рассмотрим основные способы записи и работы с функциями в JavaScript.

В JavaScript существует 2 способа объявления функций. В первом случае функция объявляется с использованием ключевого слова function, после которого указывается имя функции и круглые скобки. Имя функции может содержать буквы, цифры, нижнее подчеркивание и знак доллара. В круглых скобках могут быть указаны названия параметров функции, отделенные друг от друга запятой [2]. Внутри функции параметры ведут себя как локальные переменные. За круглыми скобками располагаются фигурные скобки. В фигурных скобках содержится тело функции. Для возвращения значения внутри функции используется ключевое слово return. Если не указано конкретное возвращаемое значение, то функция возвращает undefined. return останавливает выполнение функции, то есть любой код, написанный после выражения с return никогда не выполнится [10].

На рисунке 29 приведен пример объявления функции умножения (параметры – a,b, в теле функции происходит возвращение их произведения). На рисунке 30 приведен пример вызова пустой функции, которая возвращает undefined, так как отсутствует return с другим значением.

Рисунок 29. Пример объявления функции на JavaScript

Рисунок 30. Пример вызова пустой функции, которая возвращает undefined

Во втором случае функцию можно объявить через функциональное выражение. В таком случае функция является выражением и ее результат присваивается куда-либо, например, переменной. Таким образом можно определить как обычную функцию, так и анонимную функцию. Функции, объявленные через функциональное выражение, не могут быть использованы перед объявлением, в то время как функции, объявленные первым способом – могут [10]. На рисунке 31 приведен пример объявления анонимной функции с помощью функционального выражения. На рисунке 32 приведен пример объявления именованной функции с помощью функционального выражения.

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

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

Для более краткой записи функционального выражения используют стрелочные функции. Стрелочные функции могут быть только анонимными. Стрелочные функции записываются следующим образом: сначала в круглых скобках указываются параметры через запятую, затем следует оператор «=>», далее в фигурных скобках идет тело функции [2]. Пример стрелочной функции изображен на рисунке 33.

Рисунок 33. Пример стрелочной функции

Для того, чтобы вызвать функцию, нужно указать ее имя, и параметры в круглых скобках. Если параметров нет, то оставить пустые круглые скобки. Важно отметить, что круглые скобки являются оператором, который непосредственно вызывает функцию. Если указать имя функции без круглых скобок, то вернется сама реализация функции, а не ее возвращаемое значение [2]. Пример вызова обычной функции приведен на рисунке 34. На рисунке 35 приведен пример вызова стрелочной функции. При вызове стрелочной функции важно отметить, что в качестве названия функции указывается имя переменной, которой была присвоена эта функция.

Рисунок 34. Пример вызова обычной функции

Рисунок 35. Пример вызова стрелочной функции

В JavaScript, так же как и в С++, например, есть области видимости. Локальная переменная, объявленная внутри функции, видна только в пределах этой функции. На рисунке 36 объявлена функция, внутри которой объявлена локальная переменная i. Внутри этой функции можно использовать переменную i, но при попытке использовать ее вне функции, выскакивает ошибка.

Если вне функции была объявлена глобальная переменная, то она тоже будет доступна внутри функции. На рисунке 37 вне функции объявлена глобальная переменная globalVar. При попытке ее использования внутри функции func ошибки не происходит.

Если мы объявили локальную переменную в одной функции, то она не будет видна в другой функции. На рисунке 38 в функции func1 объявлена локальная переменная i. При попытке использования переменной i в функции func2 выскакивает ошибка [10].

Понятие области видимости рассматривается не только в рамках функции, но и в рамках циклов и условий. Если локальная переменная объявлена внутри условия, которое находится внутри функции, то за пределами этого условия в функции переменная не будет видна [10]. На рисунке 39 внутри условия объявлена локальная переменная i. Несмотря на то, что она объявлена внутри функции, при попытке ее использования вне условия выскакивает ошибка.

Рисунок 36. Пример использования локальной переменной вне функции

Рисунок 37. Пример использования глобальной переменной внутри функции

Рисунок 38. Пример использования локальной переменной одной функции в другой

Рисунок 39. Пример использования локальной переменной условия внутри функции и вне условия

В JavaScript параметрам функции можно задавать начальные значения. Для этого нужно после имени параметра через равно присвоить начальное значение [10]. На рисунке 40 параметрам функции умножения заданы начальные значения: 5 и 2. При вызове функции без указания значений параметров функция возвращает 10, то есть произведение начальных значений.

Рисунок 40. Пример задания начальных значений параметрам функции

В JavaScript есть возможность объявлять функцию внутри другой функции. В таком случае вызвать вложенную функцию можно только внутри функции, в которой она объявлена [2]. На рисунке 41 приведен пример вложенной функции. На рисунке 42 приведен пример использования вложенной функции вне области видимости.

Рисунок 41. Пример вложенной функции

Рисунок 42. Пример использования вложенной функции вне области видимости

Таким образом, мы рассмотрели основные особенности использования функций в таких языках программирования, как Assembler, С++ и JavaScript.

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

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

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

Заключение

В данной работе решены все поставленные задачи.

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

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

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

Список использованных источников

1. Калашников О.А. Ассемблер – это просто. Учимся программировать / О.А. Калашников. – СПб.: БХВ-Петербург, 2011. – 336 с.

2. Крокфорд Д. JavaScript: сильные стороны / Д. Крокфорд. – СПб.: Питер, 2012. – 176 с.

3. Липман, Стенли Б., Лажойе, Жози, Му, Барбара Э. Язык программирования С++. Базовый курс / Липман. – М.: ООО «И.Д. Вильямс», 2014. – 1120 с.

4. Макконелл С. Совершенный код. Мастер-класс / С. Макконелл. – М. : Издательско-торговый дом «Русская редакция»; СПб. : Питер, 2005. – 896 с.

5. Мартин Р. Чистый код: создание, анализ и рефакторинг. Библиотека программиста / Р. Мартин. – СПб.: Питер, 2010. – 464с.

6. Непейвода Н.Н., Скопин И.Н. Основания программирования / Н.Н. Непейвода, 2002. – 892 с.

7. Орлов С.А. Теория и практика языков программирования: Учебник для вузов. Стандарт 3-го поколения. – СПб.: Питер, 2014. – 688 с.

8. Прата С. Язык программирования С++. Лекции и упражнения / С. Прата. – М.: ООО «И.Д. Вильямс», 2012. – 1248 с.

9. Cтепанов А.А., Мак-Джонс П. Начала программирования / А.А. Степанов. – М. : ООО «И.Д. Вильямс», 2011. – 272 с.

10. Флэнаган Д. JavaScript. Подробное руководство / Д.Флэнаган. – СПб.: Символ-Плюс, 2012. – 1080 с.

11. Юров В.И. Assembler: Учебник для вузов. – СПб.: Питер, 2003. – 637 с.