Swift 6.0 beta 日本語化計画 : Swift 6.0 beta
高度な演算子
基本演算子 で説明した演算子に加えて、Swift は、より複雑な値操作を実行する、いくつかの高度な演算子を提供しています。これらは C や Objective-C で精通しているであろう、ビット単位演算子とビットシフト演算子すべてを含んでいます。
C 言語 での算術演算子とは異なり、Swift における算術演算子は、デフォルトではオーバーフローしません。オーバーフロー動作はトラップされ、エラーとして報告されます。オーバーフローする動作を選択するには、Swift の算術演算子の第2セットを使用しますが、それらはデフォルトでオーバーフローする、オーバーフロー加算演算子(&+) などを使用します。これらのオーバーフロー演算子はすべてアンパサンド(&)で始まります。
独自の構造体、クラス、および列挙型を定義する際には、これらのカスタム型の標準 Swift 演算子の独自の実装を提供するのが役立ちます。Swift は、簡単にこれらの演算子のおあつらえの実装を提供し、それらの動作は、作成する各々の型ごとにどうするべきかを正確に決定できます。
これは事前に定義された演算子に限定されることではありません。Swift はカスタムの優先順位と結合値で、独自のカスタムの挿入辞、接頭辞、接尾辞、および代入演算子を定義する自由を与えます。これらの演算子を使用し、事前に定義された演算子のように、コード内で使用し採用し、そして定義したカスタム演算子をサポートするために、既存の型を拡張することさえもできます。
ビット単位演算子 では、データ構造体の中の個々の生データのビットを操作できます。それらはしばしば、グラフィック・プログラミングおよびデバイスドライバの作成のように、低レベルのプログラミングに使用されます。カスタムプロトコルによる通信用のデータのコード化と復号化などのような、外部ソースからの生データで作業する場合にもビット単位演算子は役立ちます。
下記のように Swift は、C 言語で見られるビット単位の演算子のすべてをサポートしています。
ビット単位の NOT 演算子 (~)は、数のすべてのビットを反転します:
ビット単位の NOT 演算子は、接頭辞演算子であり、それが操作する値の直前に、全く空白をおかずに書きます。
UInt8 整数は 8 ビットであり、0 と 255 の間の全ての値を格納できます。この例では、その最初の4ビットが 0 に設定されており、次の4ビットが 1 に設定された2進数値の 00001111 で UInt8 整数を初期化しています。これは、十進数の 15 に等しいです。
ビット単位の NOT 演算子は、その後、全てのビットが反転した initialBits に等しい、invertedBits と言う新しい定数を作成するために使用されます。ゼロは1になり、1はゼロになります。invertedBits の値は 11110000 となり、符号なし十進数の 240 に等しくなります。
ビット単位の AND 演算子 (&) は、2つの数のビットを組み合わせます。それは、そのビットの 両方の 入力の数が 1 に等しい場合にのみ、そのビットが 1 に設定されている新しい数字を返します。
以下の例では、firstSixBits と lastSixBits の値の両方が 1 に等しい4つの中央のビットを持っています。ビット単位の AND 演算子は、それらを組み合わせて符号なし十進数値の 60 に等しい数 00111100 を作ります。
ビット単位の OR 演算子 (|) は2つの数のビットを比較します。演算子は、入力した数の いずれか が 1 に等しい場合、そのビットを 1 に設定して、新しい数字を返します。
以下の例では、someBits と moreBits の値が異なるビットは 1 に設定されます。ビット単位の OR 演算子はそれらを組み合わせて 254 の符号なし十進数に等しい数 11111110 を作ります。
ビット単位の XOR 演算子、または "排他的論理和演算子" (^) は、2つの数のビットを比較します。演算子は、その入力ビットが異なっていれば 1 に設定され、入力ビットが同じであれば 0 に設定して新しい数字を返します。
以下の例では、firstBits と otherBits の値はそれぞれ、相手にはない場所にはビットを 1 に設定されています。ビット単位の XOR 演算子は、その出力値でこれらのビットの両方を 1 に設定します。firstBits と otherBits が一致する他のすべてのビットは出力値で 0 に設定されます。
ビット単位の左シフト演算子 (<<) と ビット単位の右シフト演算子 (>>) は、以下に定義された規則に従って、左か右に特定の数だけ数字のすべてのビットを移動します。
ビット単位の左シフトと右シフトは、2の要素で整数を乗算または除算する効果があります。整数のビットを一つの位置左にシフトすることは、その値を2倍にし、一方、一つの位置右にシフトすると、その値を半分にします。
符号なし整数のビットシフト動作は以下のとおりです。
このアプローチは、論理シフト として知られています。
下の図は、(11111111 は 1 箇所だけ左にシフトする) 11111111 << 1 と、(11111111 は 1 箇所だけ右にシフトする) 11111111 >> 1 の結果を示しています。緑の数字はシフトされ、灰色の数字は破棄され、ピンク色のゼロが挿入されています。
ここでビットのシフトが Swift のコード内でどのように見えるかを示します:
他のデータ型内の値をコード化し、解読するために、ビットシフトを使用できます。
この例では、ピンク色用のカスケーディングスタイルシートの色値を格納するために pink と言う UInt32 型の定数を使用しています。CSS カラー値 #CC6699 は、Swift の16進数表現で 0xCC6699 と書かれています。この色は、ビット単位の AND 演算子 (&) とビット単位の右シフト演算子 (>>)によって、その赤 (CC)、緑 (66)、青 (99) の成分に分解されます。
赤色の成分は、数値 0xCC6699 と 0xFF0000 間でビット単位の AND を実行することによって得られます。0xFF0000 内のゼロは効果的に 0xCC6699 の2番目と3番目のバイトを "マスク" し、6699 は無視され、結果として 0xCC0000 を残します。
この数字は、その後右へ16箇所シフトされます (>> 16)。16進数で文字の各ペアは 8 ビットを使うので、右に16箇所移動すると 0xCC0000 は 0x0000CC に変換されます。これは 10 進値の 204 である 0xCC と同じです。
同様に、緑色の成分は数値 0xCC6699 と 0x00FF00 間でビット単位の AND を実行することにより得られ、0x006600 の出力値を得られます。この出力値は、さらに右に8箇所シフトされ、10 進値の 102 である 0x66 の値を得ます。
最後に、青色の成分は、数値 0xCC6699 と 0x0000FF 間でビット単位の AND を行うことにより、0x000099 の出力値を得られます。0x000099 はすでに 10 進値の 153 である 0x99 に等しいので、この値を右にシフトする必要はありません。
シフト動作は、符号付き整数が二進法で表現されているため、符号なし整数の場合よりも符号付き整数の方が、より複雑です。(簡単にするために、8 ビット符号付き整数に基づいて以下に例を挙げますが、同じ原理は、どんな大きさの符号付き整数にも当てはまります。)
符号付き整数は、整数が正か負かを示すために (符号ビット として知られる) その最初のビットを使用します。0 の符号ビットは正を意味し、1 の符号ビットは負を意味します。
(値ビット として知られる) 残りのビットは、実際の値を格納します。正の数は、0 から数え上げて符号なし整数の場合とまったく同じ方法で格納されます。ここでは Int8 内部のビットが数 4 の場合どう見えるか示します。
符号ビットは 0 ("正" を意味します) で、7つの値ビットが2進数表記で書かれた 4 の数です。
負の数は、しかし、異なる方法で格納されます。それらは、n が値ビット数である 2 の n 乗の絶対値から減算して格納されます。8ビットの数には7つの値ビットがあるので、これは、2 の 7 乗、または 128 を意味します。
ここで Int8 内部のビットが数 -4 の時どう見えるか示します。
このとき、符号ビットは 1 ("負" の意味) で、7つの値ビットには、124 (128 - 4 です) の二進値があります。
この負の数の符号化は、2の補数 表現として知られています。これは、負の数を表すために、普通でない方法に見えるかもしれませんが、いくつかの利点があります。
まず、単に (符号ビットを含む) 全8ビットの標準バイナリ加算を行うと、-1 を -4 に加算することができ、終わったら8ビットに収まらないものは全て破棄します:
次に、2の補数表現はまた、正の数のように負の数のビットを左右にシフトし、左シフトして2倍の数を得、またはそれらを右シフトして半分の数を得ます。これを達成するためには、符号付き整数を右にシフトするときの、追加のルールが使用されます。右に符号付き整数をシフトする場合は、符号なし整数の場合と同じルールを適用しますが、ゼロではなく、符号ビット で左側の空のビットを埋めます。
このアクションは、それらが右にシフトされた後、符号付き整数が、同じ符号であることを保証するので、算術シフト として知られています。
正と負の数が格納されている特別な方法のため、それらのいずれを右にシフトしてもゼロに近づきます。このシフトの間、符号ビットを同じに維持することは、それらの値がゼロに近づく間負の整数が負のままであることを意味します。
整数の定数や変数に数字を挿入しようとして値を保持できない場合、デフォルトでは Swift は、無効な値が作成されるのを許可するのではなく、エラーを報告します。この動作は、大きすぎるか小さすぎる数字で作業するのに特別の安全性を与えます。
たとえば、Int16 の整数型は -32768 から 32767 の間の全ての符号付き整数を保持できます。この範囲外の数に Int16 の定数または変数を設定しようとすると、エラーが発生します。
境界値条件をコード化するとき値が大きすぎるか小さすぎる時のエラー処理を提供することは、より多くの柔軟性を与えます。
しかし、利用可能なビット数を切り捨てるオーバーフロー条件を特に望む場合、エラーを引き起こすよりも、この動作を選択できます。Swift は、3つの算術 オーバーフロー演算子 を提供し、整数計算のオーバーフロー動作を選択できます。これらの演算子はすべて、アンパサンド (&) で始まります。
数値は、正と負両方の方向にオーバーフローできます。
ここで符号なしの整数値をオーバーフロー加算演算子 (&+) を使用して正の方向にオーバーフローさせるのが許されると、何が起こるか例を示します。
変数 unsignedOverflow は UInt8 が保持できる最大値 (255、または2進数で 11111111) で初期化されます。次いで、それはオーバーフロー加算演算子 (&+) を用いて、1 だけ増分されます。これは UInt8 が保持できる大きさをちょうど超えるその 2 進数表現をプッシュし、その境界を越えてオーバーフローを引き起こし、以下の図のようになります。オーバーフロー加算後 UInt8 の範囲内にとどまる値は 00000000、またはゼロです。
似たような事が、符号なし整数を負の方向にオーバーフローさせると発生します。ここに、オーバーフロー減算演算子(&-)を使用した例を示します:
UInt8 が保持できる最小値はゼロ、または2進数で 00000000 です。オーバーフロー減算演算子(&-)を使用して 00000000 から 1 を減算した場合、数がオーバーフローし、11111111、または 十進数で 255 になります。
同じようなオーバーフローは、符号付き整数でも発生します。符号付き整数のすべての加算と減算は、ビット単位の左シフトと右シフト演算子 で説明したように、符号ビットが加算または減算される数字の部分として含まれ、ビット単位の方法で行われます。
Int8 が保持できる最小値は -128、または2進数で 10000000 です。オーバーフロー演算子でこの2進数から 1 を減算すると、符号ビットを入れ替え、正の 127 となり、Int8 が保持できる最大の正の値 01111111 の2進数値を与えます。
符号付きと符号なし両方の整数の場合、最大の有効な整数値から正の方向へのオーバフローは最小へ戻り、最小値から負の方向のへオーバーフローは最大値になります。
演算子の 優先順位 は、一部の演算子に他よりも高い優先度を与えます。これらの演算子が最初に適用されます。
演算子の 結合性 は、同じ優先順位の演算子が一緒にグループ化され、左からグループ化されるか、または右からグループ化されるかを決定します。"それらはその左の式に結合される"、または "それらはその右の式に結合される"、の意味であると考えて下さい。
複合式が計算される順序を操作するとき、各演算子の優先順位と結合性を考慮することは重要です。例えば、なぜ以下の式は 17 に等しいか、演算子の優先順位が説明します。
左から右に厳密に考えると、以下のようにこの式を計算できます。
しかし、実際の答えは 5 ではなく 17 です。より高い優先順位の演算子は、優先順位の低いものより前に評価されます。Swift では、C のように、剰余演算子 (%) 及び乗算演算子 (*)は、加算演算子 (+) よりも高い優先順位です。結果として、加算が考慮される前に、それらは両方とも評価されます。
しかし、剰余と乗算は互いに 同じ 優先順位です。使用するために正確な評価順序で操作するには、それらの結合性も考慮する必要があります。剰余と乗算はその左の式と、両方とも結合されます。それらの左から順に、式のこれらの部分の周りに暗黙の括弧を追加するとこう考えられます。
(3 % 4) は 3 なので、これは以下と等価です:
(3 * 5) は 15 なので、これは以下と等価です:
この計算の最終的な答えは 17 が得られます。
演算子優先順位グループと結合性設定の完全なリストを含む、Swift 標準ライブラリが提供する演算子についての詳細は、演算子の宣言 を参照してください。
クラスと構造体は、既存の演算子の独自の実装を提供できます。これは、既存の演算子を オーバーロードする 事として知られています。
以下の例は、カスタム構造体のための算術加算演算子 (+) を実装する方法を示しています。算術加算演算子は二つのターゲット上で演算するので 二項演算子 であり、それら二つのターゲットの間に表示されるので、挿入辞(infix) 演算子であると言われます。
以下の例では、二次元の位置ベクトル (x,y) のための Vector2D 構造体の定義に続いて、Vector2D 構造体のインスタンスを一緒に加算する 演算子メソッド の定義が続きます。
演算子メソッドは、オーバーロードする演算子 (+) に一致するメソッド名を持つ Vector2D 上の型メソッドとして定義されています。加算は、ベクトルのために不可欠な動作の一部ではないので、型メソッドは、 Vector2D の主構造体の宣言よりもむしろ Vector2D の拡張機能内で定義されています。算術加算演算子は二項演算子であるため、この演算子メソッドは、型 Vector2D の2つの入力パラメータを取り、また型 Vector2D である、単一の出力値を返します。
この実装では、入力パラメータは left と right と名付けられ、+ 演算子の左側と右側になる Vector2D インスタンスを表現します。メソッドは、x と y プロパティが、一緒に加算された2つの Vector2D インスタンスからの x と y プロパティの和で初期化された、新しい Vector2D インスタンスを返します。
型メソッドは、既存の Vector2D インスタンス間の挿入辞(infix) 演算子として使用できます。
この例は、以下の図に示すように、ベクトル (3.0, 1.0) と (2.0, 4.0) を一緒に加算するとベクトル (5.0,5.0) になる事を示しています。
上記の例では、二項の挿入辞演算子のカスタムの実装を示しました。クラスと構造体もまた、標準 単項演算子 の実装を提供できます。単項演算子は、一つのターゲットに作用します。それらは (-a のように) それらのターゲットの前ならば、接頭辞 であり、それらが (b! のように) それらのターゲットの後に続く場合、それらは 接尾辞 演算子です。
演算子メソッドを宣言するとき、func キーワードの前に prefix または postfix 修飾子を書くことで単項演算子の接頭辞や接尾辞を実装できます:
上記の例は、Vector2D インスタンスの単項マイナス演算子 (-a) を実装しています。単項マイナス演算子は接頭辞演算子なので、このメソッドは prefix 修飾子で修飾する必要があります。
簡単な数値の場合、単項マイナス演算子は、正の値を同等の負の値に変え、またその逆に変換します。Vector2D インスタンスの対応する実装は、x と y プロパティの両方でこの操作を実行します。
複合代入演算子 は、別の操作を代入 (=) と組み合わせます。例えば、加算代入演算子 (+=) は、加算と代入を1つの操作に組み合わせています。パラメータの値は、演算子メソッドの中から直接変更されますので、inout として複合代入演算子の左入力パラメータ型をマークして下さい。
以下の例は、Vector2D インスタンスの加算代入演算子メソッドを実装しています。
加算演算子が先に定義されているので、ここで加算処理を再実装する必要はありません。代わりに、加算代入演算子メソッドは、既存の加算演算子メソッドを利用し、左の値を左の値プラス右の値となるように、設定するために使用します。
デフォルトでは、カスタムのクラスと構造体は、等価 演算子 (==) と 不等価 演算子 (!=) として知られる 等価演算子 の実装を受けません。普通は == 演算子の実装をして、== 演算子の結果を否定する != 演算子の Swift 標準ライブラリのデフォルトの実装を使用します。== 演算子を実装するには、2 つの方法があります。あなた自身でそれを実装する事も可能ですが、多くの型の場合、Swift に実装を合成してもらう事もできます。両方の場合で、Swift 標準ライブラリの Equatable プロトコルへの準拠を追加して下さい。
他の挿入辞演算子を実装するのと同じ方法で、==演算子の実装を提供して下さい、
上記の例では、2つの Vector2D インスタンスが同等の値を持っているかどうかをチェックするために、== 演算子を実装しています。Vector2D の文脈では、"両方のインスタンスが同じ x 値と y 値を持つ" という意味で、"等しい" と考慮することに意味があり、これは、演算子の実装によって使用される論理です。
これで、2つの Vector2D インスタンスが等価であるかどうかをチェックするために、この演算子を使えるようになりました。
多くの単純な場合、合成された実装を使用したプロトコルの採用 で説明したように、Swift に等価演算子の合成した実装を提供するよう依頼できます。
Swift で提供される標準の演算子に加えて、独自の カスタム演算子 を宣言し、実装できます。カスタム演算子を定義するために使用できる文字の一覧については、演算子 を参照してください。
新しい演算子は、operator のキーワードを使用して、グローバルレベルで宣言され、prefix、infix または postfix 修飾子でマークされます:
上記の例では、+++ と言う新しい接頭辞演算子を定義しています。この演算子は、Swift では既存の意味がないので、それは Vector2D インスタンスとの作業の具体的な文脈で、独自のカスタムの意味を以下で与えられています。この例の目的は、+++ が新しい "接頭辞倍増" 演算子として扱われる事です。それは Vector2D インスタンスの x と y 値を倍にし、以前に定義された、加算代入演算子でそれ自体にベクトルを加算します。+++ 演算子を実装するには、Vector2D に +++ という型メソッドを以下のように追加します:
カスタム infix (挿入辞) 演算子もそれぞれ優先順位グループに属しています。優先順位グループは、他の挿入辞演算子に対する演算子の優先順位だけでなく、演算子の結合性も指定します。これらの特性が、他の挿入辞演算子と挿入辞演算子の相互作用にどのように影響するかの詳細については、優先順位と結合性 を参照してください。
明示的に優先順位のグループに配置されていないカスタムの挿入辞演算子は、三項条件演算子の優先順位よりもちょっとだけ高い優先順位で、デフォルトの優先順位のグループを与えられています。
以下の例では、+- と言う新しいカスタムの挿入辞演算子を定義しており、それは優先グループ AdditionPrecedence に属しています:
この演算子は、2つのベクトルの x の値を一緒に加算し、第1のものから第2のベクトルの y 値を減算します。それは本質的に "加算的" 演算子のため、+ や - などの加算的挿入辞演算子と同じ優先順位グループが与えられます。演算子優先順位グループと結合性の設定の完全なリストを含む、Swift 標準ライブラリが提供する演算子については、演算子の宣言 を参照してください。優先順位グループの詳細および独自の演算子および優先順位グループを定義する構文については、演算子の宣言 を参照してください。
Result Builder (結果の確立) は、リストやツリーなどの入れ子にされたデータを、自然で宣言的な方法で作成するための構文を追加する、あなたが定義する型です。結果の確立を使用するコードには、if や for などの通常の Swift 構文を含めて、条件付きまたは繰り返しのデータを処理できます。
以下のコードは、星とテキストを使用して単一の線を描画するためのいくつかの型を定義しています。
Drawable プロトコルは、線や形状など、描画できるものの要件を定義しています。型は draw( ) メソッドを実装しなければなりません。Line 構造体は単一の線の描画を表し、ほとんどの描画の最上位コンテナとして機能します。Line を描画するために、構造体は線の各コンポーネントで draw( ) を呼び出し、結果の文字列を 1 つの文字列に連結します。Text 構造体は、文字列を包み込んで描画の一部にします。AllCaps 構造体は、別の描画を包み込んで変更し、描画内の全てのテキストを大文字に変換します。
それらのイニシャライザを呼び出すことで、これらの型で描画を行うことができます。
このコードは機能しますが、少し扱いにくいです。AllCaps の後の深く入れ子になった括弧は読みにくいです。name が nil のときに "World" を使用するフォールバックロジックは、?? 演算子を使用してインラインで実行する必要があります。これは、より複雑なものでは困難です。図面の一部を構築するためにスイッチや for ループを含める必要がある場合、それを行う方法はありません。結果の確立を使用すると、通常の Swift コードのように見えるように、このようなコードを書き直すことができます。
結果の確立を定義するには、型宣言に @resultBuilder 属性を記述して下さい。たとえば、以下のコードは DrawingBuilder という結果の確立を定義します。これにより、宣言構文を使用して図面を記述できます。
DrawingBuilder 構造体は、結果の確立構文の一部を実装する 3 つのメソッドを定義しています。buildBlock(_:) メソッドは、一連の行をコードブロック内に書き込むためのサポートを追加します。そのブロック内のコンポーネントを Line に結合します。buildEither(first:) および buildEither(second:) メソッドは、if-else のサポートを追加します。
@DrawingBuilder 属性を関数のパラメータに適用すると、関数に渡されたクロージャーが、結果の確立がそのクロージャから作成する値に変換されます。例えば:
makeGreeting(for:) 関数は name パラメータを受け取り、それを使用してパーソナライズされた挨拶を描画します。draw(_:) 関数と caps(_:) 関数は両方とも、@DrawingBuilder 属性でマークされた単一のクロージャを引数として取ります。これらの関数を呼び出すときは、DrawingBuilder が定義する特別な構文を使用します。Swift は、図面の宣言的な記述を DrawingBuilder のメソッドへの一連の呼び出しに変換し、関数の引数として渡される値を作成します。たとえば、Swift はこの例の caps(_:) への呼び出しを以下のようなコードに変換します。
Swift は、if-else ブロックを buildEither(first:) および buildEither(second:) メソッドへの呼び出しに変換します。これらのメソッドを独自のコード内で呼び出すことはありませんが、変換の結果を表示すると、DrawingBuilder 構文を使用するときに Swift がコードをどのように変換するかを簡単に確認できます。
特殊な描画構文で for ループを記述するためのサポートを追加するには、buildArray(_:) メソッドを追加します。
上記のコードでは、for ループが描画の配列を作成し、buildArray(_:) メソッドがその配列を Line に変換します。
Swift がビルダーの構文をビルダー型のメソッドへの呼び出しに変換する方法の完全なリストについては、resultBuilder を参照してください。