การตรวจจับไม่มีใน Go


165

ฉันเห็นรหัสจำนวนมากใน Go เพื่อตรวจจับไม่มีเช่นนี้

if err != nil { 
    // handle the error    
}

อย่างไรก็ตามฉันมีโครงสร้างเช่นนี้:

type Config struct {
    host string  
    port float64
}

และ config เป็นตัวอย่างของการกำหนดค่าเมื่อฉัน:

if config == nil {
}

มีข้อผิดพลาดในการรวบรวมว่า: ไม่สามารถแปลงศูนย์เพื่อพิมพ์ Config


3
ฉันไม่เข้าใจว่าทำไมพอร์ตถึงเป็นประเภท float64?
alamin

2
มันไม่ควรจะเป็น JSON ของ Go API นำเข้าหมายเลขใด ๆ จาก JSON เป็น float64 ฉันต้องแปลง float64 เป็น int
Qian Chen

คำตอบ:


179

คอมไพเลอร์ชี้ข้อผิดพลาดให้คุณคุณกำลังเปรียบเทียบอินสแตนซ์โครงสร้างและศูนย์ มันไม่ใช่ประเภทเดียวกันดังนั้นจึงถือว่าเป็นการเปรียบเทียบที่ไม่ถูกต้องและตะโกนใส่คุณ

สิ่งที่คุณต้องการทำที่นี่คือการเปรียบเทียบตัวชี้ไปยังอินสแตนซ์การตั้งค่าของคุณกับศูนย์ซึ่งเป็นการเปรียบเทียบที่ถูกต้อง ในการทำเช่นนั้นคุณสามารถใช้ golang new builtin หรือเริ่มต้นตัวชี้ไปที่มัน:

config := new(Config) // not nil

หรือ

config := &Config{
                  host: "myhost.com", 
                  port: 22,
                 } // not nil

หรือ

var config *Config // nil

จากนั้นคุณจะสามารถตรวจสอบได้

if config == nil {
    // then
}

5
ฉันเดาว่าvar config &Config // nilควรเป็น:var config *Config
Tomasz Plonka

var config *Configinvalid memory address or nil pointer dereferenceเกิดปัญหากับ บางทีเราต้องการvar config Config
kachar

ฉันเข้าใจว่าเหตุผลที่อยู่เบื้องหลังตัวเลือกนี้อาจไม่ใช่ของคุณ แต่ไม่มีเหตุผลสำหรับฉันที่ "if! (config! = nil)" ใช้ได้ แต่นั่น "ถ้า config == ไม่มี" ทั้งสองกำลังทำการเปรียบเทียบระหว่าง struct เดียวกันและที่ไม่ใช่โครงสร้าง
retorquere

@retorquere พวกเขาทั้งสองไม่ถูกต้องดูplay.golang.org/p/k2EmRcels6 ไม่ว่าจะเป็น '! =' หรือ '==' ไม่สร้างความแตกต่าง สิ่งที่สร้างความแตกต่างคือว่า config เป็น struct หรือตัวชี้ไปที่ struct
stewbasic

ฉันคิดว่ามันผิดเพราะเป็นเท็จเสมอ: play.golang.org/p/g-MdbEbnyNx
Madeo

61

นอกจาก Oleiade แล้วให้ดูที่spec ของค่าศูนย์ :

เมื่อมีการจัดสรรหน่วยความจำเพื่อเก็บค่าไม่ว่าจะเป็นการประกาศหรือการเรียกใช้การสร้างหรือการสร้างใหม่และไม่มีการกำหนดค่าเริ่มต้นอย่างชัดเจนหน่วยความจำจะได้รับการกำหนดค่าเริ่มต้น แต่ละองค์ประกอบของค่าดังกล่าวถูกตั้งค่าเป็นศูนย์ค่าสำหรับประเภท: false สำหรับ booleans, 0 สำหรับจำนวนเต็ม, 0.0 สำหรับโฟลต, "" สำหรับสตริงและไม่มีศูนย์สำหรับพอยน์เตอร์, ฟังก์ชัน, อินเทอร์เฟซ, ชิ้น, ช่องทางและแผนที่ การกำหนดค่าเริ่มต้นนี้จะเกิดขึ้นซ้ำ ๆ ดังนั้นเช่นแต่ละองค์ประกอบของอาร์เรย์ของ struct จะมีฟิลด์เป็นศูนย์ถ้าไม่มีการระบุค่า

อย่างที่คุณเห็นnilไม่ใช่ค่าศูนย์สำหรับทุกประเภท แต่สำหรับพอยน์เตอร์ฟังก์ชั่นอินเทอร์เฟซชิ้นส่วนช่องและแผนที่เท่านั้น นี่คือสาเหตุที่ทำให้config == nilเกิดข้อผิดพลาดและ &config == nilไม่ใช่

เพื่อตรวจสอบว่าโครงสร้างของคุณจะเตรียมคุณจะต้องตรวจสอบสมาชิกทุกคนสำหรับค่าเป็นศูนย์ตามลำดับ (เช่นhost == "", port == 0ฯลฯ ) หรือมีข้อมูลส่วนตัวซึ่งกำหนดโดยวิธีการเริ่มต้นภายใน ตัวอย่าง:

