Heim > Backend-Entwicklung > Golang > Was ist Null in der Go-Sprache?

Was ist Null in der Go-Sprache?

青灯夜游
Freigeben: 2023-01-11 10:45:27
Original
3041 Leute haben es durchsucht

nil ist ein vordefinierter Bezeichner in der Go-Sprache, der einen Null- oder Nullwert darstellt. In der Go-Sprache ist der Nullwert (Anfangswert) des booleschen Typs falsch, der Nullwert des numerischen Typs ist 0, der Nullwert des Zeichenfolgentyps ist die leere Zeichenfolge „''“ und der Zeiger ist Slice , Karte, Kanal, Funktion und der Nullwert der Schnittstelle ist Null. Null ist kein Schlüsselwort oder reserviertes Wort. Null-Zeiger verschiedener Typen können nicht verglichen werden, und Null-Werte desselben Typs können nicht verglichen werden.

Was ist Null in der Go-Sprache?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, GO Version 1.18, Dell G3-Computer.

Was ist Null? In der Go-Sprache ist der Nullwert (Anfangswert) des booleschen Typs falsch, der Nullwert des numerischen Typs ist 0 und der Nullwert des Zeichenfolgentyps ist die leere Zeichenfolge. ", und der Zeiger: Der Nullwert für Slices, Maps, Kanäle, Funktionen und Schnittstellen ist Null.

nil ist ein vordefinierter Bezeichner in der Go-Sprache. Entwickler, die Erfahrung in der Entwicklung anderer Programmiersprachen haben, betrachten Null in anderen Sprachen möglicherweise nicht vollständig zwischen Null in dieser Sprache und Null in anderen Sprachen.

Nil-Funktion der Go-Sprache

Nil-Bezeichner können nicht verglichen werden
  • nil ist kein Schlüsselwort oder reserviertes Wort
  • Die Zeiger verschiedener Arten von Null sind gleich.
  • verschiedene Arten von nil Es kann nicht verglichen werden.
  • Zwei Nullwerte desselben Typs dürfen nicht verglichen werden.
  • nil ist der Nullwert eines gemeinsamen Referenztyps. Ich glaube, dass Programmierer, die Golang geschrieben haben, sehr Sehr vertraut mit dem folgenden Codeteil Ja:
  • if err != nil {
        // do something....
    }
    Nach dem Login kopieren
  • Wenn es nicht gleich nil ist, bedeutet das, dass ein Fehler aufgetreten ist und wir diesen Fehler verarbeiten müssen, und wenn ja gleich nil ist, bedeutet dies, dass es normal läuft. Was ist also nil? Schauen Sie im Wörterbuch nach und Sie werden wissen, dass nil nichts oder einen Nullwert bedeutet. Nullwert, Nullwert, kommt Ihnen das bekannt vor? Wenn Sie in der Go-Sprache eine Variable deklarieren, ihr aber keinen Wert zuweisen, hat die Variable den Standardwert Null. Dies ist der entsprechende Nullwert für jeden Typ:

    bool      -> false                              
    numbers -> 0                                 
    string    -> ""      
    
    pointers -> nil
    slices -> nil
    maps -> nil
    channels -> nil
    functions -> nil
    interfaces -> nil
    Nach dem Login kopieren

    Zum Beispiel, wenn Sie eine Struktur definieren:
  • type Person struct {
      AgeYears int
      Name string
      Friends []Person
    }
    
    var p Person // Person{0, "", nil}
    Nach dem Login kopieren
Die Variable p wird nur deklariert, aber nicht zugewiesen, sodass alle Felder von p entsprechend sind Nullwert. Was genau ist also dieser nil? In der Dokumentation von Go heißt es, dass nil ein vordefinierter Bezeichner ist, der den Nullwert eines Zeigers, Kanals, einer Funktion, einer Schnittstelle, einer Zuordnung oder eines Slice darstellt, bei dem es sich um eine vordefinierte Variable handelt:

type Type int
var nil Type
Nach dem Login kopieren

Etwas überrascht? nil ist nicht eines der Schlüsselwörter von Go. Sie können den Wert von nil sogar selbst ändern:

var nil = errors.New("hi")
Nach dem Login kopieren
nil的时候,说明出现某些错误了,需要我们对这个错误进行一些处理,而如果等于nil说明运行正常。那什么是nil呢?查一下词典可以知道,nil的意思是无,或者是零值。零值,zero value,是不是有点熟悉?在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。这是每种类型对应的零值:

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference
Nach dem Login kopieren

