Для управления потоком выполнения кода в Dart используются следующие виды операторов:
Оператор if, с необязательным оператором else, выбирает действия, которые будут выполняться в процессе работы программы в зависимости от условий (результата выполнения выражений), которые проверяются в скобках, после объявления оператора if. Общая форма конструкции «если-то-иначе» представлена ниже:
if (условие1) { блок1 } else if (условие2) { блок 2 } … else if (условие(n-1)) { блок (n-1) } else { блок (n) }
Начиная с третьей версии в Dart появилась поддержка оператора if-case, позволяющего более удобным способом проверять объекты на необходимый тип, форму и т.д. Общий вид принципа работы этого оператора можно представить следующим образом:
if (значение case шаблон) { блок1 } else if (значение case шаблон) { блок 2 } … else if (значение case шаблон) { блок (n-1) } else { блок (n) }Представим, что у нас на входе список и необходимо проверить, действительно ли в нем 2 значения, после чего деструктурировать его по переменным:
void main() {
List<int> myList = [1, 2, 3];
if (myList case [int x, int y]) {
print('2 значения $x и $y');
}
else if (myList case [int x, ..., int y]) {
print('3 и более значений'); // 3 и более значений
}
}
При работе с классами оператор if-case можно использовать как для проверки, относится ли объект к нужному типу данных, так и сразу для его деструктурирования, чтобы перенести необходимые значения полей в переменные, с которыми в блоке if и продолжится работа.
На проверяемые объекты, а точнее на их значения, которые в процессе сопоставления записываются в переменные можно накладывать Guard clause (защитное условие). Для этого после case с указанием шаблона следует ключевое слово when, за которым идет условное выражение:
class Employee {
final String name;
final int age;
final int salary;
Employee(this.name, this.age, this.salary); }
}
void main() {
dynamic obj = Employee('John', 30, 1000);
if (obj case Employee(:String name, :int age, :int salary) when age > 20) {
// Employee name is John, age is 30, salary is 1000
print( 'Employee name is $name, age is $age, salary is $salary' );
}
}
Общий вид записи тернарного оператора можно представить следующим способом:
condition ? expr1 : expr2
Если условие истинно, то вычисляется и возвращается expr1, в противном случае вычисляется и возвращается значение expr2. Для примера давайте посредством данного оператора реализуем поиск максимального из двух значений:
var a = 5, b =10;
var c = a > b ? a : b;
print('Max is $c'); // будет напечатано 10
Конечно, он может быть и вложенным, но тогда сильно страдает читаемость кода.
Общий вид записи данного оператора можно представить следующим способом:
expr1 ?? expr2
Если expr1 не равно null, возвращается его значение, иначе вычисляется и возвращается значение expr2. Пример:
int? calculate([int? a]) {
if (a == null) {
return a;
}
return a * 7;
}
void main() {
var c = calculate() ?? calculate(10);
print(c); // будет напечатано 70
var d = calculate(3) ?? calculate(10);
print(d); // будет напечатано 21
}
Цикл (оператор) for позволяет выполнить блок кода определенное количество раз. Пример:
var str = '';
for (var i = 0; i <= 4; i++) {
str += i.toString();
}
print(str); // будет напечатано 01234
Для итерации по коллекциям использовать цикл for-in, который позволяет перебирать значения, возвращаемые любым объектом, поддерживающим итерацию: списки, множества и т. д., то есть объект должен представлять собой коллекцию из элементов. Пример:
var myList = <int>[for (var i = 0; i<= 3; i++) i];
for (var it in myList) {
print(it); // будет напечатано 0 1 2 3 построчно
}
var mySet = <int>{1, 2, 5, 6, 7, 8};
for (var it in mySet) {
print(it); // будет напечатано 1 2 5 6 7 8 построчно
}
Итерация по объектам типа Mapvar myMap = <int, String> { 1: 'Мама', 2: 'мыла', 3: 'раму' };
// старый способ
myMap.forEach((key, value) {
print('$key => $value');
});
// или
for (var it in myMap.entries) {
print('${it.key} => ${it.value}');
}
// более новый способ с деструктурированием элементов итерируемой коллекции
for (var MapEntry(:key, :value) in myMap.entries) {
print('$key => $value');
}
Принципы работы циклов while и do-while довольно похожи. Ключевое различие заключается в том, что цикл while может ни разу не выполниться. Это связано с тем, что сначала проверяется условие его выполнения. Примеры:
var myStr = 'Hi!';
var i = 0;
while(i < myStr.length) {
print(myStr[i]);
i++;
}
// будет напечатано H i ! построчно
i = 0;
do {
print(i);
i++;
} while(i < 3);
// будет напечатано 0 1 2 построчно
Оператор continue используется для немедленного перехода в начало цикла, в котором он был вызван. Оператор break используется для немедленного выхода из цикла, в котором он был вызван. Оператор return возвращает результат функции, switch-выражения, метода класса и завершает их выполнение.
Операторы continue и break в определенных случаях могут использоваться совместно с метками переходов. Например, при использовании меток («название_метки:») с оператором break можно сразу выйти из нескольких вложенных друг в друга циклов.
Оператор switch-case используется как правило для дамены длинных цепочек if-else. В Dart 2 оператор switch-case позволял сравнивать целочисленные, строковые переменные или константы времени компиляции с помощью оператора сравнения «==». Начиная с Dart 3, сравнение осуществляется с помощью шаблонов (Pattern Matching), указываемых после ключевого слова case. В общем виде конструкцию switch-case можно записать следующим образом:
switch(объект) { case шаблон1: блок1 case шаблон2: блок2 case шаблон3: блок3 ... default: блок (n) // действие по умолчанию }
В отличии от С++ в блоках не нужно добавлять ключевое слово break, если блок не пустой. В качестве шаблонов можно использовать не только константы, но и выражения. Пример:
void main() {
var i = 4;
switch (i) {
case > 1 && < 3:
case > 3 && < 5:
print('больше 1 и меньше 3 - или - больше 3 и меньше 5');
case == 4:
print('больше 4'); // не сработает, т.к. сработает правило выше
default:
print('default');
}
}
// будет напечатано: больше 1 и меньше 3 - или - больше 3 и меньше 5
Так как оператор switch-case осуществляет сопоставление шаблонов, то его можно использовать со списками, мэпами, записями и классами (объектами). Для начала приведем пример полного сопоставления шаблона с подаваемым списком:
void main() {
var myList = [
[],
[1],
[1, 2, 3],
[1, 2, 3, 4, 5], ];
var myStr = '';
for (var element in myList) {
switch (element) {
case [1]:
myStr += '1';
case [1, 2, 3]:
myStr += '3';
case []:
myStr += '0';
default:
myStr += '!';
}
}
print(myStr); // будет напечатано 013!
}
Что лучше посмотреть в документации:
Небольшая уличная магия, использование нотации =>
void main() {
var a = 10;
var b = switch (a) {
2 => 5 + a,
3 => 4 + a,
_ => 14 - a, // сработает значение по умолчанию
};
print(b); // будет напечатано 4
}