type Config struct {
    Host string  
    Port float64
    setup bool
}

func NewConfig(host string, port float64) *Config {
    return &Config{host, port, true}
}

func (c *Config) Initialized() bool { return c != nil && c.setup }

4
นอกเหนือจากข้างต้นนั่นคือเหตุผลที่time.TimeมีIsZero()วิธีการ อย่างไรก็ตามคุณสามารถทำได้var t1 time.Time; if t1 == time.Time{}และคุณยังสามารถทำการif config == Config{}ตรวจสอบฟิลด์ทั้งหมดให้คุณได้ด้วย อย่างไรก็ตามนั่นไม่ได้ผลถ้าคุณมีฟิลด์จำนวนมาก และค่าศูนย์อาจเป็นค่าที่มีประโยชน์และมีประโยชน์ดังนั้นการส่งผ่านค่าในไม่พิเศษ
เดฟ C

1
ฟังก์ชันการเตรียมใช้งานจะล้มเหลวหากเข้าถึงการกำหนดค่าในฐานะตัวชี้ สามารถเปลี่ยนเป็นfunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar

@Sundar ในกรณีนี้มันอาจจะสะดวกที่จะทำแบบนี้ดังนั้นฉันจึงใช้การเปลี่ยนแปลง อย่างไรก็ตามโดยปกติฉันจะไม่คาดหวังว่าการสิ้นสุดการรับเมธอดโทรเพื่อตรวจสอบว่าตัวเองเป็นศูนย์หรือไม่เนื่องจากนี่ควรเป็นงานของผู้โทร
nemo

16

ฉันได้สร้างโค้ดตัวอย่างซึ่งสร้างตัวแปรใหม่โดยใช้วิธีการที่หลากหลายที่ฉันสามารถคิดได้ ดูเหมือนว่า 3 วิธีแรกจะสร้างค่าและสองวิธีสุดท้ายสร้างการอ้างอิง

package main

import "fmt"

type Config struct {
    host string
    port float64
}

func main() {
    //value
    var c1 Config
    c2 := Config{}
    c3 := *new(Config)

    //reference
    c4 := &Config{}
    c5 := new(Config)

    fmt.Println(&c1 == nil)
    fmt.Println(&c2 == nil)
    fmt.Println(&c3 == nil)
    fmt.Println(c4 == nil)
    fmt.Println(c5 == nil)

    fmt.Println(c1, c2, c3, c4, c5)
}

ผลลัพธ์ใด:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}

6

struct_var == (struct{})นอกจากนี้คุณยังสามารถตรวจสอบเช่น สิ่งนี้ไม่อนุญาตให้คุณเปรียบเทียบกับศูนย์ แต่จะตรวจสอบว่ามีการเริ่มต้นหรือไม่ ระวังขณะใช้วิธีนี้ หาก struct ของคุณมีค่าเป็นศูนย์สำหรับเขตข้อมูลทั้งหมดคุณจะไม่มีเวลามาก

package main

import "fmt"

type A struct {
    Name string
}

func main() {
    a := A{"Hello"}
    var b A

    if a == (A{}) {
        fmt.Println("A is empty") // Does not print
    } 

    if b == (A{}) {
        fmt.Println("B is empty") // Prints
    } 
}

http://play.golang.org/p/RXcE06chxE


3

ข้อมูลจำเพาะภาษากล่าวถึงพฤติกรรมการดำเนินการเปรียบเทียบ:

ผู้ประกอบการเปรียบเทียบ

ในการเปรียบเทียบใด ๆ ตัวถูกดำเนินการแรกจะต้องกำหนดให้กับชนิดของตัวถูกดำเนินการที่สองหรือในทางกลับกัน


Assignability

ค่า x สามารถกำหนดให้กับตัวแปรประเภท T ("x สามารถกำหนดให้กับ T") ในกรณีเหล่านี้:

  • ชนิดของ x เหมือนกับ T
  • x ประเภท V และ T มีประเภทพื้นฐานที่เหมือนกันและอย่างน้อยหนึ่ง V หรือ T ไม่ใช่ประเภทที่มีชื่อ
  • T เป็นประเภทอินเตอร์เฟสและ x ใช้ T
  • x คือค่าช่องทางสองทิศทาง T คือประเภทของช่องสัญญาณ, ประเภทของ V และ T มีประเภทองค์ประกอบที่เหมือนกันและอย่างน้อยหนึ่งใน V หรือ T ไม่ใช่ประเภทที่มีชื่อ
  • x คือตัวระบุที่ประกาศไว้ก่อนแล้วและ T คือตัวชี้ฟังก์ชันสไลซ์แผนที่ช่องหรือประเภทอินเตอร์เฟส
  • x คือค่าคงตัวที่ไม่ได้พิมพ์แทนค่าประเภท T

0

ใน Go 1.13 และใหม่กว่าคุณสามารถใช้Value.IsZeroวิธีการที่เสนอในreflectแพ็คเกจ

if reflect.ValueOf(v).IsZero() {
    // v is zero, do something
}

นอกเหนือจากประเภทพื้นฐานแล้วยังสามารถใช้งานได้กับ Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer และ Struct ดูสิ่งนี้สำหรับการอ้างอิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.