私はgoで数週間作業を始めましたが、(もう一度)奇妙に思える何かに偶然出会いました。
// Not working
a := 1
{
a, b := 2, 3
}
// Works
a := 1
a, b := 2, 3
2つの変数を同時に割り当てたい。 1つはすでに宣言されていますが、上位のスコープではもう1つは宣言されていません。
これは機能しません。コンパイラは前の変数を再宣言しようとします。ただし、この変数が同じスコープで宣言されている場合は問題なく機能します。
何故ですか?
あなたが経験しているものは、一般的に "variable shadowing" として知られています。中括弧がないにもかかわらず、if
やfor
などのステートメントを含め、内部スコープ内の任意の変数で:=
を使用すると、新しい値と型がその変数に関連付けられます。
n := "Example"
//Prints the string variable `n` to standard output and
// returns the number of bytes written in int variable `n` and
// an error indicator in error variable `err`.
if n, err := fmt.Println(n); err != nil {
panic(err)
} else {
fmt.Println(n, "bytes written")
}
//Prints the string variable `n` to standard output.
fmt.Printf("n = %q\n", n)
出力:
Example
8 bytes written
n = "Example"
この問題を修正するには、いくつかの方法があります。
=
で通常の割り当てを使用します:=
で使用し、スコープが終了する前に値を復元します。とにかく別の変数を作成しているので、通常は異なる変数名を使用する方が簡単です。反対の効果も発生する可能性があり、内部スコープで何かを宣言し、それを認識しません。
if _, err := fmt.Println(n); err != nil {
panic(err)
} else {
fmt.Println(n, "bytes written")
}
//undefined: err
if _, err = fmt.Println(n); err != nil {
//undefined: err
panic(err)
}
この問題を修正するには、いくつかの方法があります。
=
で通常の割り当てを使用します:=
およびif
ステートメントを分離して、変数が意図したとおりに宣言されるようにします。これにより、そのスコープおよびそれが囲まれているスコープのコンテキストで、その変数の他のすべてのインスタンスに=
を使用できます=
のすべてのインスタンスを:=
に変更します関数が複数の値を返す場合、最後の2つのケースのいずれかで変数のシャドウイングの問題が発生する可能性がありますが、上記で説明したように解決できます。
最後の例は、新しい変数b
を宣言して初期化すると同時に、既存の変数a
に値を割り当てることを示しています。新しいスコープは作成されないので、元の変数a
をシャドウすることはありません。これは 各割り当ての後(ただし次の宣言の前)にa
のアドレスを出力して確認します) /割り当て) :
a := 1
fmt.Println(&a)
a, b := 2, 3
fmt.Println(&a)
a = b // avoids a "declared but not used" error for `b`
もちろん、b
を宣言しなかった場合、2番目の宣言である:=
の左側に新しい変数がないというエラーがコンパイラーから返されます。同じスコープ内でa
を2回宣言しようとしていることを示す遠回りの方法。
このアイデアは、注意深く適用すると、シャドウされている変数を見つけるためにも使用できることに注意してください。たとえば、例の「機能しない」コードは、内部スコープ内のa
がまだ宣言されているかどうかに応じて、 a
の異なるアドレスを出力する になります。ない:
a := 1
{
fmt.Println(&a) // original `a`
a, b := 2, 3
fmt.Println(&a) // new `a`
a = b // avoids a "declared but not used" error for `b`
}
fmt.Println(&a) // original `a`
golangドキュメント によると:
ブロックで宣言された識別子は、内部ブロックで再宣言できます。
それはまさにあなたの例が示しているものであり、aは ':='のために括弧内で再宣言され、決して使用されません。
解決策は、両方の変数を宣言してから使用することです。
var a, b int
{
b, a = 2, 3
fmt.Println(b)
}
fmt.Println(a)
あなたの質問には2つの部分があります:
最初の部分:
=は単なる割り当てです
:=は、関数ブロック(グローバルではない)内の新しい変数(少なくとも1つの新しい変数)を定義し、割り当てます。作業サンプル:
package main
import (
"fmt"
)
func main() {
var u1 uint32 //declare a variable and init with 0
u1 = 32 //assign its value
var u2 uint32 = 32 //declare a variable and assign its value at once
//declare a new variable with defining data type:
u3 := uint32(32) //inside the function block this is equal to: var u3 uint32 = 32
fmt.Println(u1, u2, u3) //32 32 32
//u3 := 20//err: no new variables on left side of :=
u3 = 20
fmt.Println(u1, u2, u3) //32 32 20
u3, str4 := 100, "str" // at least one new var
fmt.Println(u1, u2, u3, str4) //32 32 100 str
}
後編:
ブロックで宣言された識別子は、内部ブロックで再宣言される場合があります。
ここでは、変数のスコープとシャドウイングの4つの異なる作業サンプルを示します。
変数のスコープを制限する簡単な方法:
package main
import "fmt"
func main() {
i := 1
j := 2
//new scope :
{
i := "hi" //new local var
j++
fmt.Println(i, j) //hi 3
}
fmt.Println(i, j) //1 3
}
関数呼び出しを使用して変数のスコープを制限します。
package main
import "fmt"
func fun(i int, j *int) {
i++ //+Nice: use as local var without side effect
*j++ //+Nice: intentionally use as global var
fmt.Println(i, *j) //11 21
}
func main() {
i := 10 //scope: main
j := 20
fun(i, &j)
fmt.Println(i, j) //10 21
}
ステートメント内での省略代入を使用する:
package main
import "fmt"
func main() {
i := 10 //scope: main
j := 4
for i := 'a'; i < 'b'; i++ {
fmt.Println(i, j) //97 4
}
fmt.Println(i, j) //10 4
if i := "test"; len(i) == j {
fmt.Println(i, j) // i= test , j= 4
} else {
fmt.Println(i, j) //test 40
}
fmt.Println(i, j) //10 4
}
グローバル変数のシャドウイング:
package main
import "fmt"
var i int = 1 //global
func main() {
j := 2
fmt.Println(i, j) //1 2
i := 10 //Shadowing global var
fmt.Println(i, j) //10 2
fun(i, j) //10 2
}
func fun(i, j int) {
//i := 100 //no new variables on left side of :=
fmt.Println(i, j) //10 2
}