Maison > Article > développement back-end > Qu'est-ce qui est nul dans le langage go
nil est un identifiant prédéfini dans le langage Go, qui représente une valeur nulle ou nulle. Dans le langage Go, la valeur zéro (valeur initiale) du type booléen est fausse, la valeur zéro du type numérique est 0, la valeur zéro du type chaîne est la chaîne vide "''", et le pointeur, slice , map, canal, fonction La valeur zéro de l'interface est nulle. nil n'est pas un mot-clé ou un mot réservé. Les pointeurs vers nil de types différents ne peuvent pas être comparés et les valeurs nil du même type ne peuvent pas être comparées.
L'environnement d'exploitation de ce tutoriel : système Windows 7, GO version 1.18, ordinateur Dell G3.
Dans le langage Go, la valeur zéro (valeur initiale) du type booléen est fausse, la valeur zéro du type numérique est 0, la valeur zéro du type chaîne est la chaîne vide" ", et le pointeur, La valeur zéro pour les tranches, les cartes, les canaux, les fonctions et les interfaces est nulle.
nil est un identifiant prédéfini dans le langage Go. Les développeurs ayant de l'expérience dans le développement d'autres langages de programmation peuvent considérer nil comme nul (NULL) dans d'autres langages. En fait, ce n'est pas tout à fait correct, car Go Il existe de nombreuses différences. entre nul dans cette langue et null dans d’autres langues.
Fonctionnalité Go Language Nil
Les identifiants Nil ne peuvent pas être comparés
Nil n'est pas un mot-clé ou un mot réservé
Les pointeurs des différents types de Nil sont les mêmes
différents types de nil Il ne peut pas être comparé
Deux valeurs nulles du même type ne peuvent pas être comparées
nil est la valeur zéro d'un type de référence commun
Je crois que les programmeurs qui ont écrit Golang sont très, très familier avec le morceau de code suivant Oui :
if err != nil { // do something.... }
Quand il n'est pas égal à nil
, cela signifie qu'une erreur s'est produite. Nous devons effectuer un traitement pour cette erreur. Si elle est égale. à nil
, cela signifie fonctionner normalement. Alors, qu'est-ce que néant
? Consultez le dictionnaire et vous saurez que nil
ne signifie rien, ou une valeur nulle. Valeur zéro, valeur zéro, ça vous dit quelque chose ? Dans le langage Go, si vous déclarez une variable mais ne lui attribuez pas de valeur, la variable aura une valeur par défaut de zéro. C'est la valeur zéro correspondante pour chaque type : nil
的时候,说明出现某些错误了,需要我们对这个错误进行一些处理,而如果等于nil
说明运行正常。那什么是nil
呢?查一下词典可以知道,nil
的意思是无,或者是零值。零值,zero value,是不是有点熟悉?在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。这是每种类型对应的零值:
bool -> false numbers -> 0 string -> "" pointers -> nil slices -> nil maps -> nil channels -> nil functions -> nil interfaces -> nil
举个例子,当你定义了一个struct:
type Person struct { AgeYears int Name string Friends []Person } var p Person // Person{0, "", nil}
变量p
只声明但没有赋值,所以p的所有字段都有对应的零值。那么,这个nil
到底是什么呢?Go的文档中说到,nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量:
type Type int var nil Type
是不是有点惊讶?nil
并不是Go的关键字之一,你甚至可以自己去改变nil
的值:
var nil = errors.New("hi")
这样是完全可以编译得过的,但是最好不要这样子去做。
在了解了什么是nil
之后,再来说说nil
有什么用。
pointers
var p *int p == nil // true *p // panic: invalid memory address or nil pointer dereference
指针表示指向内存的地址,如果对为nil的指针进行解引用的话就会导致panic。那么为nil
的指针有什么用呢?先来看一个计算二叉树和的例子:
type tree struct { v int l *tree r *tree } // first solution func (t *tree) Sum() int { sum := t.v if t.l != nil { sum += t.l.Sum() } if t.r != nil { sum += t.r.Sum() } return sum }
上面的代码有两个问题,一个是代码重复:
if v != nil { v.m() }
另一个是当t
是nil
的时候会panic:
var t *tree sum := t.Sum() // panic: invalid memory address or nil pointer dereference
怎么解决上面的问题?我们先来看看一个指针接收器的例子:
type person struct {} func sayHi(p *person) { fmt.Println("hi") } func (p *person) sayHi() { fmt.Println("hi") } var p *person p.sayHi() // hi
对于指针对象的方法来说,就算指针的值为nil
也是可以调用的,基于此,我们可以对刚刚计算二叉树和的例子进行一下改造:
func(t *tree) Sum() int { if t == nil { return 0 } return t.v + t.l.Sum() + t.r.Sum() }
跟刚才的代码一对比是不是简洁了很多?对于nil
指针,只需要在方法前面判断一下就ok了,无需重复判断。换成打印二叉树的值或者查找二叉树的某个值都是一样的:
func(t *tree) String() string { if t == nil { return "" } return fmt.Sprint(t.l, t.v, t.r) } // nil receivers are useful: Find func (t *tree) Find(v int) bool { if t == nil { return false } return t.v == v || t.l.Find(v) || t.r.Find(v) }
所以如果不是很需要的话,不要用NewX()去初始化值,而是使用它们的默认值。
slices
// nil slices var s []slice len(s) // 0 cap(s) // 0 for range s // iterates zero times s[i] // panic: index out of range
一个为nil
的slice,除了不能索引外,其他的操作都是可以的,当你需要填充值的时候可以使用append
函数,slice会自动进行扩充。那么为nil
// nil maps var m map[t]u len(m) // 0 for range m // iterates zero times v, ok := m[i] // zero(u), false m[i] = x // panic: assignment to entry in nil mapPar exemple, lorsque vous définissez une struct :
func NewGet(url string, headers map[string]string) (*http.Request, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } return req, nil }La variable
p
est uniquement déclarée mais non assignée, donc tous les champs de p correspondent à valeur nulle. Alors, qu'est-ce que c'est exactement néant
? La documentation de Go dit que nil est un identifiant prédéfini qui représente la valeur zéro d'un pointeur, d'un canal, d'une fonction, d'une interface, d'un mappage ou d'une tranche, qui est une variable prédéfinie : NewGet("http://google.com", map[string]string{ "USER_AGENT": "golang/gopher", },)
Un peu surpris ? nil
n'est pas l'un des mots-clés de Go. Vous pouvez même modifier vous-même la valeur de nil
:
NewGet("http://google.com", map[string]string{})
C'est complètement compilable, mais il vaut mieux ne pas le faire. Par ici.
🎜À quoi sert nil🎜🎜🎜Après avoir compris ce qu'estnil
, parlons de l'utilité de nil
. 🎜🎜🎜pointeurs🎜🎜NewGet("http://google.com", nil)🎜Les pointeurs représentent des adresses pointant vers la mémoire. Le déréférencement d'un pointeur nul provoquera la panique. Alors à quoi sert un pointeur
nil
? Regardons d'abord un exemple de calcul de la somme d'arbres binaires : 🎜// nil channels var c chan t <- c // blocks forever c <- x // blocks forever close(c) // panic: close of nil channel🎜Le code ci-dessus a deux problèmes, l'un est la duplication de code : 🎜
func merge(out chan<- int, a, b <-chan int) { for { select { case v := <-a: out <- v case v := <- b: out <- v } } }🎜L'autre est lorsque
t
est nil Parfois ça va paniquer : 🎜<pre class="brush:js;toolbar:false;">func merge(out chan<- int, a, b <-chan int) {
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok {
a = nil
fmt.Println("a is nil")
continue
}
out <- v
case v, ok := <-b:
if !ok {
b = nil
fmt.Println("b is nil")
continue
}
out <- v
}
}
fmt.Println("close out")
close(out)
}</pre>🎜Comment résoudre le problème ci-dessus ? Regardons d'abord un exemple de récepteur pointeur : 🎜<pre class="brush:js;toolbar:false;">func do() error { // error(*doError, nil)
var err *doError
return err // nil of type *doError
}
func main() {
err := do()
fmt.Println(err == nil)
}</pre>🎜Pour la méthode de l'objet pointeur, elle peut être appelée même si la valeur du pointeur est <code>nil
. Sur cette base, nous pouvons simplement. calculer l'arbre binaire Modifier l'exemple de et : 🎜func do() error { return nil }🎜Par rapport au code de tout à l'heure, est-ce beaucoup plus simple ? Pour les pointeurs
nil
, il vous suffit de porter un jugement avant la méthode et tout ira bien. Il n'est pas nécessaire de porter des jugements répétés. C'est la même chose lors de l'impression de la valeur d'un arbre binaire ou de la recherche d'une valeur dans un arbre binaire : 🎜func do() *doError { // nil of type *doError return nil } func wrapDo() error { // error (*doError, nil) return do() // nil of type *doError } func main() { err := wrapDo() // error (*doError, nil) fmt.Println(err == nil) // false }🎜Donc si ce n'est pas vraiment nécessaire, n'utilisez pas NewX() pour initialiser les valeurs, mais utilisez leurs valeurs par défaut. 🎜🎜🎜tranches🎜🎜rrreee🎜Une tranche
nil
peut être utilisée sauf qu'elle ne peut pas être indexée, lorsque vous en avez besoin. pour remplir la valeur, vous pouvez utiliser la fonction append
, et la tranche se développera automatiquement. Alors, quelle est la structure sous-jacente de la tranche qui est nil
? Selon la documentation officielle, slice comporte trois éléments, à savoir la longueur, la capacité et le pointeur vers le tableau : 🎜🎜🎜🎜🎜Quand il y a des éléments : 🎜🎜🎜🎜所以我们并不需要担心slice的大小,使用append的话slice会自动扩容。(视频中说slice自动扩容速度很快,不必担心性能问题,这个值得商榷,在确定slice大小的情况只进行一次内存分配总是好的)
map
对于Go来说,map,function,channel都是特殊的指针,指向各自特定的实现,这个我们暂时可以不用管。
// nil maps var m map[t]u len(m) // 0 for range m // iterates zero times v, ok := m[i] // zero(u), false m[i] = x // panic: assignment to entry in nil map
对于nil
的map,我们可以简单把它看成是一个只读的map,不能进行写操作,否则就会panic。那么nil
的map有什么用呢?看一下这个例子:
func NewGet(url string, headers map[string]string) (*http.Request, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } for k, v := range headers { req.Header.Set(k, v) } return req, nil }
对于NewGet
来说,我们需要传入一个类型为map的参数,并且这个函数只是对这个参数进行读取,我们可以传入一个非空的值:
NewGet("http://google.com", map[string]string{ "USER_AGENT": "golang/gopher", },)
或者这样传:
NewGet("http://google.com", map[string]string{})
但是前面也说了,map的零值是nil
,所以当header
为空的时候,我们也可以直接传入一个nil
:
NewGet("http://google.com", nil)
是不是简洁很多?所以,把nil
map作为一个只读的空的map进行读取吧。
channel
// nil channels var c chan t <- c // blocks forever c <- x // blocks forever close(c) // panic: close of nil channel
关闭一个nil
的channel会导致程序panic
(如何关闭channel可以看这篇文章:如何优雅地关闭Go channel)举个例子,假如现在有两个channel负责输入,一个channel负责汇总,简单的实现代码:
func merge(out chan<- int, a, b <-chan int) { for { select { case v := <-a: out <- v case v := <- b: out <- v } } }
如果在外部调用中关闭了a或者b,那么就会不断地从a或者b中读出0,这和我们想要的不一样,我们想关闭a和b后就停止汇总了,修改一下代码:
func merge(out chan<- int, a, b <-chan int) { for a != nil || b != nil { select { case v, ok := <-a: if !ok { a = nil fmt.Println("a is nil") continue } out <- v case v, ok := <-b: if !ok { b = nil fmt.Println("b is nil") continue } out <- v } } fmt.Println("close out") close(out) }
在知道channel关闭后,将channel的值设为nil,这样子就相当于将这个select case子句停用了,因为nil
的channel是永远阻塞的。
interface
interface并不是一个指针,它的底层实现由两部分组成,一个是类型,一个值,也就是类似于:(Type, Value)。只有当类型和值都是nil
的时候,才等于nil
。看看下面的代码:
func do() error { // error(*doError, nil) var err *doError return err // nil of type *doError } func main() { err := do() fmt.Println(err == nil) }
输出结果是false
。do
函数声明了一个*doErro
的变量err
,然后返回,返回值是error
接口,但是这个时候的Type已经变成了:(*doError,nil),所以和nil
肯定是不会相等的。所以我们在写函数的时候,不要声明具体的error变量,而是应该直接返回nil
:
func do() error { return nil }
再来看看这个例子:
func do() *doError { // nil of type *doError return nil } func wrapDo() error { // error (*doError, nil) return do() // nil of type *doError } func main() { err := wrapDo() // error (*doError, nil) fmt.Println(err == nil) // false }
这里最终的输出结果也是false
。为什么呢?尽管wrapDo
函数返回的是error
类型,但是do
返回的却是*doError
类型,也就是变成了(*doError,nil),自然也就和nil
不相等了。因此,不要返回具体的错误类型。遵从这两条建议,才可以放心地使用if x != nil
。
看完了那个视频,发现nil
还有这么多用处,真是意外之喜。
油管上面还有很多干货满满的视频,可以多学习学习咯。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!