Перейти к основному содержимому

5.22. Управляющие конструкции и операторы

Разработчику Архитектору

Управляющие конструкции и операторы

Условные конструкции

Условные конструкции позволяют выполнять различные блоки кода в зависимости от истинности или ложности заданного выражения. В Dart основной конструкцией этого типа является ifelse.

Конструкция if начинается с ключевого слова if, за которым следует выражение в круглых скобках. Если результат этого выражения приводится к логическому значению true, выполняется следующий за ним блок кода. Если значение выражения равно false, управление переходит к необязательному блоку else. Конструкция может содержать цепочку else if, что позволяет реализовать множественный выбор.

Пример:

int score = 85;

if (score >= 90) {
print('Отлично');
} else if (score >= 75) {
print('Хорошо');
} else if (score >= 60) {
print('Удовлетворительно');
} else {
print('Неудовлетворительно');
}

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

Для ситуаций, где требуется выбор по значению одной переменной, Dart предлагает выражение switch. Оно работает с константными значениями и поддерживает типы данных, такие как int, String, перечисления (enum) и некоторые другие. Каждый случай выбора оформляется с помощью метки case, завершающейся обязательным оператором break, если не используется continue, return или throw. Отсутствие явного выхода из case приведет к ошибке времени компиляции — это так называемый запрет на «проваливание» (fall-through), принятый для повышения читаемости и предотвращения ошибок.

Пример:

enum Color { red, green, blue }

void describeColor(Color color) {
switch (color) {
case Color.red:
print('Цвет — красный');
break;
case Color.green:
print('Цвет — зелёный');
break;
case Color.blue:
print('Цвет — синий');
break;
}
}

Начиная с Dart 3, выражение switch стало ещё мощнее благодаря поддержке шаблонов (pattern matching), что позволяет использовать его не только для простых значений, но и для деструктуризации объектов и списков. Однако даже в классическом виде switch остается удобным инструментом для четкого и структурированного выбора.


Циклы

Циклы предназначены для многократного выполнения блока кода. Dart предоставляет три основных вида циклов: for, while и dowhile.

Цикл for состоит из трех частей: инициализации, условия продолжения и шага обновления. Все эти части указываются в круглых скобках после ключевого слова for. Тело цикла выполняется до тех пор, пока условие продолжения возвращает true.

Пример:

for (int i = 0; i < 5; i++) {
print('Итерация $i');
}

Dart также поддерживает расширенную форму цикла for — так называемый цикл for-in, который используется для перебора элементов коллекций, таких как списки, множества или строки. Этот цикл автоматически извлекает каждый элемент коллекции и присваивает его переменной, объявленной в заголовке цикла.

Пример:

List<String> fruits = ['яблоко', 'банан', 'апельсин'];

for (String fruit in fruits) {
print(fruit);
}

Цикл while проверяет условие перед каждой итерацией. Если условие истинно, выполняется тело цикла. Если условие ложно с самого начала, тело цикла не выполнится ни разу.

Пример:

int count = 3;
while (count > 0) {
print('Осталось $count');
count--;
}

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

Пример:

int attempts = 0;
do {
print('Попытка номер ${attempts + 1}');
attempts++;
} while (attempts < 3);

Все циклы поддерживают операторы управления потоком выполнения: break для немедленного выхода из цикла и continue для перехода к следующей итерации без выполнения оставшейся части тела цикла.


Операторы сравнения и логические операторы

Операторы сравнения позволяют оценивать отношения между двумя значениями. В Dart к ним относятся:

  • == — равенство;
  • != — неравенство;
  • <, <=, >, >= — числовые сравнения.

Результатом любого из этих операторов является булево значение (true или false). Dart строго проверяет типы при сравнении, особенно при использовании оператора ==. Для пользовательских классов поведение оператора == можно переопределить, реализовав метод operator == и соответствующий hashCode.

Логические операторы работают с булевыми значениями и включают:

  • && — логическое И (возвращает true, только если оба операнда истинны);
  • || — логическое ИЛИ (возвращает true, если хотя бы один операнд истинен);
  • ! — логическое НЕ (инвертирует значение: !true даёт false, !falsetrue).

Dart поддерживает ленивые вычисления (short-circuit evaluation) для && и ||. Это означает, что если результат выражения можно определить по первому операнду, второй операнд не вычисляется. Например, в выражении a && b(), если a равно false, функция b() не будет вызвана. Это полезно для предотвращения ошибок, таких как обращение к null.


Арифметические и побитовые операторы

Арифметические операторы выполняют базовые математические действия:

  • +, -, *, / — сложение, вычитание, умножение, деление;
  • ~/ — целочисленное деление (возвращает int);
  • % — остаток от деления.

Оператор / всегда возвращает значение типа double, даже если делимое и делитель — целые числа. Для получения целого результата используется ~/.

Пример:

print(7 / 2);   // 3.5
print(7 ~/ 2); // 3
print(7 % 2); // 1

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

  • & — побитовое И;
  • | — побитовое ИЛИ;
  • ^ — побитовое исключающее ИЛИ (XOR);
  • ~ — побитовое НЕ (инверсия всех битов);
  • << — сдвиг влево;
  • >> — сдвиг вправо.

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


Операторы присваивания

Оператор присваивания = связывает переменную со значением. Dart также предоставляет составные операторы присваивания, которые объединяют арифметическую или побитовую операцию с присваиванием:

  • +=, -=, *=, /=, ~/=, %=;
  • &=, |=, ^=, <<=, >>=.

Пример:

int x = 10;
x += 5; // эквивалентно x = x + 5;

Такие операторы делают код более компактным и читаемым.


Тернарный условный оператор

Тернарный оператор — это сокращённая форма условной конструкции ifelse. Он имеет вид:

условие ? выражение_если_true : выражение_если_false

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

Пример:

String status = score >= 60 ? 'Сдано' : 'Не сдано';

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


Операторы null-aware

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

  • ?? — оператор объединения с нулём (null-coalescing): возвращает левый операнд, если он не null, иначе — правый.

    Пример:

    String name = userName ?? 'Гость';
  • ??= — присваивание только если текущее значение null.

    Пример:

    config.theme ??= 'light';
  • ?. — условный оператор доступа к члену: вызывает метод или обращается к свойству только если объект не null. Если объект null, всё выражение возвращает null.

    Пример:

    int? length = user?.name?.length;

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


Операторы проверки и приведения типов

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

  • is — проверяет, принадлежит ли объект указанному типу. Возвращает true, если тип совпадает.

    Пример:

    if (obj is String) {
    print(obj.length);
    }
  • is! — отрицание is: возвращает true, если объект не принадлежит указанному типу.

  • as — приведение типа. Используется, когда разработчик уверен в типе объекта, но компилятор не может это гарантировать. При несоответствии типов во время выполнения возникает исключение.

    Пример:

    (obj as String).toUpperCase();

В большинстве случаев предпочтительнее использовать is перед обращением к свойствам, чтобы избежать необходимости в as.


Операторы управления потоком: break, continue, return

Хотя эти слова не являются «операторами» в строгом смысле, они управляют выполнением кода:

  • break — немедленно завершает выполнение цикла или switch.
  • continue — прерывает текущую итерацию цикла и переходит к следующей.
  • return — завершает выполнение функции и возвращает значение (если указано).

В Dart также поддерживается маркировка циклов (labeled loops), что позволяет указывать, из какого именно цикла нужно выйти при вложенных конструкциях:

outerLoop:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outerLoop;
}
print('($i, $j)');
}
}