举个例子,当你定义了一个struct:

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
}
Nach dem Login kopieren

变量p只声明但没有赋值,所以p的所有字段都有对应的零值。那么,这个nil到底是什么呢?Go的文档中说到,nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量:

if v != nil {
  v.m()
}
Nach dem Login kopieren

是不是有点惊讶?nil并不是Go的关键字之一,你甚至可以自己去改变nil的值:

var t *tree
sum := t.Sum()   // panic: invalid memory address or nil pointer dereference
Nach dem Login kopieren

这样是完全可以编译得过的,但是最好不要这样子去做。

nil有什么用

在了解了什么是nil之后,再来说说nil有什么用。

pointers

type person struct {}
func sayHi(p *person) { fmt.Println("hi") }
func (p *person) sayHi() { fmt.Println("hi") }
var p *person
p.sayHi() // hi
Nach dem Login kopieren

指针表示指向内存的地址,如果对为nil的指针进行解引用的话就会导致panic。那么为nil的指针有什么用呢?先来看一个计算二叉树和的例子:

func(t *tree) Sum() int {
  if t == nil {
    return 0
  }
  return t.v + t.l.Sum() + t.r.Sum()
}
Nach dem Login kopieren

上面的代码有两个问题,一个是代码重复:

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)
}
Nach dem Login kopieren

另一个是当tnil的时候会panic:

// nil slices
var s []slice
len(s)  // 0
cap(s)  // 0
for range s  // iterates zero times
s[i]  // panic: index out of range
Nach dem Login kopieren

怎么解决上面的问题?我们先来看看一个指针接收器的例子:

// 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
Nach dem Login kopieren
Nach dem Login kopieren

对于指针对象的方法来说,就算指针的值为nil也是可以调用的,基于此,我们可以对刚刚计算二叉树和的例子进行一下改造:

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
}
Nach dem Login kopieren
Nach dem Login kopieren

跟刚才的代码一对比是不是简洁了很多?对于nil指针,只需要在方法前面判断一下就ok了,无需重复判断。换成打印二叉树的值或者查找二叉树的某个值都是一样的:

NewGet("http://google.com", map[string]string{
  "USER_AGENT": "golang/gopher",
},)
Nach dem Login kopieren
Nach dem Login kopieren

所以如果不是很需要的话,不要用NewX()去初始化值,而是使用它们的默认值。

slices

NewGet("http://google.com", map[string]string{})
Nach dem Login kopieren
Nach dem Login kopieren

一个为nil的slice,除了不能索引外,其他的操作都是可以的,当你需要填充值的时候可以使用append函数,slice会自动进行扩充。那么为nil Das ist vollständig kompilierbar, aber es ist am besten, es nicht zu tun Hier entlang.

Was ist Null in der Go-Sprache?Wozu dient nil?

Nachdem wir verstanden haben, was nil ist, sprechen wir über den Nutzen von nil.

Was ist Null in der Go-Sprache?

