Dart演算子

Dart

Dart演算子について調査したのでメモ書き。公式サイトはこちら
なお、演算子の紹介で使用する変数は「expr」を用いる。

Dart演算子基礎知識

演算子の優先順位と結合性は以下の通り。優先順位が高い方から記述していく。
「分類」の()内に補足を入れておく。

分類演算子結合性
unary postfix(単項後置演算子)expr++    expr--    ()    []    ?[]    .    ?.    !無し
unary prefix(単項前置演算子)-expr    !expr    ~expr    ++expr    
--expr    await expr
無し
multiplicative(乗除演算子)*    /    %  ~/左結合性
additive(加減演算子)+    -左結合性
shift(シフト)<<    >>    >>>左結合性
bitwise AND(ビット論理積)&左結合性
bitwise XOR(ビット排他的論理和)^左結合性
bitwise OR(ビット論理和)|左結合性
relational and type test
(関係演算子と型テスト演算子)
>=    >    <=    <    as    is    is!無し
equality(等価演算子)==    !=無し
logical AND(論理積)&&左結合性
logical OR(論理和)||左結合性
if-null(null合体演算子)??左結合性
conditional(三項演算子)condition ? expr1 : expr2右結合性
cascade(カスケード演算子)..    ?..左結合性
assignment(代入演算子)=    *=    /=   +=   -=   &=   ^=   etc.右結合性
spread(スプレッド構文)...    ...?無し


結合性とは同じ優先順位の演算子が並んでいる時、左右どちらから計算するかのルール。
additiveを例にすると、additiveは左結合性なので左から計算する。

int a = 1 + 4 - 3;
print(a); // 2を返す

additiveとmultiplicativeはmultiplicativeの方が優先順位が高いので、先に計算される。

int a = 1 + 4 - 3 * 3;
print(a); // -4を返す

unary postfix等結合性がないものは並べて記載できない。
そのため、以下のような記述はシンタックスエラーとなる。

int c = a++ b--;
int c = 1 a++;

ただし、以下の場合「+」演算子が左結合性を持つので、こういったケースではシンタックスエラーにはならない。

int a = 1;
int b = a++ + 1 * 3; // 4を返す

Dartでは演算子をクラスメンバーとして実装できる。
以下例ではplusクラス用に”+”をオーバーロードしている。
オーバーロードとは、同じメソッド名を持ちながら引数の数や型が異なる複数のメソッドを定義する事をいう。使用時は引数の数や型によってどのメソッドが呼び出されるか決定される。
似た言葉にオーバーライド(override)があるが、これは親クラスで定義されたメソッドを子クラスで再定義する事をいう。

class plus {
  final int x, y;

  plus(this.x, this.y);

  // +演算子のオーバーロード。plusオブジェクト同士で加算できるようにしている
  plus operator +(plus other) {
    return plus(x + other.x, y + other.y);
  }

  // toString()メソッドのオーバーライド。plusクラス用に再定義
  @override
  String toString() => 'plus($x, $y)';
}

void main() {
  plus v1 = plus(2, 3);
  plus v2 = plus(4, 5);
  // plusクラスでオーバーロードした加算演算子を使用
  plus v3 = v1 + v2;
  // print()では暗黙的にtoString()が呼ばれるので、print(v3)でもOK
  print(v3.toString());
}

実行結果。

plus(6, 8)

各演算子詳細

各演算子の詳細について記述していく。

unary postfix演算子

unary postfix(単項後置演算子)は1つの変数等の後に置かれる演算子の事。

expr++

後置インクリメント演算子。
変数exprの値を使用した後に値を1増やす。

動作確認。

void main() {
  int a = 1;
  print("a:$a");

  // bにaの値を代入
  int b = a++;
  print("b:$b");
  print("a:$a");
}

実行結果。
変数bに値を代入した後、aは値を1増やす。

a:1
b:1
a:2

expr–

後置デクリメント演算子。
変数exprの値を使用した後に値を1減らす。

動作確認。

void main() {
  int a = 1;
  print("a:$a");

  // bにaの値を代入
  int b = a--;
  print("b:$b");
  print("a:$a");
}

実行結果。
変数bに値を代入した後、aは値を1減らす。

a:1
b:1
a:0

()

()演算子は関数呼び出しで使用。

動作確認。

void funCall(String s) {
  print('Hello $s');
}

void main() {
  // 関数呼び出し
  funCall('Yamada');
}

実行結果。

Hello Yamada

[]

[]演算子はListで使用。

動作確認。
Listの添字は0から始まるので、fooList[1]とした場合、Listの2番目の要素を返す。

