Go言語のnilとは何ですか

青灯夜游
リリース: 2023-01-11 10:45:27
オリジナル
2988 人が閲覧しました

nil は Go 言語の事前定義された識別子で、null またはゼロの値を表します。 Go 言語では、Boolean 型のゼロ値 (初期値) は false、数値型のゼロ値は 0、文字列型のゼロ値は空文字列 "''"、ポインタ、スラ​​イスです。 、マップ、チャネル、関数 インターフェイスのゼロ値は nil です。 nil はキーワードや予約語ではありません。異なる型の nil へのポインタは同じです。異なる型の nil は比較できません。また、同じ型の nil 値は比較できません。

Go言語のnilとは何ですか

このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。

nil とは

#Go 言語では、Boolean 型のゼロ値 (初期値) は false、数値型のゼロ値は 0、および文字列型のゼロ値。ゼロ値は空の文字列 "" で、ポインタ、スラ​​イス、マップ、チャネル、関数、およびインターフェイスのゼロ値は nil です。

nil は Go 言語で定義済みの識別子です。他のプログラミング言語の開発経験のある開発者は、他の言語では nil を null (NULL) とみなす可能性があります。実際、これは完全に正しいわけではありません。 Go 言語の nil と他の言語の null の間には多くの違いがあるためです。

#Go 言語の nil 機能

    nil 識別子は比較できません
  • nil は重要ではありませんまたは予約語
  • 異なる型の nil のポインタは同じです
  • 異なる型の nil のポインタは比較できません
  • 同じ型の 2 つの nil 値は比較できません
  • nil は共通の参照型のゼロ値です
  • 私はそれを信じています。Golang を書いたことのあるプログラマは、次のコード部分によく精通しています:
if err != nil {
    // do something....
}
ログイン後にコピー

これが

nil

と等しくない場合、それは何らかのエラーを意味します。が発生したため、このエラーを修正する必要があります。何らかの処理を実行し、それが nil に等しい場合、動作は正常であることを意味します。では、nil とは何でしょうか?辞書を調べれば、nil は何も意味しない、またはゼロ値を意味することがわかります。ゼロ値、ゼロ値、聞き覚えがあるでしょうか? Go 言語では、変数を宣言しても値を代入しない場合、変数のデフォルト値はゼロになります。これは、各型に対応するゼロ値です: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">bool -&gt; false numbers -&gt; 0 string -&gt; &quot;&quot; pointers -&gt; nil slices -&gt; nil maps -&gt; nil channels -&gt; nil functions -&gt; nil interfaces -&gt; nil</pre><div class="contentsignin">ログイン後にコピー</div></div>たとえば、構造体を定義する場合:

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

var p Person // Person{0, "", nil}
ログイン後にコピー

変数

p

は宣言されるだけで割り当てられません。 p のすべてのフィールドには、対応するゼロ値があります。では、この nil とは一体何でしょうか? Go のドキュメントには、nil はポインタ、チャネル、関数、インターフェイス、マッピング、またはスライス のゼロ値を表す事前定義された識別子であり、事前定義された変数であると記載されています。少し驚いた? nil は Go のキーワードの 1 つではありません。

nil

の値を自分で変更することもできます: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">type Type int var nil Type</pre><div class="contentsignin">ログイン後にコピー</div></div> これは完全にコンパイル可能ですが、最終的にはその方が良いですこのようにしないでください。

nil の用途について

nil とは何かを理解した後、

nil

の使用方法について説明します。 pointers

var nil = errors.New("hi")
ログイン後にコピー
ポインタはメモリを指すアドレスを表し、nil ポインタを逆参照するとパニックが発生します。では、nil であるポインターは何に役立つのでしょうか?まず、バイナリ ツリーの合計を計算する例を見てみましょう:

var p *int
p == nil    // true
*p          // panic: invalid memory address or nil pointer dereference
ログイン後にコピー

上記のコードには 2 つの問題があります。1 つはコードの重複です: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">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 }</pre><div class="contentsignin">ログイン後にコピー</div></div>、もう 1 つは

t# の場合です。 ## は

nil

でパニックになります:

if v != nil {
  v.m()
}
ログイン後にコピー
上記の問題を解決するにはどうすればよいですか?まず、ポインタ レシーバの例を見てみましょう:
var t *tree
sum := t.Sum()   // panic: invalid memory address or nil pointer dereference
ログイン後にコピー
ポインタ オブジェクトのメソッドについては、ポインタの値が nil

であっても呼び出すことができます。先ほどの二分木の合計を計算する例を変更してみましょう:

type person struct {}
func sayHi(p *person) { fmt.Println("hi") }
func (p *person) sayHi() { fmt.Println("hi") }
var p *person
p.sayHi() // hi
ログイン後にコピー

先ほどのコードと比べて、はるかに簡単になっていますか?

nil ポインタの場合は、メソッドの前に判定するだけでOKで、何度も判定する必要はありません。バイナリ ツリーの値を出力する場合や、バイナリ ツリー内で特定の値を検索する場合も同様です。

func(t *tree) Sum() int {
  if t == nil {
    return 0
  }
  return t.v + t.l.Sum() + t.r.Sum()
}
ログイン後にコピー

したがって、必要がない場合は、値の初期化に NewX() を使用せず、次のメソッドを使用します。それらのデフォルト値。

slices

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)
}
ログイン後にコピー
nil のスライス。インデックス付けできないことを除いて、他の操作は可能です。値を入力する必要がある場合は、append

関数を使用すると、スライスが自動的に拡張されます。では、

nil であるスライスの基礎となる構造は何でしょうか?公式ドキュメントによると、スライスには長さ、容量、配列へのポインタの 3 つの要素があります。 #要素がある場合:

Go言語のnilとは何ですか

所以我们并不需要担心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)
}
ログイン後にコピー

输出结果是falsedo函数声明了一个*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还有这么多用处,真是意外之喜。
油管上面还有很多干货满满的视频,可以多学习学习咯。

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

以上がGo言語のnilとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート