Голанг смешанного присвоения и декларации
Я начал работать с го в течение нескольких недель, и (еще раз) я наткнулся на что-то странное для меня:
// Not working
a := 1
{
a, b := 2, 3
}
// Works
a := 1
a, b := 2, 3
Я хочу назначить две переменные одновременно. Один уже объявлен в превосходном объеме, другой - нет.
Это не работает: компилятор пытается переопределить прежнюю переменную. Тем не менее, он работает нормально, если эта переменная объявлена в той же области видимости.
Это почему?
3 ответа
То, что вы испытываете, широко известно как "переменное затенение". Когда вы используете :=
с любой переменной во внутренней области, в том числе в таких выражениях, как 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
оператор, поэтому переменная объявляется как задумано; это позволяет вам использовать=
для всех других экземпляров этой переменной в контексте этой области и любых областей, в которые она включена - изменить все экземпляры
=
в:=
исправить ошибку
Обратите внимание, что вы можете столкнуться с проблемой затенения переменных в любом из двух последних случаев, когда функция возвращает несколько значений, но это может быть решено, как описано выше.
Попробуйте оба примера на игровой площадке Go.
Ваш последний пример иллюстрирует комбинацию объявления и инициализации новой переменной 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
то вы получите сообщение от компилятора об отсутствии новых переменных в левой части :=
для второго объявления, которое является окольным способом сказать, что вы пытаетесь объявить a
дважды в одной области.
Обратите внимание, что эта идея, если применять ее осторожно, также может быть использована для поиска переменных, которые затенены. Например, код "не работает" в вашем примере будет печатать разные адреса для 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`
Согласно Голангу документация:
Идентификатор, объявленный в блоке, может быть повторно объявлен во внутреннем блоке.
Это именно то, что показывает ваш пример, a объявлено в скобках из-за ':=' и никогда не используется.
Решение состоит в том, чтобы объявить обе переменные и затем использовать их:
var a, b int
{
b, a = 2, 3
fmt.Println(b)
}
fmt.Println(a)
Ваш вопрос состоит из 2 частей:
первая часть:
= это просто назначение
:= определить и назначить для новых переменных (как минимум, одну новую переменную) внутри функционального блока (не глобального), рабочий пример:
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
}
Короче: как a, b = 2, 3
означает "назначить оба" и a, b := 2, 3
означает "объявить и назначить оба", и вам нужно назначить одно, а объявить и назначить другое, решение состоит в том, чтобы объявить другой и назначить оба:
a := 1
{
var b int
a, b = 2, 3
}