Эта статья была впервые опубликована вличный блог at7h
В этой статье мы обсудим вопросы, связанные с нулевым значением, nil и пустой структурой в Golang, а также некоторые из их применений.
нулевое значение
Нулевое значение относится к стратегии всегда автоматически устанавливать начальное значение по умолчанию для вашей переменной, когда вы объявляете ее (выделяете память) без явной ее инициализации.
Во-первых, давайте посмотрим на официальное нулевое значение (The zero value) Технические характеристики:
When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
Из этого мы можем сделать вывод:
- затип значения: Логический тип
false
, тип значения0
, строка""
, массивы и структуры рекурсивно инициализируют свои элементы или поля, т.е. их начальные значения зависят от элементов или полей. - затип ссылки: обе
nil
, включая указатель указателя, функциональную функцию, интерфейс интерфейса, срез среза, канал канала, карту карты.
Часто бывает полезно присвоить значение по умолчанию объявленным вами переменным, особенно для элементов или полей в ваших массивах и структурах.Это безопасная и правильная практика, а также позволяющая сохранить ваш код кратким.
Например, обычно используется следующий пример, структураValue
содержит два неэкспортированных поля,sync.Mutex
Также вдва неэкспортированных поля. Поскольку существует нулевое значение по умолчанию, мы можем использовать его напрямую:
package main
import "sync"
type Value struct {
mu sync.Mutex
val int
}
func (v *Value)Incr(){
defer v.mu.Unlock()
v.mu.Lock()
v.val++
}
func main() {
var i Value
i.Incr()
}
Поскольку срез является ссылочным типом, его нулевое значение такжеnil
:
package main
import "fmt"
import "strings"
func main(){
var s []string
fmt.Println(s, len(s), cap(s)) // [] 0 0
fmt.Println(s == nil) // true
s = append(s, "Hello")
s = append(s, "World")
fmt.Println(strings.Join(s, ", ")) // Hello, World
}
Особого внимания требуют следующие ситуации: иногда легко запутаться, если не обращать внимания.:=
Синтаксический сахар этообъявить и инициализировать переменные, так это реальный экземпляр (для которого был назначен адрес памяти), а не нулевойnil
:
package main
import "fmt"
import "reflect"
func main() {
var s1 []string
s2 := []string{} // 或者等同于 var s2 = []string{}
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false
fmt.Println(reflect.DeepEqual(s1, s2)) // false
fmt.Println(reflect.DeepEqual(s1, []string{})) // false
fmt.Println(reflect.DeepEqual(s2, []string{})) // true
}
Также для пустых структурnil
Можно вызывать методы этого типа, которые также можно использовать для простого предоставления значений по умолчанию:
package main
import "fmt"
const defaultPath = "/usr/bin/"
type Config struct {
path string
}
func (c *Config) Path() string {
if c == nil {
return defaultPath
}
return c.path
}
func main() {
var c1 *Config
var c2 = &Config{
path: "/usr/local/bin/",
}
fmt.Println(c1.Path(), c2.Path())
}
nil
Для разработчика, который только начинает работать с Golang, только начинаетnil
Его следует использовать для проверки ошибок, примерно так:
func doSomething() error {
return nil
}
func main(){
if doSomething() != nil {
return err
}
}
Это идиоматично в Golang и побуждает разработчиков явно обрабатывать ошибки как возвращаемые значения. Теперь давайте обсудим этоnil
, аналогичные определения есть и в других языках, таких как C, C++, Java и т. д.null
, на ПитонеNone
, но в Goalngnil
С ними много различий.
nil
— это предварительно объявленный идентификатор (зарезервированное слово, не являющееся ключевым словом) в Golang, который в основном используется для представления нулевого значения ссылочных типов (указателей, интерфейсов, функций, карт, срезов и каналов), представляющих их неинициализированные значения.
// [src/builtin/builtin.go](https://golang.org/src/builtin/builtin.go#L98)
//
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
nil
это единственное нетипизированное значение в Golang, у которого нет типа по умолчанию, это не неопределенное состояние. Таким образом, вы не можете использовать его следующим образом:
a := nil
// cannot declare variable as untyped nil: a
не будет иметь типаnil
значение, присвоенноеa
неправильно, компилятор не знает, что он должен датьa
Какой тип назначить.
Стоит отметить, что самые известные в Голангеnil != nil
Проблема, давайте рассмотрим пример ниже:
var p *int
var i interface{}
fmt.Println(p) // <nil>
fmt.Println(i) // <nil>
fmt.Println(p == i) // false
Почему? почему жеnil
Но не равно?
С вопросом давайте рассмотрим следующий пример (из официальногоWhy is my nil error value not equal to nil):
func Foo() error {
var err *MyError = nil
if bad() {
err = ErrBad
}
return err
}
func main() {
err := Foo()
fmt.Println(err) // <nil>
fmt.Println(err == nil) // false
}
Виновникinterface
, принцип реализации, относящийся к интерфейсу, выходит за рамки этой статьи и будет рассмотрен позже. Общий принцип заключается в том, что для определения переменной интерфейсу нужны два основных свойства:Type and Value, давайте добавим комментарий к двум приведенным выше фрагментам кода, чтобы понять:
var p *int // (T=*int,V=nil)
var i interface{} // (T=nil,V=nil)
fmt.Println(p == i) // (T=*int, V=nil) == (T=nil, V=nil) -> false
func Foo() error {
var err *PathError = nil // (T=*PathError, V=nil)
if bad() {
err = ErrBad
}
return err // 这将始终返回 non-nil 错误
}
func main() {
err := Foo()
fmt.Println(err) // <nil>
fmt.Println(err == nil) // (T=*PathError, V=nil) == (T=nil, V=nil) -> false
}
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: чтобы избежать этой проблемы, всегда используйте при возврате ошибки
error
интерфейса и никогда не инициализируйте пустую переменную ошибки, которая может быть возвращена функцией.
Давайте изменим приведенный выше пример и посмотрим на следующий пример:
var p *int // (T=*int, V=nil)
var i interface{} // (T=nil, V=nil)
fmt.Println(p == nil) // true
fmt.Println(i == nil) // true
i = p
fmt.Println(i == nil) // (T=*int, V=nil) == (T=nil, V=nil) -> false
Суть этой проблемы в том, что в Go TourInterface values with nil underlying values.
примерi
может быть передан вinterface{}
в качестве входного параметра, вы просто проверяетеi == nil
недостаточно. Так что для суждения о нулевом указателе типа интерфейса иногда на него нельзя смело полагатьсяv == nil
, хотя эта контрольная яма случается редко, иногда она может привести к сбою вашей программы. Есть два способа решить эту проблему, вы можете разделить тип и значение с помощьюnil
Сравните или используйте пакет отраженияreflect
.
Помните: если в интерфейсе хранятся какие-либо конкретные значения, интерфейс не будет
nil
, видетьзакон отражения.
Есть, может быть, вы тоже запутались, еще приведенный выше пример, почему следующие типы можно сравнивать напрямую и получать точные результаты:
var p *int // (T=*int, V=nil)
fmt.Println(p == nil) // true
Это потому, что при выполнении приведенного выше сравнения компилятор уже знаетp
тип, поэтому компилятор может преобразовать вp == (*int)(nil)
. Но для интерфейсов компилятор не может определить базовый тип, поскольку его можно изменить.
пустая структура
Пустая структура — это тип структуры без каких-либо полей, например:
type Q struct{}
var q struct{}
Какая польза от него, если у него нет полей?
Мы знаем, что размер экземпляра структуры (то есть количество байт занимаемой памяти) определяется шириной (размером) и выравниванием (выравниванием) ее полей, что помогает скорости адресации, языка С, и т. д. Существуют похожие стратегии, пожалуйста, прочитайте о конкретных стратегиях Golang.Size and alignment guarantees.
Ясно, что размер пустой структуры равен нулю байтов:
var q struct {}
fmt.Println(unsafe.Sizeof(q)) // 0
Поскольку пустые структуры занимают ноль байтов, выравнивание заполнения не требуется, поэтому пустые структуры с вложенными пустыми структурами также не занимают место в памяти.
type Q struct {
A struct{}
B struct{
C struct{}
}
}
var q Q
fmt.Println(unsafe.Sizeof(q)) // 0
Поскольку пустые структуры не занимают места в памяти, мы объявляем массивы или слайсы с пустыми структурами как элементы, которые также не занимают места (Orthogonality in Go):
var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x)) // 0
var y = make([]struct{},1000000000)
fmt.Println(unsafe.Sizeof(x))// 24,背后关联数组为 0
Для пустой структуры (или пустого массива) размер хранилища, который она занимает, равен нулю, поэтому в памяти находятся две разные переменные нулевого размера.может иметь тот же адрес.
Взгляните на следующие примеры:
var a, b struct{}
fmt.Println(&a == &b) // true
c := make([]struct{}, 10)
d := make([]struct{}, 20)
fmt.Println(&c[0] == &d[1]) // true
type Q struct{}
func (q *Q)addr() { fmt.Printf("%p\n", q) }
func main() {
var a, b Q
a.addr() // 0x5af5a60
b.addr() // 0x5af5a60
}
e := struct{}{} // 不是零值,一个真正的实例
f := struct{}{}
fmt.Println(e == f) // true
Обратите внимание, что это равенство только возможно, но не достоверно.
такПример, см. это для объяснения связанных вопросовissue.
После долгого разговора вы можете подумать, что это, кажется, не имеет практического применения.Вот еще два практических практического применения:
1. использоватьchan struct{}
заменятьchan bool
Передача сигналов между горутинами. использоватьbool
Людям легко не понять ценности,true
or false
, но используяchan struct{}
Понятно, что нас не волнует значение, нас волнует только то, что произошло, и это проще выразить ясно.
2.Чтобы предотвратить инициализацию структуры без ключа, вы можете добавить_ struct {}
Поле:
type Q struct {
X, Y int
_ struct{}
Таким образом, используйтеQ{X: 1, Y: 1}
Да, но использоватьQ{1, 1}
Возникает ошибка компиляции:too few values in struct initializer
, что тоже помоглоgo ver
Проверка кода.
Ссылаться на
- The Go Programming Language Specification
- Golang Frequently Asked Questions
- Why Golang Nil Is Not Always Nil? Nil Explained
Одноименный публичный аккаунт: