The Go Programming Language Specificationの翻訳、9回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。
式
式は、値の算出方法を規定します。値の算出はオペランドに演算子および関数を適用することで行われます。
オペランド
オペランドは、式の基本要素である値です。
Operand = Literal | QualifiedIdent | MethodExpr | "(" Expression ")" . Literal = BasicLit | CompositeLit | FunctionLit . BasicLit = int_lit | float_lit | char_lit | StringLit .
限定付き識別子
限定付き識別子とは、パッケージ名をプレフィックスとして指定した識別子で、これにはブランク識別子は使用できません。
QualifiedIdent = [ PackageName "." ] identifier .
限定付き識別子は、別パッケージの識別子にアクセスするときに使用します。その識別子はエクスポートされていなければなりません。すなわち識別子がユニコードの大文字で始まっている必要があります。
math.Sin
複合リテラル
複合リテラルは構造体、配列、スライス、マップを構築し、評価をその都度行って新しい値を作成します。複合リテラルは、値の型と、それに続く波括弧{}でくくられた要素リストから構成されます。この要素は単一式、もしくはキーと値のペアのどちらかです。
CompositeLit = LiteralType "{" [ ElementList ] "}" . LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | SliceType | MapType | TypeName | "(" LiteralType ")" . ElementList = Element { "," Element } [ "," ] . Element = [ Key ":" ] Value . Key = FieldName | ElementIndex . FieldName = identifier . ElementIndex = Expression . Value = Expression .
このLiteralTypeは、構造体、配列、スライス、マップ型のいずれかでなければなりません(文法上、型がTypeNameと記述されたとき以外はこの制約が適用されます)。式の型は、LiteralTypeの各フィールド、または要素、またはキーの型との間で代入の適合性を持たなければなりません。このとき変換はできません。
キーは、構造体リテラルのフィールド名、配列またはスライスリテラルのインデックス式、マップリテラルのキーのいずれかとして解釈されます。マップリテラルのときは、全ての要素に対しキーを記述しなくてはなりません。また複数の要素に同じフィールド名やキー値を指定したときはエラーとなります。
構造体リテラルには次の規則が適用されます。
- キーはLiteralTypeで宣言されているフィールド名でなければなりません。
- リテラルにキーが含まれないときは、各フィールドの要素をフィールドが宣言されている順にリストしなければなりません。
- キーを持つ要素がひとつでもあるなら、全ての要素にキーを持たせなければなりません。
- リテラルにキーが含まれているときは、構造体の全フィールドに要素を持たせる必要はありません。省略されたフィールドはゼロ値となります。
- リテラルの要素リストは省略可能です。このようなリテラルはその型のゼロ値となります。
- 他のパッケージに属している構造体の非エクスポートフィールドに要素を設定しようとするとエラーとなります。
構造体を定義します。
type Point struct { x, y, z float } type Line struct { p, q Point }
次のように記述します。
origin := Point{}; // Pointはゼロ値 line := Line{origin, Point{y: -4, z: 12.3}}; // line.q.xはゼロ値
配列リテラル、スライスリテラルには次の規則が適用されます。
- 各要素は、配列内の位置を示す整数インデックスを持ちます。
- キーを伴った要素は、そのキーをインデックスとして使用します。キーは整数の定数式でなければなりません。
- キーを伴わない要素は、前の要素のインデックスを+1した値をインデックスとして用います。先頭の要素がキーを伴わないときは、そのインデックスはゼロです。
複合リテラルのアドレスを取得(§アドレス演算子)すると、リテラル値のインスタンスを指すユニークなポインタが生成されます。
var pointer *Point = &Point{y: 1000};
配列リテラルの長さは、LiteralType内で指定した長さです。要素数がリテラルで指定した長さに足りないとき、不足した要素にはその要素型のゼロ値がセットされます。配列のインデックスの範囲を超えたインデックス値を要素に指定するとエラーとなります。配列の長さに…と記述すると、要素の最大インデックス値に+1した値を指定したことと同じになります。
buffer := [10]string{}; // len(buffer) == 10 intSet := [6]int{1, 2, 3, 5}; // len(intSet) == 6 days := [...]string{"Sat", "Sun"}; // len(days) == 2
スライスリテラルは、元になっている配列リテラル全体を表します。そのためスライスの長さとキャパシティは、要素の最大インデックス値に+1した値となります。スライスリテラルは次の形式です。
[]T{x1, x2, ... xn}
そして次は、配列リテラルに対してスライス操作を行うショートカットです。
[n]T{x1, x2, ... xn}[0 : n]
“if”、”for”、”switch”ステートメントの条件内に、LiteralTypeとしてTypeName形式を使った複合リテラルが現れると、意味が曖昧になり構文解析に支障をきたします。これはリテラル内の波括弧{}でくくられた式と、これらのステートメントに続くステートメントブロックとの見分けがつかないためです。この稀なケースにて発生する曖昧さを解決するには、複合リテラルを丸括弧()内に記述しなければなりません。
if x == (T{a,b,c}[i]) { ... } if (x == T{a,b,c}[i]) { ... }
次は正しく配列、スライス、マップリテラルを使った例です。
// 素数リスト primes := []int{2, 3, 5, 7, 9, 11, 13, 17, 19, 991}; // chが母音(vowel)のとき、vowels[ch]の値はtruevowels[ch] vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}; // 配列 [10]float{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}; filter := [10]float{-1, 4: -0.1, -0.1, 9: -1}; // 平均律音階の周波数(Hz) (A4 = 440Hz) noteFrequency := map[string]float{ "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83, "G0": 24.50, "A0": 27.50, "B0": 30.87, }
関数リテラル
関数リテラルは匿名関数を表します。匿名関数は関数の型および関数の本体から構成されます。
FunctionLit = FunctionType Body .
func (a, b int, z float) bool { return a*b < int(z) }
関数リテラルは変数に代入することも、直接実行することも可能です。
f := func(x, y int) int { return x + y } func(ch chan int) { ch <- ACK } (reply_chan)
関数リテラルはクロージャです。そのため関数リテラル内から、外側の関数内で定義した変数を参照可能です。これらの変数は外側の関数と、関数リテラル間で共有され、これらからアクセス可能な限り存続します。
基本式
基本式は、単項式、二項式に与えられるオペランドです。
PrimaryExpr = Operand | Conversion | BuiltinCall | PrimaryExpr Selector | PrimaryExpr Index | PrimaryExpr Slice | PrimaryExpr TypeAssertion | PrimaryExpr Call . Selector = "." identifier . Index = "[" Expression "]" . Slice = "[" Expression ":" Expression "]" . TypeAssertion = "." "(" Type ")" . Call = "(" [ ExpressionList ] ")" .
x 2 (s + ".txt") f(3.1415, true) Point{1, 2} m["foo"] s[i : j + 1] obj.color Math.sin f.p[i].x()
セレクタ
次の形式の基本式があります。
x.f
これは、x
(またはx
がポインタ型であれば*x
)で表される値が持つ、フィールドまたはメソッドf
を表します。識別子f
は(フィールドまたはメソッドの)セレクタと呼ばれます。セレクタはブランク識別子であってはなりません。この式の型はf
の型です。
セレクタf
は、型T
のフィールド/メソッドf
を表すか、もしくはT
内でネストしている匿名フィールドのフィールド/メソッドf
を表します。f
に到達するまで渡り歩いた匿名フィールドの数は、T
におけるf
の深さと呼ばれます。T
に直接宣言されていればフィールド/メソッドf
の深さはゼロです。T
内の匿名フィールドA
で宣言されていればフィールド/メソッドf
の深さは、A
の深さ+1となります。
セレクタには次の規則が適用されます。
- 仮に、型
T
もしくは*T
である値x
があり、T
がインタフェース型でなく、該当するフィールドまたはメソッドf
が存在するならば、x.f
が表すのは、T
内で深さの値が最も小さいフィールドまたはメソッドf
です。最も浅い深さのf
がひとつだけでなければ、このセレクタは不正となります。 - 仮に、型
I
もしくは*I
である値x
があり、I
がインタフェース型で、かつ該当するメソッドが存在するならば、x.f
が表すのは、x
に割り当てられている値が持っているf
という名前の実メソッドです。x
に値がないかnil
のとき、x.f
は不正となります。 - これら以外のケースでは、
x.f
は不正となります。
セレクタは自動的にポインタの間接参照を行います。x
がポインタ型のとき、x.y
は(*x).y
の簡略形として使用可能です。y
も同じくポインタ型のとき、x.y.z
も同様に(*(*x).y).z
の簡略形です。
ただし、*x
がポインタ型のときは、明示的に間接参照を行わなければなりません。これは自動間接参照が行われるのは1レベルだけだからです。例えば、T
型の値x
が*A
として宣言された匿名フィールドを含んでいるとき、x.f
は(*x.A).f
の簡略形です。
例文のために、宣言を行います。
type T0 struct { x int; } func (recv *T0) M0() type T1 struct { y int; } func (recv T1) M1() type T2 struct { z int; T1; *T0; } func (recv *T2) M2() var p *T2; // with p != nil and p.T1 != nil
次のように記述します。
p.z // (*p).z p.y // ((*p).T1).y p.x // (*(*p).T0).x p.M2 // (*p).M2 p.M1 // ((*p).T1).M1 p.M0 // ((*p).T0).M0
インデックス
次の形式の基本式があります。
a[x]
これは、配列、スライス、文字列、マップa
内の、x
でインデックス指定された要素を表します。このx
の値は、インデックスまたはマップのキーと呼ばれます。これには次の規則が適用されます。
仮にa
が、配列型の型A
または*A
、もしくはスライス型の型S
の値であるとします。
x
は整数値で、0 <= x < len(a)
でなければならないa[x]
はインデックスx
の位置にある配列要素であり、a[x]
の型はA
の要素型である
仮にa
が、文字列型である型T
の値であるとします。
x
は整数値で、0 <= x < len(a)
でなければならないa[x]
はインデックスxの位置にあるバイトであり、a[x]
の型はbyte
型であるa[x]
には値を代入できない
仮にa
が、マップ型である型M
の値であるとします。
x
の型は、M
のキーの型と互換性を持つ必要があり、かつこのマップはx
をキーとするエントリを持っていなければならないa[x]
は、マップ内のx
をキーとする値であり、a[x]
の型はM
の値の型である
これ以外のa[x]
は不正となります。また、インデックスまたはキーが範囲外であるときは、たとえインデックス式としては正しくても、ランタイム例外が発生します。
ただし、インデックス式がマップであるときは、次の形式を使ってmap[K] V
型であるマップa
から代入または、変数の初期化を行えます。
r, ok = a[x] r, ok := a[x] var r, ok = a[x]
このインデックス式の結果として、(V, bool)
型の2つの値が返されます。指定したキーがマップ内に存在したときは、この式は(a[x], true)
を返します。存在しない時は、(Z, false)
を返します。このZ
はV
型のゼロ値です。このときは、ランタイム例外は発生しません。上の例のように、このときのインデックス式は、値と成否を返すような関数を呼び出したのと同様に振舞います。 (§代入)
同様に、マップに代入するときは、次の特殊な形式を使うことができます。
a[x] = r, ok
このとき、論理値であるok
の値がfalse
であれば、キーがx
であるエントリはマップから削除されます。ok
の値がtrue
であれば、通常通りマップに要素が代入されます。
スライス
文字列、配列、またはスライス自身をスライスすることで、部分文字列、または部分配列の参照を作ることができます。スライスを行うときは、結果として得たい要素をインデックス式で選択します。得られる結果は0から始まるインデックスを持ち、長さはスライスするときに指定した2つのインデックス値の差と等しくなります。次は配列a
のスライスです。
a := [4]int{1, 2, 3, 4}; s := a[1:3];
このスライスs
の型は[]int
であり、長さは2、キャパシティは3です。要素の値は次となります。
s[0] == 2 s[1] == 3
スライスの長さはマイナス値にはなりません。また配列または文字列のスライス作成時のインデックス[lo:hi
]
が、「 0 <= lo
<= hi
<= 長さ」を満たしていなくてはなりません。スライスからスライスを作成するときは、上限値は長さではなくキャパシティとなります。
スライスする対象が、文字列またはスライスのとき、スライスの結果は文字列もしくは同じ型のスライスになります。しかしスライスの対象が配列のときは、スライスの結果は、その配列の要素型と同じ要素型を持ったスライスになります。
型アサーション
式x
および型T
があると仮定して、次の基本式をみてください。
x.(T)
この式は、x
はゼロ値ではなく、かつx
にはT
型の値が格納されていると断定します。このx.(T)
という表記は、型アサーションと呼ばれます。このときのx
の型はインタフェース型でなければなりません。
より明確にすると、T
がインタフェース型でないときx.(T)
は、x
の動的な型とT
が同一の型であることを表しています。(§型の同一性と互換性)。もし、T
がインタフェース型のときx.(T)
は、x
がインタフェースT
を実装している動的な型であることを表します(§インタフェース型)。
型アサーションが有効であれば、その式が表す値は、x
に格納されている型T
の値となります。ただし型アサーションに失敗したときはランタイム例外が発生します。これらを言い換えると、正しいプログラムにおいては、x
の動的な型が実行時にしか分からなくともx.(T)
がT
になりうることだけは分かっているということです。
型アサーションが代入、または初期化で使われるときは次の形式になります。
v, ok = x.(T) v, ok := x.(T) var v, ok = x.(T)
このアサーションの結果として、(T, bool)
型の2つの値が返されます。アサーションに成功したときは、この式は(x.(T), true)
を返します。失敗したときはこの式は(Z, false)
を返します。このZ
はT
型のゼロ値です。このときは、ランタイム例外は発生しません。上の例のように、このときの型アサーションは、値と成否を返すような関数を呼び出したのと同様に振舞います。 (§代入)
呼び出し
下は、F
型の関数であるf
の式です。
f(a1, a2, ... an)
これは引数、a1, a2, ... an
を伴なうf
の呼び出しです。1つの特例を除いて、各引数は単一値となる式であり、その値はF
のパラメータの型と代入の適合性を持つ必要があります。これらの引数の式は関数の呼び出し前に評価されます。この式の型は、F
の戻り値の型となります。メソッドの実行も同様ではありますが、メソッドは、そのメソッドのレシーバの型の値に対するセレクタとして指定されます。
Atan2(x, y) // 関数の呼び出し var pt *Point; pt.Scale(3.5) // レシーバptによるメソッドの呼び出し
特例として、関数またはメソッドg
の戻り値と、別の関数またはメソッドであるf
の各パラメータの数およびそれらの代入の適合性が一致していれば、f(g(parameters_of_g))
の呼び出しによって、g
の戻り値をf
のパラメータとして順に代入したのち、f
が実行されます。ただし、f
の呼び出しにはg
からの戻り値以外のパラメータを指定することはできません。またf
の最後のパラメータが…のときは、f
の戻り値のうち通常の代入を行った残りがそこに代入されます。
func Split(s string, pos int) (string, string) { return s[0:pos], s[pos:len(s)] } func Join(s, t string) string { return s + t } if Join(Split(value, len(value)/2)) != value { log.Fatal("test fails") }
メソッド呼び出しx.m()
は、x
(の型)のメソッド群がm
を含んでいて、かつ引数リストがm
の引数リストと代入の適合性があるときに有効となります。またx
がアドレス指定可能であり、&x
のメソッド群がm
を含んでいるならば、x.m()
は、(&x).m()
の簡略形として使用可能です。
var p Point; p.Scale(3.5)
これ以外のメソッド型やメソッドリテラルはありません。
…パラメータの解析
関数f
が…パラメータを持つとき、…は常に一番最後の仮パラメータとなります。f
の呼び出しのとき、…より前の引数は通常通り扱われます。それらのパラメータのあとに続いて現れた任意数(ゼロも含む)の引数が…パラメータにバインドされます。
f
関数内では…パラメータは、静的な型interface{}
(空インタフェース)を持ちます。
各呼び出しにおいて、このパラメータの動的な型は、呼び出し時に並べられた引数を連続したフィールドとして持つ構造体となります。つまり、…に与えられた実引数が構造体にラップされて、実引数の代わりとして渡されます。リフレクションインタフェースを使用すると、この動的な型から要素を取り出して、本来の実引数を得ることができます。
関数とその呼び出しです。
func Fprintf(f io.Writer, format string, args ...) Fprintf(os.Stdout, "%s %d", "hello", 23);
このFprintf
呼び出しにおいて、このargs
の動的な型は概念的に、struct { string; int }
となります。
特例として、関数が受け取った…パラメータを、別の関数を呼び出す際に…パラメータとして使用するときは、このパラメータは再ラップされることなくそのまま渡されます。すなわち、…仮パラメータは変更されることなく…実パラメータとして受け渡されます。
演算子
演算子はオペランドを伴って式を作ります。
Expression = UnaryExpr | Expression binary_op UnaryExpr . UnaryExpr = PrimaryExpr | unary_op UnaryExpr . binary_op = log_op | com_op | rel_op | add_op | mul_op . log_op = "||" | "&&" . com_op = "<-" . rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . add_op = "+" | "-" | "|" | "^" . mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
比較演算子については別途説明します。それ以外の二項演算子では、チャネル、シフト、型を持たない定数のいずれかを伴う演算子を除き、オペランドの型は同じでなければなりません (§型と値の特性)。定数のみを伴う演算子については、定数式のセクションを参照ください。
チャネルの送信のときは、最初のオペランドは常にチャネルであり、2番目のオペランドは、チャネルの要素型に対して代入の適合性を持つ値でなくてはなりません。
シフト演算を除き、一方のオペランドが型を持たない定数で、もう一方がそれ以外のときは、定数のオペランドが相手側のオペランドの型に変換されます。
シフト演算の右側のオペランドは符号なし整数型か、もしくは符号なし整数型に変換可能で型を持たない定数でなければなりません。
定数とならないシフト演算の場合で、左側オペランドが型を持たない定数であるときは、その定数の型はシフト演算自体を左側オペランドひとつと置き換えてみたときに得られる型となります。
var s uint = 33; var i = 1<<s; // 1はint型 var j = int32(1<<s); // 1はint32型で、j == 0 var u = uint64(1<<s); // 1はint64型で、u == 1<<33 var f = float(1<<s); // 不正。1はfloat型で、シフト不可 var g = float(1<<33); // 正しい。1<<33はシフト演算の定数で、g == 1<<33
演算子の優先順位
単項演算子は高い優先順位を持っています。++
と--
演算子は、式ではなくステートメントを構成するため演算子のグループからは除外されています。そのためステートメント*p++
は、(*p)++
と同じです。
二項演算子には、6つの優先順位レベルがあります。乗算演算子は最も強く、それに続いて加算演算子、比較演算子、<-
(チャネル送信)、&&
(論理積)、最後が ||
(論理和)です。
優先順位 演算子 6 * / % << >> & &^ 5 + - | ^ 4 == != < <= > >= 3 <- 2 && 1 ||
同じ優先順位を持つ二項演算子は、左から右へと対応づけされます。例で示すと、x / y * z
は、(x / y) * z
と同じになります。
+x 23 + 3*x[i] x <= f() ^a >> b f() || g() x == y+1 && <-chan_ptr > 0
算術演算子
算術演算子は数値に対して使用します。その算出結果の型は一つ目のオペランドの型と同じになります。四則演算子(+
, -
, *
, /
)は、整数及び浮動小数点に対して使用しますが、+
は文字列にも使います。その他の算術演算子は整数にのみ使います。
+ 和 整数、浮動小数点、文字列 - 差 整数、浮動小数点 * 積 整数、浮動小数点 / 商 整数、浮動小数点 % 剰余 整数 & ビット演算 and 整数 | ビット演算 or 整数 ^ ビット演算 xor 整数 &^ ビットクリア(and not) 整数 << 左シフト 整数 << 符号なし整数 >> 右シフト 整数 >> 符号なし整数
文字列は、+
演算子、または+=
代入演算子を使用して連結することができます。
s := "hi" + string(c); s += " and good bye";
文字列の加算は、オペランドを連結することで新たな文字列を作り出します。
整数型のとき、/
と%
は以下の関係を満たします。
(a / b) * b + a % b == a
(a / b)
は、ゼロに近づくように切り捨て/切り上げられます。例を上げますと、
x y x / y x % y 5 3 1 2 -5 3 -1 -2 5 -3 -1 2 -5 -3 1 -2
被除数が正の値で、除数が2の累乗の定数であるときは、その割り算は右シフトに置き換えられ、剰余の計算はビット演算のANDに置き換えられます。
x x / 4 x % 4 x >> 2 x & 3 11 2 3 2 3 -11 -2 -3 -3 1
シフト演算子は、右オペランドで指定されたシフト数、左オペランドをシフトします。実装としては、シフトの左オペランドが、符号あり整数のとき算術シフト、符号なし整数のときは論理シフトが使われます。シフト数は符号なし整数でなければなりません。また、シフト数には上限がありません。シフト数n
でシフトを行うとき、シフト演算は左オペランドをn
回繰り返して、シフト数1でシフトしたように振舞います。シフト演算の結果として、x << 1
はx*2
と同じであり、x >> 1
は x/2
を負の無限大の値に近づくように切り捨てた値と同じになります。
整数オペランドに対する単項演算子+
、-
、^
は以下で示すように定義されています。
+x は、0 + x -x 符号反転 は、0 - x ^x ビットの補数 は、m ^ x (xが符号なしのとき、mの全ビットは1。 xが符号ありのとき、mは-1)
浮動小数点においては、+x
はx
と同じであり、-x
はx
の符号を反転させた値です。
整数のオーバフロー
符号なし整数において、+
、 -
、*
、<<
演算子はmodulo 2n (nは、この符号なし整数型のビット幅)で計算されます(§数値型)。大雑把な解説をすると、これら符号なし整数の演算は、オーバフローした高位のビットを破棄するので、プログラム側はこの「ラップアラウンド」が行われることを期待してもよいでしょう。
符号あり整数において、+
、 -
、*
、<<
演算子はオーバフローを起こすことがあり、演算結果の値として何が返されるかは、この符号あり整数の値、演算子、オペランドにより決まります。オーバフローが起きても例外は発生しません。オーバフローは起こらないという前提のため、コンパイラはこれに関するコードの最適化は行いません。たとえば、x < x + 1
が常に成り立つとは限りません。
比較演算子
比較演算子は、bool
型の値を返します。演算子==
と!=
は、いくつかのケースを除いて配列と構造体以外のすべての型に適用できます。他の比較演算子に適用できるのは数値と文字列だけです。
== 等しい != 等しくない < 小なり <= 小なりイコール > 大なり >= 大なりイコール
数値型のオペランドは、一般的な方法で比較されます。
文字列型のオペランドは、バイト単位で(辞書的に)比較されます。
論理値型のオペランドは、双方がtrue
、または双方がfalse
のときに等しいとみなされます。
複合型の比較の規則は、§比較の適合性にて解説します。
論理演算子
論理演算子は論理値に適用され、オペランドと同じ型で結果を返します。右オペランドが評価されるかどうかは条件によります。
&& and条件 p && q is "if p then q else false" || or条件 p || q is "if p then true else q" ! 否定 !p is "not p"
アドレス演算子
アドレス演算子&
は、オペランドのアドレスを生成します。このときオペランドはアドレス指定可能でなければならず、また変数、ポインタの間接参照、配列またはスライスのインデックス操作、アドレス指定可能な構造体のフィールドセレクタのいずれかでなければなりません。関数の戻り値はアドレス指定可能ではありません。オペランドとしてポインタ型を取るポインタの間接参照演算子*
は、オペランドによって指し示されている値を取り出します。
&x &a[f(2)] *p *pf(x)
通信演算子
チャネルという用語は「チャネル型の値」を意味します。
送信操作には、二項演算子“<-”を使用します。この演算子はチャネルと値(式)に作用します。
ch <- 3
送信操作によって、チャネル上に値を送信します。チャネルと式は通信を開始する前に評価されます。通信は、送信が実行可能となるまでブロックされ、可能になると値はチャネルに送られます。バッファリングされていないチャネルへの送信は、受信側の準備ができているときに実行可能です。バッファリングされているチャネルへの送信は、バッファに空きがあるときに実行できます。
送信操作が、式のコンテキスト中に現れるならば、その式の値は論理値であり、操作はブロックされません。通信が行われたときは、その論理値の値はtrue
となります。行われなかったときはfalse
になります。(成否とは関係なく、チャネルと送信される式は評価が行われます。)
次の2つの例は等しい内容です。
ok := ch <- 3; if ok { print("sent") } else { print("not sent") } if ch <- 3 { print("sent") } else { print("not sent") }
言い換えるなら、プログラムが送信操作の値をチェックするときは、送信はブロックされず、その式の値は操作の結果となります。プログラムで値をチェックしなければ、それが行われるまで操作はブロックし続けます。
受信操作には、単項演算子“<-”を使用します。この式の値は受信した値であり、その型はこのチャネルの要素型です。
<-ch
値が利用可能になるまで式はブロックされ、そのあとは変数に代入するなど、他の式と同じように利用できます。受信した値を受け取らなければ、その値は破棄されます。
v1 := <-ch v2 = <-ch f(<-ch) <-strobe // クロックパルス待ち
受信式が代入、または初期化で使われるときは次の形式になります。
x, ok = <-ch x, ok := <-ch var x, ok = <-ch
この受信操作のときはブロックされません。受信操作が実行できるときは、論理値型の変数ok
にtrue
が、x
には受信した値が格納されます。そうでなければok
にはfalse
がセットされ、x
にはその型のゼロ値がセットされます。(§ゼロ値)
メソッド式
メソッドM
が、型T
のメソッド群に含まれているとき、M
の引数リストの先頭にこのメソッドのレシーバを加えることで、 T.M
を普通の関数として呼び出すことができます。
MethodExpr = ReceiverType "." MethodName . ReceiverType = TypeName | "(" "*" TypeName ")" .
2つのメソッド、Mv
(レシーバは型 T
)とMp
(レシーバは型*T
)を持つ構造体型T
について考えてみます。
type T struct { a int; } func (tv T) Mv(a int) int { return 0 } // 非ポインタのレシーバ func (tp *T) Mp(f float) float { return 1 } // ポインタのレシーバ var t T;
次の式をみてください。
T.Mv
これは、Mv
メソッドとは等しいですが、必ず引数の先頭にレシーバを持った関数です。この関数のシグネチャは、次のようになります。
func (tv T, a int) int
この関数は明示的にレシーバを指定することで、通常通り呼び出すことができます。下の3つの呼び出しは等価です。
t.Mv(7) T.Mv(t, 7) f := T.Mv; f(t, 7)
次の同じような式をみてください。
(*T).Mp
これは、Mp
メソッドを表す関数値であり、次のシグネチャを持っています。
func (tp *T, f float) float
非ポインタのレシーバを持つメソッドから、ポインタのレシーバを持った関数を得ることができます。これは次のようになります。
(*T).Mv
これは、Mv
メソッドを表す関数値であり、次のシグネチャを持っています。
func (tv *T, a int) int
このような関数では、本来のメソッドのレシーバへ受け渡す値を得るためにレシーバを間接参照します。メソッドは、関数呼び出しで渡されたこのアドレスの値を上書きすることはありません。
最後のケースとして、ポインタのレシーバを持つメソッドを、非ポインタのレシーバ関数として使用することはできません。なぜならポインタのレシーバを持つメソッドは、この型のメソッド群には含まれていないからです。
メソッドから取得した関数の値は、関数の呼び出し構文によって呼び出されます。このときレシーバは、呼び出しの引数の先頭に与えられます。つまり、f := T.Mv
のとき、f
はt.f(7)
ではなく、 f(t, 7)
として実行します。レシーバにバインドする関数を作成するには、クロージャを使ってください。
インタフェース型のメソッドから関数の値を引き出すこともできます。結果得られる関数は、そのインタフェース型のレシーバを必ず受け取ります。
変換
変換とは、T
が型であり、x
が型T
へ変換可能な式であるとき、T(x)
で表される式です。
Conversion = LiteralType "(" Expression ")" .
一般的に変換は、x
の値が型T
と代入の適合性を持っているとき、あるいは値と型T
が代入の整合性をとりうるとき、あるいは値の型、T
、またはこれらのコンポーネント型が名前を持たないときに成功します。通常、このような変換は、x
の値でなく型を変更するため、実行時にはコストがかかりません。
変換規則は、T
が数値型または文字列型である変換に適用されます。これらの変換によって値の内容が変わったり、実行コストが増えたりする可能性があります。
整数型間の変換
値が符号を持った数値であるならば、無限精度まで暗黙的に符号拡張されます。さもなければ、その値はゼロ拡張されます。そのあとで変換結果となる型に合うように精度が切り捨てられます。たとえば、x := uint16(0x10F0)
ならば、uint32(int8(x)) == 0xFFFFFFF0
になります。変換の結果は常に有効な値となり、オーバフローは起こしません。
浮動小数点型を含む変換
- 浮動小数点数を整数に変換するとき、小数部は捨てられます(ゼロに近づくよう切り捨て/切り上げ)。
- 数値を浮動小数点型に変換するとき、結果となる値はその浮動小数点型で規定されている精度に丸められます。たとえば、
float32
型の変数x
の値は、格納されるときIEEE-754 32ビットの数値以上の精度が使われます。しかし、float32(x)
が表すのは32ビットに丸められたx
の値です。同様に、x + 0.1
は32ビット以上の精度が使われますが、float32(x + 0.1)
はそうなりません。
浮動小数点値を含んでいるすべての変換において、結果となる型が値を表現することができなければ、変換自体は成功しますが、結果となる値は実装依存となります。
文字列への変換
- 整数値を変換することで、その整数が表すUTF-8文字を持つ文字列が得られます。
string(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
- 整数のスライスを変換することで、各整数を文字列へ変換したあと結合した文字列が得られます。スライスの値が
nil
であれば、結果は空文字列になります。string([]int{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
- バイトのスライスを変換することで、スライス内の連続したバイトデータをそのまま持った文字列が得られます。スライスの値が
nil
であれば、結果は空文字列になります。string([]byte{'h', 'e', 'l', 'l', 'o'}) // "hello"
ポインタと整数間で変換を行う仕組みは言語上はありません。ただしunsafeパッケージでは、ある程度の制限はありますがこの機能を実装しています。
定数式
定数式は、定数オペランドだけを含み、コンパイル時に評価される式です。
論理値型、数値型、文字列型定数がオペランドとして合法的に使えるときは常に、型を持たない論理値型、数値型、文字列型定数をそれぞれオペランドとして使用できます。シフト演算を除き、二項演算のオペランドが型を持たない整数定数と、同じく型を持たない浮動小数点定数であるとき、整数定数は型を持たない浮動小数点定数に変換されます(/
と%
がこれに相当します)。
結果がbool
型となる比較演算子を除き、型を持たない定数に演算子を適用した結果は、同種(すなわち、論理値型、整数型、浮動小数点型、文字列型定数いずれか)の型を持たない定数となります。
定数式は、常に正確に評価されます。そのため評価中の値と定数は、言語でサポートされている事前定義済みの型よりかなり大きな精度を必要とするかもしれません。よって以下は、正しい宣言です。
const Huge = 1 << 100; const Four int8 = Huge >> 98;
型を持っている定数の値は常に、その定数の型の値を正確に表せなければなりません。よって以下の定数式は正しくありません。
uint(-1) // -1はuintでオーバフローを起こす int(3.14) // 3.14はintegerで切り捨てられる int64(Huge) // 1<<100はint64でオーバフローを起こす Four * 300 // 300はint8でオーバフローを起こす Four * 100 // 400はint8でオーバフローを起こす
単項のビット補数演算子^
を使用したマスクは、非定数の規則と適合します。このマスクは符号なし定数のときは全て1に、符号あり、または型を持たない定数のときは-1になります。
^1 // 型を持たない整数定数。-2に等しい uint8(^1) // エラー。 uint8(-2)と同じで、範囲外 ^uint8(1) // uint8型の定数。0xFF ^ uint8(1) = uint8(0xFE)となる int8(^1) // int8(-2)と同じ ^int8(1) // -1 ^ int8(1) = -2となる
評価の順番
代入または式の要素を評価するときには、全ての関数呼び出し、メソッド呼び出し、通信操作は、字句ごとに左から右へと順に評価されます。
代入例です。
y[f()], ok = g(h(), i() + x[j()], <-c), k()
これら関数の呼び出しと通信は、f()
、 h()
、 i()
、 j()
、 <-c
、 g()
、k()
の順で起こります。しかし、これら関数呼び出しと通信の順番は、x
のインデックス指定と評価、およびy
の評価と比べると未定義です。
単一式中の浮動小数点演算は、演算子がもつ結合法則に従って評価されます。明示的な括弧は、規定の結合法則を上書きすることで評価に影響を及ぼします。式x + (y + z)
では、x
を加える前にy + z
の加算が行われます。
Comments