Apa itu nil dalam bahasa go

青灯夜游
Lepaskan: 2023-01-11 10:45:27
asal
2976 orang telah melayarinya

nil ialah pengecam yang dipratentukan dalam bahasa Go, mewakili nilai nol atau sifar. Dalam bahasa Go, nilai sifar (nilai awal) jenis Boolean adalah palsu, nilai sifar jenis angka ialah 0, nilai sifar jenis rentetan ialah rentetan kosong "''", dan penunjuk, potong. , peta, saluran, fungsi Nilai sifar antara muka adalah sifar. nil bukan kata kunci atau perkataan terpelihara Penunjuk kepada nil daripada jenis yang berbeza adalah sama, dan nilai nil dari jenis yang sama tidak boleh dibandingkan.

Apa itu nil dalam bahasa go

Persekitaran pengendalian tutorial ini: sistem Windows 7, GO versi 1.18, komputer Dell G3.

Apakah nil

Dalam bahasa Go, nilai sifar (nilai awal) jenis Boolean adalah palsu, nilai sifar jenis angka ialah 0 dan sifar nilai jenis rentetan Nilai sifar ialah rentetan kosong "", dan nilai sifar untuk penunjuk, kepingan, peta, saluran, fungsi dan antara muka adalah sifar.

nil ialah pengecam yang telah ditetapkan dalam bahasa Go Pembangun yang mempunyai pengalaman dalam membangunkan bahasa pengaturcaraan lain mungkin menganggap nil sebagai null (NULL) dalam bahasa lain, sebenarnya, ini tidak betul terdapat banyak perbezaan antara nol dalam bahasa Go dan null dalam bahasa lain.

Ciri tiada bahasa Go

  • pengecam nol tidak boleh dibandingkan

  • nil bukan perkataan kritikal atau perkataan terpelihara

  • Penunjuk jenis nil yang berbeza adalah sama

  • Tipe nol yang berbeza tidak boleh dibandingkan

  • Dua nilai nil ​​dari jenis yang sama tidak boleh dibandingkan

  • nil ialah nilai sifar bagi jenis rujukan biasa

Percayalah Pengaturcara yang telah menulis Golang sangat biasa dengan sekeping kod berikut:

if err != nil {
    // do something....
}
Salin selepas log masuk

Apabila ia tidak sama dengan nil, ini bermakna beberapa ralat telah berlaku, dan kita perlu lakukan sesuatu tentang ralat ini, dan jika sama dengan nil, ini bermakna operasi itu normal. Jadi apakah itu nil? Semak kamus dan anda akan tahu bahawa nil tidak bermakna apa-apa, atau nilai sifar. Nilai sifar, nilai sifar, adakah ia terdengar biasa? Dalam bahasa Go, jika anda mengisytiharkan pembolehubah tetapi tidak memberikan nilai kepadanya, pembolehubah tersebut akan mempunyai nilai lalai sifar. Ini ialah nilai sifar yang sepadan untuk setiap jenis:

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil
Salin selepas log masuk

Sebagai contoh, apabila anda mentakrifkan struct:

type Person struct {
  AgeYears int
  Name string
  Friends []Person
}

var p Person // Person{0, "", nil}
Salin selepas log masuk

Pembolehubah p hanya diisytiharkan tetapi tidak ditugaskan, jadi Semua medan p mempunyai nilai sifar yang sepadan. Jadi, apakah sebenarnya ini nil? Dokumentasi Go mengatakan bahawa nil ialah pengecam yang dipratentukan, mewakili nilai sifar penunjuk, saluran, fungsi, antara muka, pemetaan atau hirisan, yang merupakan pembolehubah yang dipratentukan:

type Type int
var nil Type
Salin selepas log masuk

Adakah anda sedikit terkejut? nil bukan salah satu kata kunci Go Anda malah boleh menukar nilai nil sendiri:

var nil = errors.New("hi")
Salin selepas log masuk

Ini boleh dikompilasi sepenuhnya, tetapi sebaiknya jangan lakukannya seperti ini.

Apakah kegunaan nil

Setelah memahami apa itu nil, mari kita bercakap tentang penggunaan nil.

penunjuk

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference
Salin selepas log masuk

Penunjuk mewakili alamat yang menghala ke ingatan Menyahrujuk penuding nol akan menyebabkan panik. Jadi apakah kegunaan penunjuk untuk nil? Mari kita lihat contoh pengiraan jumlah pokok binari:

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
}
Salin selepas log masuk

Kod di atas mempunyai dua masalah Satu ialah pertindihan kod:

if v != nil {
  v.m()
}
Salin selepas log masuk

Yang lain ialah apabila <🎜. > adakah t akan panik: nil

var t *tree
sum := t.Sum()   // panic: invalid memory address or nil pointer dereference
Salin selepas log masuk
Bagaimana untuk menyelesaikan masalah di atas? Mari kita lihat contoh penerima penunjuk:

type person struct {}
func sayHi(p *person) { fmt.Println("hi") }
func (p *person) sayHi() { fmt.Println("hi") }
var p *person
p.sayHi() // hi
Salin selepas log masuk
Untuk kaedah objek penunjuk, ia boleh dipanggil walaupun nilai penunjuk adalah

Berdasarkan ini, kita boleh hanya mengira Mari kita ubah suai contoh jumlah pokok binari: nil

func(t *tree) Sum() int {
  if t == nil {
    return 0
  }
  return t.v + t.l.Sum() + t.r.Sum()
}
Salin selepas log masuk
Berbanding dengan kod tadi, adakah ia lebih mudah? Untuk penunjuk

, anda hanya perlu menilai sebelum kaedah dan ia akan menjadi ok. Mencetak nilai pokok binari atau mencari nilai tertentu dalam pokok binari adalah sama: nil

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)
}
Salin selepas log masuk
Jadi jika tidak perlu, jangan gunakan NewX() untuk memulakan nilai, tetapi gunakan nilai lalai mereka.

hirisan

// nil slices
var s []slice
len(s)  // 0
cap(s)  // 0
for range s  // iterates zero times
s[i]  // panic: index out of range
Salin selepas log masuk
Sepotong

, kecuali ia tidak boleh diindeks, semua operasi lain boleh dilakukan Apabila Anda boleh menggunakan fungsi nil apabila anda perlu mengisi nilai, dan kepingan akan dikembangkan secara automatik. Jadi apakah struktur asas kepingan append? Menurut dokumentasi rasmi, slice mempunyai tiga elemen, iaitu panjang, kapasiti, dan penunjuk kepada tatasusunan: nil

Apa itu nil dalam bahasa go

Apabila terdapat elemen:

Apa itu nil dalam bahasa go

所以我们并不需要担心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
Salin selepas log masuk

对于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
}
Salin selepas log masuk

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

NewGet("http://google.com", map[string]string{
  "USER_AGENT": "golang/gopher",
},)
Salin selepas log masuk

或者这样传:

NewGet("http://google.com", map[string]string{})
Salin selepas log masuk

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

NewGet("http://google.com", nil)
Salin selepas log masuk

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

channel

// nil channels
var c chan t
<- c      // blocks forever
c <- x    // blocks forever
close(c)  // panic: close of nil channel
Salin selepas log masuk

关闭一个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
    }
  }
}
Salin selepas log masuk

如果在外部调用中关闭了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)
}
Salin selepas log masuk

在知道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)
}
Salin selepas log masuk

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

func do() error {
  return nil
}
Salin selepas log masuk

再来看看这个例子:

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
}
Salin selepas log masuk

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

总结

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

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

Atas ialah kandungan terperinci Apa itu nil dalam bahasa go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!