void main() {
  List<int> fooList = [10, 20, 30];
  print(fooList[1]);
}

実行結果。

20

?[]

?[]はnullセーフティインデックスアクセス演算子。
nullの可能性があるListにアクセスする時に使用する。nullの場合はnullを返す。

動作確認。

void main() {
  List<int>? fooList = null;
  print(fooList?[1]);
}

実行結果。

null

.

“.”はメンバーアクセス演算子。
オブジェクトのプロパティやメソッドにアクセスするために使用。

動作確認。”.”を使い文字数を返すlengthプロパティにアクセスする。

void main() {
  String text = 'Hello!';
  print(text.length);
}

実行結果。

6

?.

“?.”はnullセーフティメンバーアクセス演算子。
オブジェクトがnullの可能性がある場合に使用する。nullの場合はnullを返す。

動作確認。

void main() {
  String? text = null;
  print(text?.length);
}

実行結果。

null

!

!は非nullアサーション演算子。
オブジェクトがnullでない事を保証する時に使用する。

三項演算子を使って動作確認。三項演算子については別途記載。

void main() {
  String? text = 'Hello';
  String? cp;
  text == 'Hello' ? cp = 'Hello World' : cp = null;
  print(cp!.length);
}

実行結果。

11

unary prefix演算子

unary prefix(単項前置演算子)は1つの変数等の前に置かれる演算子の事。

-expr

単項マイナス演算子。数値の符号を反転させる。

動作確認。

void main() {
  int a = 5;
  int b = -a;
  int c = -b;
  print('''
a:$a
b:$b
c:$c
    ''');
}

実行結果。

a:5
b:-5
c:5

!expr

!exprは論理否定演算子。bool値を反転させる。

動作確認。

void main() {
  bool isTrue = true;
  bool isFalse = !isTrue;
  print(isTrue);
  print(isFalse);
}

実行結果。

true
false

~expr

~exprはビット反転演算子。数値のビットを反転させる。

動作確認。

void main() {
  int a = 2;
  int b = ~a;
  print(a);
  print(b);
}

実行結果。

2
-3

10進数の2は2進数8ビットで表すと「0000 0010」になる。
これが反転するので「1111 1101」となる。符号付き2進数は先頭が「1」の場合マイナスになり、この「1111 1101」を10進数に直すと-3になる。
ビット付き2進数は値を反転させると数値が1減る。要するに2の場合は-2ではなく、-3となる。これは2の補数表現といい、1つ値を減らさないとプラスとマイナス値それぞれで0ができてしまう。10進数0「0000 0000」を反転させた「1111 1111」は-1となり、10進数1「0000 0001」を反転させた「1111 1110」は-2となる。

++expr

前置インクリメント演算子。変数の値を1増やし、その値を返す。

動作確認。

void main() {
  int a = 1;
  int b = ++a;
  print("a:$a");
  print("b:$b");
}

実行結果。

a:2
b:2

–expr

前置デクリメント演算子。変数の値を1減らし、その値を返す。

動作確認。

void main() {
  int a = 1;
  int b = --a;
  print("a:$a");
  print("b:$b");
}

実行結果。

a:0
b:0

await expr

非同期演算子。非同期関数の結果を待つ。awaitは非同期関数内で使用され、非同期処理が完了するまで待機する。

先ずはawaitを付けない場合を確認。
非同期処理Future.delayed(Duration(seconds: 1)で1秒処理待ちを行う。

Future<void> fetchData() async {
  Future.delayed(Duration(seconds: 1), () {
    print(1);
  });
}

void main() async {
  fetchData();
  print(2);
}

実行結果。
Future.delayedの終了を待たずにprint(2);が実行される。

2
1

次にawaitを付けて実行。

Future<void> fetchData() async {
  await Future.delayed(Duration(seconds: 1), () {
    print(1);
  });
}

void main() async {
  await fetchData();
  print(2);
}

実行結果。Future.delayedの完了を待ってからprint(2);が実行される。

1
2

multiplicative演算子

multiplicative(乗除演算子)について記載。

*

乗算演算子。

void main() {
  int a = 2 * 5;
  print(a); // 10を出力
}

/

除算演算子。

void main() {
  double a = 10 / 5;
  print(a); // 2を出力
}

%

剰余演算子。

void main() {
  int a = 10;
  int b = 3;
  print(a % b); // 1を出力
}

~/

整数剰余演算子。
2つの数値を割り算し、結果を整数として返す。

void main() {
  int a = 10;
  int b = 3;
  print(a ~/ b); // 3を出力
}

additive演算子

+

加算演算子。

void main() {
  int a = 10;
  int b = 3;
  print(a + b); // 13を出力
}

減算演算子。

void main() {
  int a = 10;
  int b = 3;
  print(a - b); // 7を出力
}

shift演算子

<<

左シフト演算子。指定したビット数だけ左にシフトする。

void main() {
  int a = 3; // 3は8ビットで00000011
  int result = a << 2; // 00001100にシフト
  print(result); // 12(00001100)を出力
}

>>

右シフト演算子。指定したビット数だけ右にシフトする。

void main() {
  int a = 12; // 12は8ビットで00001100
  int result = a >> 2; // 00000011にシフト
  print(result); // 3(00000011)を出力
}

>>>

符号なし右シフト演算子。指定したビット数だけ右にシフトするが、左側には常に0(符号付きで負の場合は1)が入る。
※ int型は本来64ビットと長いため一部省略します。

void main() {
  int a = -12; // -12はビットで1111~11110100
  int result = a >>> 2; // resultは0011~11111101。左側に0が入る
  print(result); // 4611686018427387901を出力
}

bitwise AND演算子

&

bitwise AND(ビット論理積)は2つのビット値を比較し、両方のビットが1の場合に1を返す演算子。

動作確認。

void main() {
  int a = 6;
  int b = 3;
  print(a & b);
}

実行結果。

2

上記について説明すると、6は8ビットで「0000 0110」、3は8ビットで「0000 0011」となる。
6と3の論理積は、

0000 0110
0000 0011
---------
0000 0010

「0000 0010」となり、10進数で2になる。

bitwise XOR演算子

^

bitwise XOR(ビット排他的論理和)は2つのビット値を比較し、異なるビットの位置だけが1になる演算子。

動作確認。

void main() {
  int a = 6;
  int b = 3;
  print(a ^ b);
}

実行結果。

5

上記について説明すると、6は8ビットで「0000 0110」、3は8ビットで「0000 0011」となる。
6と3の排他的論理和は、

0000 0110
0000 0011
---------
0000 0101

「0000 0101」となり、10進数で5になる。

relational and type test演算子

relational and type test(関係演算子と型テスト演算子)。関係演算子は2つの値を比較する演算子で、型テスト演算子は別の型への変換やオブジェクトの型を判定するための演算子。

>=

大なりイコール演算子。左辺の値が右辺の値以上であるかどうかを比較。

void main() {
  int a = 6;
  int b = 3;
  bool c = a >= b;
  print(c); // 左辺の値が右辺の値以上のためtrueを返す
}
void main() {
  int a = 3;
  int b = 3;
  bool c = a >= b;
  print(c); // 等しい場合もtrueを返す
}

>

大なり演算子。左辺の値が右辺の値より大きいかどうかを比較。

void main() {
  int a = 6;
  int b = 3;
  bool c = a > b;
  print(c); // 左辺の値が右辺の値より大きいためtrueを返す
}
void main() {
  int a = 3;
  int b = 3;
  bool c = a > b;
  print(c); // 等しい場合はfalseを返す
}

<=

小なりイコール演算子。左辺の値が右辺の値以下であるかどうかを比較。

void main() {
  int a = 3;
  int b = 6;
  bool c = a <= b;
  print(c); // 左辺の値が右辺の値以下のためtrueを返す
}
void main() {
  int a = 3;
  int b = 3;
  bool c = a <= b;
  print(c); // 等しい場合もtrueを返す
}

<

小なり演算子。左辺の値が右辺の値より小さいかどうかを比較。

void main() {
  int a = 3;
  int b = 6;
  bool c = a < b;
  print(c); // 左辺の値が右辺の値より小さいためtrueを返す
}
void main() {
  int a = 3;
  int b = 3;
  bool c = a < b;
  print(c); // 等しい場合はfalseになる
}

as

型キャスト演算子。オブジェクトを指定した型にキャスト(変換)する。

void main() {
  dynamic text = 'Hello';
  String str = text as String; // String型にキャスト
  print(str);
}

当然だが、文字列はint型にキャストできない。
以下例のようにキャストできないケースでは、

void main() {
  dynamic text = 'Hello';
  int i = text as int;
  print(i);
}

例外が発生する。

_TypeError (type 'String' is not a subtype of type 'int' in type cast)

is

型チェック演算子。オブジェクトが指定した型であるかチェックする。

void main() {
  dynamic text = 'Hello';
  bool str = text is String;
  print(str); // text変数には文字列(String型)が代入されているためtrueを返す
}

is!

否定型チェック演算子。オブジェクトが指定した型ではない事をチェックする。

void main() {
  dynamic text = 'Hello';
  bool str = text is! int;
  print(str); // text変数の値はint型ではないのでtrueを返す
}

equality演算子

==

等価演算子。左辺と右辺の値が等しい場合にtrueを返す。

void main() {
  int a = 5;
  int b = 5;
  bool result = a == b;
  print(result); // trueを返す
}

!=

不等価演算子。左辺と右辺の値が等しくない場合にtrueを返す。

void main() {
  int a = 5;
  int b = 3;
  bool result = a != b;
  print(result); // trueを返す
}

logical AND演算子

&&

論理AND演算子。2つのbool式を評価し、両方がtrueの場合はtrueを返す。それ以外はfalseを返す。

void main() {
  bool a = true;
  bool b = true;
  bool result = a && b;
  print(result); //trueを返す
}
void main() {
  bool a = true;
  bool b = false;
  bool result = a && b;
  print(result); //falseを返す
}

logical OR演算子

||

論理OR演算子。2つのbool式を評価し、どちらか一方、もしくは両方がtrueの場合にtrueを返す。

void main() {
  bool a = true;
  bool b = false;
  bool result = a || b;
  print(result); //trueを返す
}
void main() {
  bool a = false;
  bool b = false;
  bool result = a || b;
  print(result); //falseを返す
}

if-null演算子

??

null合体演算子。左辺の値がnullでない場合はその値を返し、nullである場合は右辺の値を返す。

void main() {
  String? text = null;
  String result = text ?? 'Hello';
  print(result); // text変数はnullのためHelloを返す
}

conditional(三項演算子)

condition ? expr1 : expr2

三項演算子。conditionがtrueの場合expr1を返し、falseの場合はexpr2を返す。

void main() {
  String? a = 'Hello';
  String? b = 'World';
  String c = (a == b) ? a : b;
  print(c); 
}

実行結果。(a == b)がfalseのためbの値が返される。

World

cascade(カスケード演算子)

..

カスケード演算子。カスケード演算子は同じオブジェクトに対して複数のメソッドやプロパティを連続して呼び出す。

文字列を連結するStringBuffer()クラスを使用して動作確認。
write()メソッドで連結する文字列を増やしている。

void main() {
  var buffer = StringBuffer()
    ..write('Hello')
    ..write(' ')
    ..write('World')
    ..write(1);
  print(buffer.toString()); // 'Hello World1'が出力される
}

?..

null安全カスケード演算子。null安全カスケード演算子を使うと、nullでない場合にのみカスケード操作を行う。

void main() {
  StringBuffer? buffer;
  buffer
    ?..write('Hello') // buffer変数がnullのため、以降のカスケード操作が行われない
    ..write(' ')
    ..write('World');
  print(buffer.toString()); // nullを返す
}

assignment(代入演算子)

代入演算子は複数種類がある。基本的な用法は同じため、よく使うものをピックアップして記載する。

=

代入演算子。
左辺に右辺の値を代入。

void main() {
  int a = 5; // aに5を代入
  print(a); 
}

*=

乗算代入演算子。
左辺の変数に右辺の値を掛け、その結果を左辺の変数に代入。

void main() {
  int a = 5;
  a *= 2; // aは10になる
  print(a); 
}

/=

除算代入演算子。
左辺の変数を右辺の値で割り、その結果を左辺の変数に代入。

void main() {
  double a = 10;
  a /= 2; // aは5.0になる
  print(a);
}

+=

加算代入演算子。
左辺の変数に右辺の値を加え、その結果を左辺の変数に代入。

void main() {
  int a = 10;
  a += 2; // aは12になる
  print(a);
}

-=

減算代入演算子。
左辺の変数から右辺の値を引き、その結果を左辺の変数に代入。

void main() {
  int a = 10;
  a -= 2; // aは8になる
  print(a);
}

spread(スプレッド構文)

スプレッド構文。
リストやマップの要素を他のリストやマップに展開するために使用。

void main() {
  List<int> list1 = [1, 2, 3];
  List<int> list2 = [4, 5, 6];
  List<int> combinedList = [...list1, ...list2];
  print(combinedList); // [1, 2, 3, 4, 5, 6]を返す
}

…?

null安全スプレッド構文。
リストやマップがnullである可能性の時に使用。

void main() {
  List<int>? list1 = null;
  List<int> list2 = [4, 5, 6];
  List<int> combinedList = [...?list1, ...list2];
  print(combinedList); // [4, 5, 6]を返す
}

コメント

タイトルとURLをコピーしました