皆さんこんにちは、asong
です。今日は Go 言語のゼロ値についてお話します。私は大学時代は C
言語の愛好家でしたが、就職後、Go
言語が C
言語に非常に似ていると感じたので、# を選択しました。 ##Go 言語。仕事上、時々 2 つの言語のいくつかの機能を比較します。今日はゼロ値の機能を比較したいと思います。
C 言語に詳しい友人は、
C 言語ではローカル変数がデフォルトでは初期化されないことを知っています。初期化されていない変数には任意の値を含めることができ、それらを使用すると未定義の動作が発生します。ローカル変数を初期化しないと、コンパイル時に警告 C4700 が報告されます。この警告は、
バグ、この
バグを示します。 は、プログラムで予期しない結果や誤動作を引き起こす可能性があります。Go 言語ではそのような問題は発生しません。Go 言語の設計者は <code style='font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);'>C
言語の設計である程度の経験を積んでいるため、Go
言語のゼロ値仕様は次のようになります。 :
次の内容は公式ブログからのものです: https://golang.org/ref/spec#The_zero_value
宣言または新しい呼び出しを通じて変数に記憶域スペースを割り当てる場合、複合リテラルを介して、または make 呼び出しで新しい値が作成され、明示的な初期化が提供されない場合、変数または値にはデフォルト値が与えられます。 このタイプの変数または値の各要素は、そのタイプのゼロ値に設定されます: ブール型の場合は false、数値型の場合は 0、文字列型、ポインター、関数の場合は "" , インターフェイス、スライス、チャネル、マップはありません。 この初期化は再帰的に行われます。つまり、値が指定されていない場合、構造体配列の各要素のフィールドはゼロにクリアされます。
たとえば、次の 2 つの単純な宣言は同等です:
var i int var i int = 0
またはこの構造体の宣言:
type T struct { i int; f float64; next *T } t := new(T)
This Structuret のゼロ値
のメンバー フィールドは次のとおりです。
t.i == 0 t.f == 0.0 t.next == nil
Go
値を常に既知のデフォルト値に設定する言語のこの機能は、コードの安全性と正確性に大きな役割を果たします。この重要な役割により、Go
プログラム全体がよりシンプルかつコンパクトになります。
我们在看一些Go
语言库的时候,都会看到在初始化对象时采用"动态初始化"的模式,其实就是在创建对象时判断如果是零值就使用默认值,比如我们在分析hystrix-go
这个库时,在配置Command
时就是使用的这种方式:
func ConfigureCommand(name string, config CommandConfig) { settingsMutex.Lock() defer settingsMutex.Unlock() timeout := DefaultTimeout if config.Timeout != 0 { timeout = config.Timeout } max := DefaultMaxConcurrent if config.MaxConcurrentRequests != 0 { max = config.MaxConcurrentRequests } volume := DefaultVolumeThreshold if config.RequestVolumeThreshold != 0 { volume = config.RequestVolumeThreshold } sleep := DefaultSleepWindow if config.SleepWindow != 0 { sleep = config.SleepWindow } errorPercent := DefaultErrorPercentThreshold if config.ErrorPercentThreshold != 0 { errorPercent = config.ErrorPercentThreshold } circuitSettings[name] = &Settings{ Timeout: time.Duration(timeout) * time.Millisecond, MaxConcurrentRequests: max, RequestVolumeThreshold: uint64(volume), SleepWindow: time.Duration(sleep) * time.Millisecond, ErrorPercentThreshold: errorPercent, } }
通过零值判断进行默认值赋值,增强了Go
程序的健壮性。
为什么叫开箱即用呢?因为Go
语言的零值让程序变得更简单了,有些场景我们不需要显示初始化就可以直接用,举几个例子:
nil
,即使不用make
进行初始化也是可以直接使用的,例如:package main import ( "fmt" "strings" ) func main() { var s []string s = append(s, "asong") s = append(s, "真帅") fmt.Println(strings.Join(s, " ")) }
但是零值也并不是万能的,零值切片不能直接进行赋值操作:
var s []string s[0] = "asong真帅"
这样的程序就报错了。
利用零值可用的特性,我们配合空结构体的方法接受者特性,可以将方法组合起来,在业务代码中便于后续扩展和维护:
type T struct{} func (t *T) Run() { fmt.Println("we run") } func main() { var t T t.Run() }
我在一些开源项目中看到很多地方都这样使用了,这样的代码最结构化~。
我们经常使用sync
包中的mutex
、once
、waitgroup
都是无需显示初始化即可使用,拿mutex
包来举例说明,我们看到mutex
的结构如下:
type Mutex struct { state int32 sema uint32 }
这两个字段在未显示初始化时默认零值都是0
,所以我们就看到上锁代码就针对这个特性来写的:
func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } // Slow path (outlined so that the fast path can be inlined) m.lockSlow() }
原子操作交换时使用的old
值就是0
,这种设计让mutex
调用者无需考虑对mutex
的初始化则可以直接使用。
还有一些其他标准库也使用零值可用的特性,使用方法都一样,就不在举例了。
Go
语言零值的设计大大便利了开发者,但是零值并不是万能的,有些场景下零值是不可以直接使用的:
var s []string s[0] = "asong" var m map[string]bool m["asong"] = true
这两种写法都是错误的使用。
零值的指针就是指向nil
的指针,无法直接进行运算,因为是没有无内容的地址:
var p *uint32 *p++ // panic: panic: runtime error: invalid memory address or nil pointer dereference
这样才可以:
func main() { var p *uint64 a := uint64(0) p = &a *p++ fmt.Println(*p) // 1 }
error内置接口类型是表示错误条件的常规接口,nil值表示没有错误,所以调用Error
方法时类型error
不能是零值,否则会引发panic
:
func main() { rs := res() fmt.Println(rs.Error()) } func res() error { return nil } panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6f27]
在日常开发中我们会使用到闭包,但是这其中隐藏一个问题,如果我们函数忘记初始化了,那么就会引发panic
:
var f func(a,b,c int) func main(){ f(1,2,3) // panic: runtime error: invalid memory address or nil pointer dereference }
我们都知道channels
的默认值是nil
,给定一个nil channel c
:
<-c
c
からの受信は永久にブロックされます c <- v
c
に値を送信すると永久にブロックされます close(c)
c
を閉じると パニックが発生します
まず、ゼロ値が利用できないシナリオを紹介します。これらをマスターすることによってのみ、日々の開発で bug
を記述する頻度を減らすことができます。
この記事で説明されているいくつかの知識ポイントを要約します:
言語内のすべての変数または値にはデフォルト値があり、これはプログラムの安全性と正確性において非常に重要な役割を果たします
以上がGo 言語のゼロ値について話しましょう。その用途は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。