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

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

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

Пример. Встроенная функция rand возвращает случайные числа взятые из равномерного распределения с интервала \((0, 1)\).

rand(3, 4)
ans =

    0.9572    0.1419    0.7922    0.0357
    0.4854    0.4218    0.9595    0.8491
    0.8003    0.9157    0.6557    0.9340

Пример выше создаёт матрицу 3x4 случайных чисел от нуля до единицы. Первый аргумент переданный фунции (3) отвечает за количество строк, второй (4) отвечает за количество столбцов.

Представим, что нам понадобились случайные числа в диапазоне от 1 до 10:

1+rand(3, 4)*9
ans =

    7.2535    1.3100    7.8897    5.4079
    3.8539    4.9487    8.1568    5.0103
    9.5520    4.4340    2.6819    6.8168

Умножение на 9 «растягивает» диапазон до \((0, 9)\), добавление 1 смещает диапазон до \((1, 10)\).

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

Давайте создадим собственную функцию с необходимой функциональность, а затем будем её вызывать в случае необходимости.

function myRand
    a = 1 + rand(3, 4) * 9
end

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

После сохранения функции (хорошая практика называть файл и функцию одинаково) в файл <названиеФункции>.m в рабочей директории можно вызвать фукнцию из командного окна:

>> myRand()

или >> myRand

a =

    7.7614    7.2917    5.9249    3.3176
    3.2959    9.0181    2.2476    8.5665
    5.5536    9.6336    2.3436    3.2885

Что произошло? Наша функция состоит лишь из одного выражения a = 1 + rand(3, 4) * 9, а отсуствующая ; приводит к тому, что выражение выводится в командном окне. Если же мы добавим ; к концу нашего выражения мы потеряем вывод наших значений.

Область видимости переменных

В любом случае (при добавлении ; или же его отсуствии) при обращении к a возвращается ошибка Unrecognized function or variable 'a'. → «Нераспознанная функция или переменная “a”.» – a отсутвует в нашей области переменных (глобальной области видимости).

«Что происходит в Вегасе, остается в Вегасе» – у каждой функции есть своя область переменных, своя область видимости. Для нас, находящихся вне области видимости (англ. scope) функции, значения стали недоступны. Область переменных, которая доступна только в рамках функции называют локальной областью видимости этой функции. Переменные принадлежащие локальной области видимости функции инициализиурются (первично присваиваются) внутри соответсвующей функции и могут использоваться лишь там.

Локальная переменная – переменная, доступная в рамках выражений только внутри соответсвующей единственной функции и существует только во время вызова функции.

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

Обозначим a нашим выходным аргументом. Достигается это путем добавления a = между ключевым словом function и именем функции myRand:

function a = myRand
    a = 1 + rand(3, 4) * 9;
end

Заметим, что мы добавили ; к концу выражения. Во избежания в некотором смысле дублирования выходного значения в командном окне.

Вызвав нашу функцию:

>> myRand()

Получаем:

ans =

    2.6366    2.2246    5.9487    6.5985
    3.3742    8.8236    2.3046    4.1586
    2.3099    6.2173    8.6773    5.6192

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

Результат работы функции можно присвоить переменной:

>> b = myRand()
b =

    4.6163    2.1099    4.7554    9.5031
    1.6837    2.6552    1.4469    5.4178
    3.1592    3.1596    9.1244    5.4033

Предположим, что мы хотим получить случайные числа в диапазоне \((2, 5)\):

2 + rand(3, 4) * (5-2)
ans =

    3.0132    2.3336    2.7251    2.3959
    4.7002    4.3408    3.2117    4.8262
    3.1077    3.1692    2.2894    4.8684

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

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

Передача входных аргументов функции

Обозначим low и high нашими входными аргументами начала и конца диапазона соотвественно . Достигается это путем добавления входных аргументов через запятую в круглые скобки после названия функции:

function a = myRand(low, high)
    a = low + rand(3, 4) * (high-low);
end

Вызвав нашу функцию:

>> myRand(2, 5)

Получаем:

ans =

    3.7256    3.0595    2.1291    4.1952
    2.1793    4.4636    2.5070    3.9432
    2.7043    2.0462    3.9473    3.3528

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

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

>> low
Unrecognized function or variable 'low'.
 
Did you mean:
>> flow

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

>> myRand(-2, 1)
ans =

   -0.3590   -1.4331   -0.8945   -1.7566
   -1.1110    0.0603   -0.1231    0.7882
    0.2341   -1.4495    0.3407    0.3271
>> myRand(-4, -2)
ans =

   -3.0264   -3.3873   -2.3647   -3.2428
   -3.1283   -2.9830   -2.4103   -2.3768
   -3.1064   -2.9785   -2.7114   -2.9343

Возвращение нескольких значений из функции

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

function [a, s] = myRand(low, high)
    a = low + rand(3, 4) * (high-low);
    s = sum(a(:));
end

Интерес представляет a(:).

Когда мы передаем одно значение в качестве индекса матрице: a(i) MATLAB воспринимает матрицу как вектор столбец, состоящий из столбцов исходной матрицы поставленных друг за другом.

В нашем случае единственное двоеточее : в качества индекса, означает, что мы хотим извлечь все элементы.

>> myRand(2, 3)
ans =

    2.2259    2.4357    2.4302    2.9797
    2.1707    2.3111    2.1848    2.4389
    2.2277    2.9234    2.9049    2.1111
>> [X, ss] = myRand(2, 3)
X =

    2.0855    2.0292    2.4886    2.4588
    2.2625    2.9289    2.5785    2.9631
    2.8010    2.7303    2.2373    2.5468


ss =

   30.1106

Стоит заметить, что если не использовать множественное присвоение, как в [X, ss] = ..., MATLAB вернет только первый выходной аргумент функции:

>> X = myRand(2, 3)
X =

    2.8147    2.9134    2.2785    2.9649
    2.9058    2.6324    2.5469    2.1576
    2.1270    2.0975    2.9575    2.9706

Формальное определение функций

Интрерфейс функции с внешним миром определяется через заголовок функции. Этот заголовок называется декларацией функцией (сигнатура функции).

function [out_arg1, out_arg2, ...] = function_name(in_arg1, in_arg2)

Пошагово:

  1. Декларация функции начинается с ключевого слова function;
  2. После следуют выходные аргументы, если они имеются. Если имеются несколько выходных значений, небходимо их заключать в квадратные скобки через запятую;
  3. Если имеются выходные значения, даже одно, тогда необходим знак равно = после их перечисления;
  4. Далее следует название функции (правила наименования функций совпадают с правилами наименования переменных);
  5. Затем следуют входные аргументы, если они есть. Если у нет входных аргументов, то скобки являются необязательными, в противном случае между скобками располагается список имен входных аргументов, разделенных запятыми.

Примеры декларации функций:

function func
function func(in1)
function func(in1, in2)
function out1 = func
function out1 = func(in1)
function out1 = func(in1, in2)
function [out1, out2] = func
function [out1, out2] = func(in1)
function [out1, out2] = func(in1, in2)

Именование функций

  • Давайте названия, которые отражают суть вашей функции. Например, если вы создаете функцию для вычисления ряда Фибоначчи, не стоит называть её F или X. Лучше выбрать имя Fibonacci, Fibo или что-то подобное, что отражает её предназначение.
  • Избегайте использования имен встроенных функций, таких как sqrt, plot, sin и так далее.
    • Это быстро приведёт к путанице.
    • К тому же, если вы дадите своей функции такое имя, то не сможете использовать оригинальную встроенную функцию с таким же именем.
    • Чтобы проверить, занято ли уже какое-то имя, используйте встроенную функцию exist. Узнать, как она работает, можно с помощью команды помощи: help exist.

Взаимная коммуникация функций

Как мы уже видели, функции могут вызывать другие функции. Например, myRand обращается к встроенным функциям rand и sum. Но можно обойтись и без встроенных функций. Наши собственные функции тоже могут обращаться друг к другу.

Допустим, у нас есть функция bob, которую мы сохраняем в файл bob.m, и функция mary, сохранённая в файле mary.m. bob может вызвать mary, а marybob.

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

Зачем же помещать две функции в один файл? Если проблема, которую мы решаем, довольно сложная, разумно разделить её решение на несколько функций. В то же время, если эти функции скорее всего не пригодятся для решения других задач, логично оставить их все в одном M-файле.

Рассмотрим пример. Давайте перепишем последнюю версию нашей функции myRand:

function [a, s] = myRand(low, high)
    a = low + rand(3, 4) * (high-low);
    s = sumAllElements(a);

function summa = sumAllElements(M)
    v = M(:);
    summa = sum(v);

Замечание: Если в файле несколько функций, то либо у всех должны быть закрывающие ключевые слова end, либо ни у одной. Если мы не используем end, то начало следующей функции автоматически обозначает конец предыдущей.

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

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

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

Так, переменная v недоступна внутри myRand, переменная a недоступна в sumAllElements, и ни одна из переменных не доступна в командном окне.

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

Мы передаем a в качестве входного аргумента в sumAllElements. Это значение, которое является матрицей 3 на 4, копируется из a во входной аргумент m функции sumAllElements. Сумма всех элементов матрицы m считается так же, как и раньше, но теперь вычисления происходит внутри sumAllElements. Обратите внимание, что мы не можем получить доступ к данным через переменную a, так как она находится вне области видимости sumAllElements.

После того как sumAllElements вычислит сумму, информация возвращается в myRand через выходной аргумент. В нашем случае локальная переменная summa функции sumAllElements объявляется выходным аргументом (в заголовке: function summa = ...).

Как только функция sumAllElements завершает работу, ее выходной аргумент возвращается в точку вызова: s = sumAllElements(a);, и будет скопирован в s, который, в свою очередь, является выходным аргументом myRand.

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

Более подробно об области видимости

Область видимости определяется как множество выражений, имеющих доступ к переменной. В случае функций область видимости любой её переменной есть множество выражений принадлежащих телу функции.

Концепция области видимости является полезной поскольку способствует упрощению кода и накладывает автоматическое ограничение доступа в то же время держа свободным множество пространства имен (если бы все переменные были бы глобальными то скоро бы столкнулись с проблемой именования при росте числа строк).

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

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

Возьмем для примера нашу функцию sumAllElements:

function summa = sumAllElements(M)
    v = M(:);
    summa = sum(v);

И предположим, мы хотим сделать переменную v глобальной, чтобы иметь возможность обращаться к ней из командного окна. Это можно осуществить, добавив ключевое слово global при её объявлении:

function summa = sumAllElements(M)
    global v;
    v = M(:);
    summa = sum(v);

Замечание: Нельзя объединять объявление глобальной переменной с присваиванием. То есть, запись вида global v = M(:); не допустима.

Теперь переменная v доступна в области видимости всех остальных функций, командных строк и скриптов (если мы объявим переменную v в соответствующих областях видимости, используя global v;).

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

Чтобы удалить глобальную переменную из всех рабочих пространств, используйте: clear global variable.

Чтобы удалить глобальную переменную только из текущего рабочего пространства используйте: clear variable.

Теперь имея следующий код:

function [a, s] = myRand(low, high)
    a = low + rand(3, 4) * (high-low);
    s = sumAllElements(a);

function summa = sumAllElements(M)
    global v;
    v = M(:);
    summa = sum(v);

Вызвав функцию myRand, например:

>> myRand(5, 6)

Мы имеем доступ к глобальной переменной v из командного окна:

>> global v;
>> v
v =

    5.9572
    5.4854
    5.8003
    5.1419
    5.4218
    5.9157
    5.7922
    5.9595
    5.6557
    5.0357
    5.8491
    5.9340

Чтобы сделать глобальную переменную видимую из другой функции необходимо декларировать её использование, например:

function [a, s] = myRand(low, high)
    a = low + rand(3, 4) * (high-low);
    s = sumAllElements(a);
    global v;
    display(v);

function summa = sumAllElements(M)
    global v;
    v = M(:);
    summa = sum(v);

Таким образом после вызова функции myRand:

>> myRand();
v =

    5.8143
    5.2435
    5.9293
    5.3500
    5.1966
    5.2511
    5.6160
    5.4733
    5.3517
    5.8308
    5.5853
    5.5497


ans =

    5.8143    5.3500    5.6160    5.8308
    5.2435    5.1966    5.4733    5.5853
    5.9293    5.2511    5.3517    5.5497

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

Анонимные фнукции

f = @(x) sin(x);

Здесь:

  • f: Это имя, данное анонимной функции. После её определения её можно использовать, как и любое другое имя функции;
  • @(...): Обозначает входные аргументы функции. В этом контексте анонимная функция принимает один аргумент, x;
  • sin(x): Это выражение функции. Оно указывает, что функция возвращает. В этом случае возвращается \(\sin x\).

Инкапсуляция более сложной логики

function mark = grade(x)
    if x > 90
        mark = 'A';
    elseif x > 80
        mark = 'B';
    elseif x > 50
        mark = 'C';
    else
        mark = 'F';
    end
end
points = [50, 62, 81, 97];
grades = arrayfun(@grade, points);
disp(grades); %> FCBA

Символ @ создает ссылку на функцию (именованную либо анонимную), следующую за знаком @.

Создание ссылки на именованную функцию:

fhandle = @myfun

Создание ссылки на анонимную функцию:

fhandle = @(x,y) x.^2 + y.^2;

Пример. Приведем пример функции нахождения максимума из трех чисел

function result = max_val(a, b)
    if a > b
        result = a;
    else
        result = b;
    end
end

result = max_val(max_val(x, y), z)