В данном разделе будут рассмотрены функции. Методы будут раccмотрены только в теме объявлений входных аргументов.
Важное замечание!
В языке программирования Dart примитивные типы int, double, bool и String передаются по значению, остальные типы - передаются по ссылке!
Входные аргументы функции Dart подразделяются на:
Самыми простыми в понимании являются позиционное аргументы, так как последовательность передаваемых на вход функций переменных, должна соответствовать последовательности и типу объявленных в функции аргументов:
void myFunction(String name, int date, String monthName) {
print('$name родился $date $monthName!');
}
void main(List<String> arguments) {
myFunction('Александр', 10, 'сентября');
}
// будет напечатано: Александр родился 10 сентября!
Для объявления того, что на вход функции аргументы передаются именованным образом необходимо обернуть их в фигурные скобки «{ }». После чего, в момент вызова функции явно указать какому именованному аргументу какое значение передается. Если тип объявляемого аргумента null-safety, т.е. не может хранить значение null, то перед объявляемым аргументом используйте ключевое слово required.
Если же значение передаваемого аргумента может хранить значение null, то после указания его типа нужно добавить символ ? и предусмотреть проверку на null, который может передаться в двух случаях:
void myFunction({
String? name,
required int date,
required String monthName,
}) {
if (name != null) {
return '$name родился $date $monthName!';
}
return 'Не установлено имя новорожденного!';
}
void main(List<String> arguments) {
myFunction(date: 10, name: 'Александр', monthName: 'сентября',);
myFunction(date: 10, monthName: 'сентября',);
}
// будет напечатано: Александр родился 10 сентября!
// будет напечатано: Не установлено имя новорожденного!
Значения могут передаваться в произвольной последовательности, так как мы явно указываем какому из аргументов функции будет соответствовать то или иное значение.
Когда мы указываем, что именованный аргумент может принимать значение null, он становится необязательным при вызове функции. Но когда, кровь из носу, запрещено давать такие вольности разработчику, который будет использовать ваш код – нужно пометить такой именованный аргумент, как required. Тогда у него не останется вариантов, кроме как самому передать такому именованному аргументу вызываемой функции null.
Для того, чтобы указать необязательные аргументы при их позиционном размещении, нужно обернуть их в квадратные скобки [ ]:
String myFunction(String name, int date, [String? monthName]) {
if (monthName != null) {
return '$name родился $date $monthName!';
}
return '$date числа, неустановленного месяца, родился $name!';
}
void main(List<String> arguments) {
print(myFunction('Александр', 20));
print(myFunction('Александр', 20, 'мая'));
}
// будет напечатано: 20 числа неустановленного месяца родился Александр!
// будет напечатано: Александр родился 20 мая!
Когда мы объявляем необязательные позиционные или именованные аргументы, они по умолчанию инициализируются значением null (когда при вызове функции им не задается отличное от null значение). Для того, чтобы присвоим им значение по умолчанию, отличное от null, после объявления имени аргумента нужно добавить оператор присваивание и само значение, которое будет использоваться в коде функции всякий раз, пока явно не передастся другое значение. При этом не имеет значения, тип объявляемого аргумента – null-safety или нет:
String myFunction(
String name, [
int? date = 10,
String monthName = 'июля',
]) {
return '$name родился $date $monthName!';
}
void main(List<String> arguments) {
print(myFunction('Александр'));
print(myFunction('Александр', 24));
print(myFunction('Александр', 20, 'мая'));
}
// будет напечатано: Александр родился 10 июля!
// будет напечатано: Александр родился 24 июля!
// будет напечатано: Александр родился 20 мая!
Чтобы ввести значение по умолчанию для именованных аргументов, достаточно просто присвоить ему значение и не использовать ключевое слово required. К расположению значений по умолчанию для такого типа аргументов требований нет, но лучше придерживаться устоявшейся традиции и объявлять их в конце:
String myFunction({
required String name,
required String monthName,
int date = 10,
}) {
return '$name родился $date $monthName!';
}
void main(List<String> arguments) {
print(myFunction(name: 'Александр', monthName: 'мая'));
print(myFunction(
date: 14,
name: 'Александр',
monthName: 'мая',
));
}
// будет напечатано: Александр родился 10 мая!
// будет напечатано: Александр родился 14 мая!
int addFunc(int a, int b) {
return a + b;
}
String seasonFunc(int month) {
return switch (month) {
== 12 || > 0 && < 3 => 'Зима',
>= 3 && < 6 => 'Весна',
>= 6 && < 9 => 'Лето',
>= 9 && < 12 => 'Осень',
_ => "Хрень в аргументах!",
};
}
String myStrFunc(
String prefix,
int month,
String Function(int) func,
) {
return prefix + ': ' + func(month);
}
int subFunc(
int a,
int b, {
int c = 10,
int Function(int, int) func = addFunc,
}) {
return c - func(a, b);
}
void main() {
var season = seasonFunc; // 1. Пример присвоение функции переменной
print(myStrFunc('Сезон', 12, season)); // 2. Пример использовании функции как аргумента
print(myStrFunc("Сезон", 0, season));
print(subFunc(3, 7));
print(subFunc(2, 4, c: 2));
print(subFunc(2, 4, c: 2, func: (int a, int b) { // 3. Пример использовании анонимной функции (лямбды) как аргумента
return a * b;
}));
}
Результат работы будет следующий:
Сезон: Зима Сезон: Хрень в аргументах! 0 -4 -6Еще один пример:
int sub(
int a,
int b, {
int c = 10,
int Function(int, int)? func,
}) {
if (func == null) {
return 0;
}
return c - func(a, b);
}
void main() {
var a = 13, b = 12;
print(sub(
2,
4,
c: 2,
func: a < b
? (int a, int b) {
return a * b;
}
: (int a, int b) {
return a - b;
},
));
}
Вывод:
4
typedef TypeMyMap = Map<String, Map<(int, List<int>), int>>; // 1. Алиас для сложного типа
typedef TypeMyMapFunc = int Function(TypeMyMap)?; // 2. Алиас для функции со сложным типом аргумента
int myFunc(TypeMyMap data) {
var sum = 0.0;
for (var MapEntry(:value) in data.entries) {
for (var MapEntry(key: recKey, value: recValue) in value.entries) {
var (int a, List<int> b) = recKey;
sum += (a * b.reduce((value, element) => value + element)) / recValue;
}
}
return sum.floor();
}
int add(
int a,
TypeMyMap data, {
TypeMyMapFunc func,
}) {
if (func == null) {
return 0;
}
return a + func(data);
}
void main() {
TypeMyMap map = {
'a': {
(1, [1, 2, 3]): 100,
(2, [2, -4, 9]): -98,
(3, [3, 4, 5]): 3,
},
'b': {
(10, [1, 0, 3]): 100,
(20, [6, -4, 2]): -98,
(30, [-3, 4, -5]): 3,
}
};
print(add(22, map, func: myFunc));
}
Будет напечатано:
-7
void main(List<String> arguments) {
var myList = ['Привет!', 'Я', '-', 'анонимная', 'функция!'];
myList.forEach((item) {
print( 'По индексу ${myList.indexOf(item)} хранится значение => $item' );
}
);
}
Будет напечатано:
По индексу 0 хранится значение => Привет! По индексу 1 хранится значение => Я По индексу 2 хранится значение => - По индексу 3 хранится значение => анонимная По индексу 4 хранится значение => функция!В данном примере анонимная функция имеет следующую реализацию:
(item) { // блок с входными аргументами, аргументы могут отсутствовать
// блок тела функции
}
int add(int a, int b) => a + b;
void main(List<String> arguments) {
print(add(1,2));
}
Будет напечатано:
3
Тем не менее, мы можем переписать пример выше, где возвращаемое будет просто игнорироваться:Важное замечание!
У стрелочных функций есть особенность - по умолчанию они всегда возвращают значение, то есть оператор return в этих функциях не используется, но его наличие подразумевается.
void main(List<String> arguments) {
var myList = ['Привет!', 'Я', '-', 'анонимная', 'функция!'];
myList.forEach((item) => print( 'По индексу ${myList.indexOf(item)} хранится значение => $item' ));
}
Вывод в консоль будет аналогичен:
По индексу 0 хранится значение => Привет! По индексу 1 хранится значение => Я По индексу 2 хранится значение => - По индексу 3 хранится значение => анонимная По индексу 4 хранится значение => функция!
Замыкание в Dart — это функция, которая запоминает переменные из своей внешней области видимости и может возвращать другую функцию, использующую эти данные, даже если они не передаются напрямую.
Общий вид:возвращаемый_тип Function(параметры) название(параметры) {
return чего-то-там
}
Допускается короче:
Function название(параметры) {
return чего-то-там
}
Замыкания в Dart могут быть определены как использованием Function, так и реализованы как анонимные функции.
Все они автоматически являются замыканиями, если они захватывают переменные.Важно!
Синтаксис Function упрощает работу с функциями высшего порядка, позволяя передавать функции как аргументы и возвращать их из других функций. Это делает код более выразительным и удобным для работы.
Использование замыканий особенно оправдано в следующих ситуациях, где они повышают читаемость, гибкость и эффективность кода:
В этих случаях замыкания делают код более выразительным, уменьшают дублирование и повышают его модульность, что значительно улучшает КПД программиста и ясность логики. Далее подробнее про примеры реализации в перечисленных ситуациях.
Function counter() {
int count = 0;
return () => ++count;
}
void main(List<String> arguments) {
final increment = counter();
print(increment()); // 1
print(increment()); // 2
}
Будет напечатано:
1 2
Function createLogger(String prefix) {
return (String message) => print('$prefix: $message');
}
void main(List<String> arguments) {
final logError = createLogger('ERROR');
logError('Something went wrong'); // ERROR: Something went wrong
}
Будет напечатано:
ERROR: Something went wrong
Function multiply(int a) {
return (int b) => a * b;
}
void main(List<String> arguments) {
final double = multiply(2);
print(double(5)); // 10
}
Будет напечатано:
10
void fetchData(void Function(String) callback) {
Future.delayed(Duration(seconds: 1), () => callback('Data loaded'));
}
void main(List<String> arguments) {
fetchData((data) => print(data)); // Data loaded
}
Будет напечатано:
Data loaded
Function createGreeter(String greeting) {
return (String name) => '$greeting, $name!';
}
void main(List<String> arguments) {
final sayHello = createGreeter('Hello');
print(sayHello('Alice')); // Hello, Alice!
}
Будет напечатано:
// Hello, Alice!
Function lazySum(int a, int b) {
return () => a + b;
}
void main(List<String> arguments) {
final sum = lazySum(3, 4);
print(sum()); // 7
}
Будет напечатано:
7
import 'dart:async';
void main() {
// Создаем таймер, который будет срабатывать каждые 2 секунды
Timer.periodic(Duration(seconds: 2), (Timer timer) {
// Обработчик события таймера
print('Timer ticked at: ${DateTime.now()}');
// Прекращаем таймер через 10 секунд
if (timer.tick >= 5) {
print('Stopping the timer.');
timer.cancel();
}
});
}
Или с использованием ключевого слова Function:
import 'dart:async';
void main() {
// Создаем переменную типа Function для обработчика таймера
void Function(Timer) timerHandler = (Timer timer) {
print('Timer ticked at: ${DateTime.now()}');
// Остановка таймера через 10 секунд
if (timer.tick >= 5) {
print('Stopping the timer.');
timer.cancel();
}
};
// Создаем таймер, который будет срабатывать каждые 2 секунды
Timer.periodic(Duration(seconds: 2), timerHandler);
}
Будет напечатано примерно следующее:
Timer ticked at: 2025-03-15 20:48:54.191 Timer ticked at: 2025-03-15 20:48:56.191 Timer ticked at: 2025-03-15 20:48:58.187 Timer ticked at: 2025-03-15 20:49:00.186 Timer ticked at: 2025-03-15 20:49:02.192 Stopping the timer.
Function memoizedFibonacci() {
final cache = <int, int>{};
int fibonacci(int n) {
if (cache.containsKey(n)) return cache[n]!;
if (n <= 1) return n;
cache[n] = fibonacci(n - 1) + fibonacci(n - 2);
return cache[n]!;
}
return fibonacci;
}
void main() {
final fib = memoizedFibonacci();
print(fib(10)); // 55
}
Будет напечатано:
55