Порхающий дротик
Порхающий дротик: Операторы
Операторы Дарта ... один за другим
В проектах 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.
Это все!