Порхающий дротик

Порхающий дротик: Операторы

Операторы Дарта ... один за другим

В проектах Flutter может использоваться как платформенно-зависимый, так и кроссплатформенный код. Последний написан на Dart, и для создания приложений Flutter требуются некоторые базовые знания Dart.

Цель Fluttering Dart - изучить фундаментальные знания и раскрыть советы и рекомендации мощного языка программирования, который воплощает в жизнь Flutter.

В первых двух частях серии мы рассмотрели встроенные типы данных и функции Дарта.

В этой части мы узнаем об операторах Дарта: как использовать и злоупотреблять или даже переопределять их поведение.

Некоторые примеры кода можно опробовать и поиграть с помощью DartPad.

Операторы

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

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

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

Для операторов, которые работают с двумя операндами, используется версия оператора самого левого операнда. Например, если у нас есть объект String и объект int, sVal + iVal использует строковую версию + и потребует использовать метод toString() int для приведения целочисленного значения к String .

// unary prefix operator
++one
// infix binary operator
one + two
// unary postfix
one++
// ternary operator
one ? two : three

Семантика операторов зависит от стратегии оценки и режима передачи аргументов. Выражение, содержащее оператор, каким-то образом оценивается, и результатом может быть просто значение или объект, допускающий присваивание. Как и в случае с Dart, мы уже выяснили, что все результаты являются объектами.

// example of operator
a + b 
// and equivalent function
add(a, b)

Приоритет

Как мы видим ниже, в Dart много операторов. Их приоритет определяет приоритет каждого оператора при выполнении нашего красивого кода.

Ассоциативность применяется к операторам с таким же приоритетом. Большинство операторов имеют ассоциативность слева направо (направление указано маленькой стрелкой). Операторы присваивания и условные операторы имеют его справа налево, а операторы без стрелки впереди не имеют ассоциативности.

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

r = a + b * c;

Приведенное выше выражение при выполнении обнаружит все операторы и выполнит их в порядке приоритета. Если мы посмотрим на список выше, мы определим 3 оператора (в порядке появления):

  • = - первое и наименее «важное»
  • + является вторым и немного более важным по сравнению с =
  • * - последний и самый "важный" из всех

Итак, у нас есть, в порядке важности, *, + и = (по иронии судьбы, полная противоположность их внешнего вида).

Во-первых, b будет умножено * на c. Затем результат будет добавлен + к c и, наконец, результат будет присвоен = r.

Чтобы это было легче читать, мы обычно пишем выражения так, как они вычисляются:

// easier to read
r = a + (b * c);

Категории

  • + Добавить assert(5 + 3 == 8);
  • - Вычтите assert(5 - 3 == 2);
  • -expr Унарный минус - меняет знак выражения assert(-(5 + 3) == -8);.
  • * Умножить assert(5 * 3 == 15);
  • / Разделить assert(5 / 3 == 1.6666666666666667);
  • ~/ Divide возвращает целочисленный результат assert(5 / 3 == 1);
  • % по модулю - возвращает остаток от целочисленного деления assert(5 % 3 == 2);.
  • expr++ Приращение постфикса - значение выражения expr
  • ++expr Приращение префикса - значение выражения expr + 1
  • expr-- Приращение постфикса - значение выражения expr
  • --expr Приращение префикса - значение выражения expr - 1

Префиксные и постфиксные операторы инкремента и декремента немного сложны для понимания:

var a, b;
a = 0;
b = ++a; // increments a first, then assigns the value to b
assert(b == a); // 1 == 1
a = b++; // first assigns the value to a, then increments a 
assert(b != a); // 2 != 1
print('a=$a and b=$b'); // prints a=1 and b2

Постфиксная операция expr++ или expr-- немного менее эффективна, чем префиксная операция ++expr или ++expr. В ситуациях, когда временное значение, созданное постфиксом, не используется, следует использовать префикс.

Это нарушает предыдущие правила приоритета. Что я могу сказать? Правила предназначены, чтобы их нарушать!

Описание спецификации языка программирования Dart (версия 2.2):

Оценка постфиксного выражения e формы v ++ соответственно v--, где v - это идентификатор, выполняется следующим образом: оценивает v объект r, и пусть y будет новой переменной, связанной с r . Вычислите v = y + 1 соответственно v = y - 1. Тогда e оценивается как r.

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

  • == равно assert(3 == 3);
  • != не равно assert(5 != 3);
  • > больше, чем assert(5 > 3);
  • < Менее assert(5 < 8);
  • >= больше или равно assert(5 >= 5);
  • <= Меньше или равно assert(5 <= 5);

Проверяет, представляют ли объекты одно и то же. Чтобы проверить, совпадают ли 2 объекта, мы должны использовать функцию identical().

Операция вызывается для первого операнда.

  • as Тип оператора приведения
  • is Проверка типа - верно, если объект имеет указанный тип.
  • is! Проверка типа - ложь, если объект имеет указанный тип.

Последние два оператора в основном проверяют, относятся ли два объекта к одному и тому же типу (что эквивалентно == и !=).

// because this operators work with objects, we'll use the fictional Cat and Dog these classes extend the Pet class
List pets = <Pet>[Cat('Simba'), Cat('Micuta')]; 
Cat oneCat = (pets[0] as Cat);
Cat anotherCat = (pets[1] as Cat);
assert(oneCat.name == 'Simba');
assert(anotherCat.name == 'Micuta');
assert(anotherCat is Cat);
assert(oneCat is! Dog);

Значения можно присвоить с помощью оператора =. Чтобы назначить только в том случае, если присвоенная переменная равна null, используйте оператор ??=, поддерживающий значение NULL.

Все указанные выше операторы, за исключением фактического оператора =, являются составным оператором.

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

  • !expr Инвертирует выражение (переключается между ложью и истиной) assert(!true == false);
  • && Логическое И assert(true && false == false);
  • || Логическое ИЛИ assert(true || false == true);

Мы можем использовать их, чтобы инвертировать или комбинировать логические выражения.

  • & И принимает два двоичных представления одинаковой длины и выполняет операцию логическое И с каждой парой соответствующих битов, что эквивалентно их умножению. Если оба бита в сравниваемой позиции равны 1, бит в результирующем двоичном представлении равен 1 (assert(1 & 1 == 1);), в противном случае результат равен 0 (assert(0 & 1 == 0);, assert(1 & 0 == 0) и assert(0 & 0 == 0);)
  • | ИЛИ принимает два двоичных представления одинаковой длины и выполняет операцию логическое включающее ИЛИ над каждой парой соответствующих битов. Результат в каждой позиции равен 0, если оба бита равны 0 (assert(0 | 0 == 0);), в противном случае результат равен 1 (assert(0 | 1 == 1);, assert(1 | 0 == 1) и assert(1 | 1 == 1);).
  • ^ XOR принимает два двоичных представления одинаковой длины и выполняет операцию логическое исключающее ИЛИ над каждой парой соответствующих битов. Результатом в каждой позиции будет 1, если только один из битов равен 1 (assert(0 ^ 1 == 1); и assert(1 ^ 0 == 1);), но будет 0, если оба равны 0 или оба равны 1 (assert(1 ^ 1 == 0); и assert(0 ^ 0 == 0);).
  • ~expr Унарное поразрядное дополнение переключает все биты между 0 и 1. (assert(1 & ~1 == 0);)
  • << Сдвиг влево сдвигает биты влево, а нули сдвигаются как новая цифра. (assert(1 << 1 == 2);)
  • >> Сдвиг вправо сдвигает цифры вправо, а нули заменяют отброшенные биты. (assert(1 >> 1 == 0);)

В Dart мы можем использовать побитовые операторы и операторы сдвига для управления битами двоичных данных.

4294967295 - это число, которое мы получим, если дополним 0. То есть (2 в степени 32) –1 или максимальное значение 32-битного целого числа без знака.

assert(~0 == math.pow(2,32) — 1);

Фактическое двоичное представление 4294967295 (в десятичном формате):

11111111111111111111111111111111

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





  • expr ? expr1 : expr2 Если expr истинно, вычисляет expr1 (и возвращает его значение); в противном случае оценивает и возвращает значение expr2
  • expr1 ?? expr2 Если expr1 не равно нулю, возвращает его значение; в противном случае вычисляет и возвращает значение expr2. Этот оператор с нулевым значением полезен, например, когда вы хотите убедиться, что вы по умолчанию используете значение в случае, если первое выражение - null:
class Cat {
  String name;
}
void main() {
  // we create a generic cat
  Cat genericCat = Cat();
  // we create a stray cat
  Cat strayCat = Cat();
  // we set the stray cat's name 
  // to the generic cat's name value 
  // that will be null so our stray cat
  // name will be Catonymous
  strayCat = genericCat.name ?? 'Catonymous';
}

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

// we'll use the fictional Cat class we used before
Cat()
  ..name = 'Simba'
  ..feed()
  ..pet();

Приведенный выше пример довольно свободно читается простым английским языком (красота каскадов): мы получаем бездомную кошку из ниоткуда, называем ее Симба, а затем кормим и гладим ее, чтобы она могла счастливо мяукать и мурлыкать.

  • () Приложение-функция - представляет собой вызов функции.
  • [] Доступ к списку - относится к значению по указанному индексу в списке.
  • . Доступ к члену - относится к свойству выражения.
  • ?. Условный доступ к члену - оператор с нулевым значением, который действует так же, как указано выше, но крайний левый операнд может иметь значение null.

Мы использовали их повсюду в предыдущих примерах (?. делает исключение, а также может помочь нам его избежать).

Условный доступ к члену, ?., полезен, когда мы хотим (или нуждаемся) избежать использования объектов, которые могут иметь значение NULL, без генерации исключения.

class Cat {
  String name;
}
void main() {
  Cat strayCat;
  // prints null
  print(t?.name);
  // throws null is not an object exception
  print(t.name);
}

Переопределение

Вы когда-нибудь мечтали определить собственное поведение для определенных операторов? Дротик позволяет вам осуществить ваши мечты через переопределение.

Имейте в виду, что только часть операторов Dart может быть переопределена. Это [], []=, ~, *, /, ~/, %, +, -, <<, >>, &, ^, |, <, >, <=, >= и ==.

!= не является переопределяемым оператором. Выражение expr1 != expr2 - это просто ярлык для !(expr1 == expr2), поэтому, если мы переопределим ==, мы также переопределим !=.

Для некоторых классов использование операторов более лаконично, чем использование методов. Например, класс List переопределяет оператор + для конкатенации списков. Код [a, b, c] + [d, e, f] очень легко понять.

Наконец, мы определяем класс Cat и хотим сравнить вес наших кошек. Мы делаем это, отменяя операторы отношения и равенства.

class Cat {
  
  final String name;
  final double weight;
  
  Cat({this.name, this.weight});
  
  bool operator ==(Object cat) => this.hashCode == cat.hashCode;
  String operator <(Cat cat) => (weight < cat.weight)?'$name is lighter than ${cat.name}.':'$name is heavier than ${cat.name}.';
  String operator >(Cat cat) => (weight > cat.weight)?'$name is heavier than ${cat.name}.':'$name is lighter than ${cat.name}.';
  String operator >=(Cat cat) => (weight >= cat.weight)?((this==cat)?'$name is as heavy as ${cat.name}.':'$name is heavier than ${cat.name}.'):'$name is lighter than ${cat.name}.';
  String operator <=(Cat cat) => (weight <= cat.weight)?((this==cat)?'$name is as light as ${cat.name}.':'$name is lighter than ${cat.name}.'):'$name is heavier than ${cat.name}.';
  
  @override
  int get hashCode => weight.hashCode;
  
}
void main() {
  // create our cats 
  Cat oneCat = Cat(name: 'Simba', weight: 4.1);
  Cat anotherCat = Cat(name: 'Micuta', weight: 3.8);
  Cat yetAnotherCat = Cat(name: 'Saki', weight: 3.8);
  Cat meowCat = Cat(name: 'Klein', weight: 5.0);
  // and compare them
  print(oneCat>anotherCat);
  print(yetAnotherCat<=anotherCat);
  print(meowCat<=oneCat);
  
}

Эти примеры переопределения немного сложнее, поскольку они изменяют даже тип результата вычисления выражения с bool на String, а в >= и <= даже используются два смешанных условных оператора. Мы сделали это, чтобы преодолеть тот факт, что мы не можем изменить возвращаемый тип ==, который должен оставаться логическим.

Обратите внимание, что мы переопределили геттер hashCode (он необходим при переопределении оператора ==).

Результатом приведенного выше кода является:

Simba is heavier than Micuta. 
Saki is as light as Micuta. 
Klein is heavier than Simba.

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



Это все!