Управление потоком выполнения кода :: Cетевой уголок Majestio

Управление потоком выполнения кода


Для управления потоком выполнения кода в 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

Цикл (оператор) for позволяет выполнить блок кода определенное количество раз. Пример:

var str = ''; 
for (var i = 0; i <= 4; i++) { 
  str += i.toString(); 
} 
print(str); // будет напечатано 01234

Цикл for-in

Для итерации по коллекциям использовать цикл 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 построчно
}
Итерация по объектам типа Map могжет осуществляться несколькими способами:
var 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 и 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! 
}

Что лучше посмотреть в документации:

  • Значения какого-то элемента списка не является важным? Или все не важны? Тогда используем нижнее подчеркивание
  • Хочется работать со списками произвольной длины, акцентируя внимание только на его начальных или последних элементах? Вспоминаем про троеточие
  • Нужно учесть ситуацию, что на определенном индексе списка могут попадаться разные элементы? Это тоже не проблема
  • К порядку сопоставления нужно подходить с особой осторожностью. Если первым блоком case будет идти слишком общий шаблон, то все остальные варианты просто не будут рассматриваться. Поэтому такие варианты шаблонов лучше смещать в конец
  • Не важно какое значение хранится по ключу в указанном шаблоне? Вспоминаем правила деструктурирования мэпы
  • Необходимо, чтобы входное значение хотя бы частично соответствовало шаблону и была возможность работать с пропускаемыми при сопоставлении значениями? Правила деструктурирования записи в помощь

switch-выражения, результат которого можно присваивать переменной или возвращать из функции

Небольшая уличная магия, использование нотации =>

void main() {
  var a = 10; 
  var b = switch (a) { 
    2 => 5 + a, 
    3 => 4 + a, 
    _ => 14 - a, // сработает значение по умолчанию
  }; 
  print(b); // будет напечатано 4
}
Рейтинг: 0/5 - 0 голосов