Zeiger🎜🎜
NewGet("http://google.com", nil)
Nach dem Login kopieren
Nach dem Login kopieren
🎜Zeiger stellen Adressen dar, die auf den Speicher verweisen. Die Dereferenzierung eines Nullzeigers führt zu Panik. Was nützt also ein nil-Zeiger? Schauen wir uns zunächst ein Beispiel für die Berechnung der Summe von Binärbäumen an: 🎜
// nil channels
var c chan t
<- c      // blocks forever
c <- x    // blocks forever
close(c)  // panic: close of nil channel
Nach dem Login kopieren
Nach dem Login kopieren
🎜Der obige Code hat zwei Probleme, eines ist die Codeduplizierung: 🎜
func merge(out chan<- int, a, b <-chan int) {
  for {
    select {
      case v := <-a:
        out <- v
      case v := <- b:
        out <- v
    }
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Das andere ist, wenn t nil< ist /code> Manchmal kommt es zu Panik: 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">func merge(out chan&lt;- int, a, b &lt;-chan int) { for a != nil || b != nil { select { case v, ok := &lt;-a: if !ok { a = nil fmt.Println(&quot;a is nil&quot;) continue } out &lt;- v case v, ok := &lt;-b: if !ok { b = nil fmt.Println(&quot;b is nil&quot;) continue } out &lt;- v } } fmt.Println(&quot;close out&quot;) close(out) }</pre><div class="contentsignin">Nach dem Login kopieren</div></div><div class="contentsignin">Nach dem Login kopieren</div></div>🎜Wie löst man das obige Problem? Schauen wir uns zunächst ein Beispiel eines Zeigerempfängers an: 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><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><div class="contentsignin">Nach dem Login kopieren</div></div><div class="contentsignin">Nach dem Login kopieren</div></div>🎜Für die Methode des Zeigerobjekts kann sie auch dann aufgerufen werden, wenn der Wert des Zeigers <code>nil ist Berechnen Sie den Binärbaum. Ändern Sie das Beispiel von und: 🎜
func do() error {
  return nil
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Ist es im Vergleich zum Code gerade viel einfacher? Für nil-Zeiger müssen Sie nur vor der Methode ein Urteil fällen, und es ist in Ordnung. Es besteht keine Notwendigkeit, wiederholte Urteile zu fällen. Das Gleiche gilt, wenn Sie den Wert eines Binärbaums drucken oder nach einem Wert in einem Binärbaum suchen: 🎜
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
}
Nach dem Login kopieren
Nach dem Login kopieren
🎜Wenn es also nicht wirklich benötigt wird, verwenden Sie nicht NewX(), um die Werte zu initialisieren, sondern verwenden Sie deren Standardwerte. 🎜🎜🎜Slices🎜🎜rrreee🎜Ein nil-Slice kann bei Bedarf bearbeitet werden, außer dass es nicht indiziert werden kann Um den Wert einzugeben, können Sie die Funktion append verwenden. Das Slice wird dann automatisch erweitert. Was ist also die zugrunde liegende Struktur von Slice, die nil ist? Laut der offiziellen Dokumentation besteht Slice aus drei Elementen, nämlich Länge, Kapazität und Zeiger auf das Array: 🎜🎜🎜🎜🎜Wenn Elemente vorhanden sind: 🎜🎜🎜🎜

所以我们并不需要担心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
Nach dem Login kopieren
Nach dem Login kopieren

对于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
}
Nach dem Login kopieren
Nach dem Login kopieren

对于NewGet来说,我们需要传入一个类型为map的参数,并且这个函数只是对这个参数进行读取,我们可以传入一个非空的值:

NewGet("http://google.com", map[string]string{
  "USER_AGENT": "golang/gopher",
},)
Nach dem Login kopieren
Nach dem Login kopieren

或者这样传:

NewGet("http://google.com", map[string]string{})
Nach dem Login kopieren
Nach dem Login kopieren

但是前面也说了,map的零值是nil,所以当header为空的时候,我们也可以直接传入一个nil

NewGet("http://google.com", nil)
Nach dem Login kopieren
Nach dem Login kopieren

是不是简洁很多?所以,把nil map作为一个只读的空的map进行读取吧。

channel

// nil channels
var c chan t
<- c      // blocks forever
c <- x    // blocks forever
close(c)  // panic: close of nil channel
Nach dem Login kopieren
Nach dem Login kopieren

关闭一个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
    }
  }
}
Nach dem Login kopieren
Nach dem Login kopieren

如果在外部调用中关闭了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)
}
Nach dem Login kopieren
Nach dem Login kopieren

在知道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)
}
Nach dem Login kopieren
Nach dem Login kopieren

输出结果是falsedo函数声明了一个*doErro的变量err,然后返回,返回值是error接口,但是这个时候的Type已经变成了:(*doError,nil),所以和nil肯定是不会相等的。所以我们在写函数的时候,不要声明具体的error变量,而是应该直接返回nil

func do() error {
  return nil
}
Nach dem Login kopieren
Nach dem Login kopieren

再来看看这个例子:

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
}
Nach dem Login kopieren
Nach dem Login kopieren

这里最终的输出结果也是false。为什么呢?尽管wrapDo函数返回的是error类型,但是do返回的却是*doError类型,也就是变成了(*doError,nil),自然也就和nil不相等了。因此,不要返回具体的错误类型。遵从这两条建议,才可以放心地使用if x != nil

总结

看完了那个视频,发现nil还有这么多用处,真是意外之喜。
油管上面还有很多干货满满的视频,可以多学习学习咯。

【相关推荐:Go视频教程编程教学

Das obige ist der detaillierte Inhalt vonWas ist Null in der Go-Sprache?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage