rilevamento zero in Go


165

Vedo molto codice in Vai per rilevare zero, in questo modo:

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

tuttavia, ho una struttura come questa:

type Config struct {
    host string  
    port float64
}

e config è un'istanza di Config, quando faccio:

if config == nil {
}

c'è un errore di compilazione, che dice: impossibile convertire zero per digitare Config


3
Non capisco perché la porta sia di tipo float64?
alamin,

2
Non dovrebbe essere. L'API JSON di Go importa qualsiasi numero da JSON a float64, devo convertire float64 in int.
Qian Chen,

Risposte:


179

Il compilatore sta indicando l'errore, stai confrontando un'istanza di struttura e zero. Non sono dello stesso tipo, quindi lo considera un confronto non valido e ti urla.

Quello che vuoi fare qui è confrontare un puntatore all'istanza di configurazione con zero, che è un confronto valido. Per fare ciò puoi usare il nuovo Golang incorporato o inizializzare un puntatore ad esso:

config := new(Config) // not nil

o

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

o

var config *Config // nil

Quindi sarai in grado di verificare se

if config == nil {
    // then
}

5
Immagino che var config &Config // nildovrebbe essere:var config *Config
Tomasz Plonka l'

var config *Configsi blocca con invalid memory address or nil pointer dereference. Forse abbiamo bisognovar config Config
Kachar,

Capisco che il ragionamento alla base di questa scelta potrebbe non essere tuo, ma per me non ha senso che "if! (Config! = Nil)" sia valido ma che "if config == nil" non lo sia. Entrambi stanno facendo un confronto tra la stessa struttura e non struttura.
Retorquere

@retorquere sono entrambi non validi, vedi play.golang.org/p/k2EmRcels6 . Che si tratti di "! =" O "==" non fa alcuna differenza; ciò che fa la differenza è se config è una struttura o un puntatore a struttura.
Stewbasic,

Penso che sia sbagliato, dato che è sempre falso: play.golang.org/p/g-MdbEbnyNx
Madeo

61

Oltre a Oleiade, vedere le specifiche sui valori zero :

Quando la memoria viene allocata per memorizzare un valore, tramite una dichiarazione o una chiamata di make o new, e non viene fornita l'inizializzazione esplicita, alla memoria viene fornita un'inizializzazione predefinita. Ogni elemento di tale valore è impostato sul valore zero per il suo tipo: false per booleani, 0 per numeri interi, 0,0 per float, "" per stringhe e zero per puntatori, funzioni, interfacce, sezioni, canali e mappe. Questa inizializzazione viene eseguita in modo ricorsivo, quindi ad esempio ogni elemento di una matrice di strutture avrà i suoi campi azzerati se non viene specificato alcun valore.

Come puoi vedere, nilnon è il valore zero per ogni tipo, ma solo per puntatori, funzioni, interfacce, sezioni, canali e mappe. Questo è il motivo per cui config == nilè un errore e &config == nilnon lo è.

Per verificare se lo struct è inizializzato che ci si controlla ogni membro per il suo rispettivo valore pari a zero (per esempio host == "", port == 0e così via) o di avere un campo privato che si trova con un metodo di inizializzazione interna. Esempio:

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
Oltre a quanto sopra, ecco perché time.Timeha un IsZero()metodo. Tuttavia si potrebbe anche fare var t1 time.Time; if t1 == time.Time{}e si potrebbe anche fare if config == Config{}per controllare tutto il campo per voi (uguaglianza struct è ben definito in Go). Tuttavia, ciò non è efficace se hai molti campi. E, forse, il valore zero è un valore sano e utilizzabile, quindi passarne uno non è speciale.
Dave C,

1
La funzione Inizializzata non funzionerà se si accede a Config come puntatore. Potrebbe essere cambiato infunc (c *Config) Initialized() bool { return !(c == nil) }
Sundar

@Sundar in questo caso potrebbe essere conveniente farlo in questo modo, quindi ho applicato la modifica. Tuttavia, normalmente non mi aspetto che la parte ricevente della chiamata del metodo controlli se è nulla, poiché questo dovrebbe essere il lavoro del chiamante.
nemo

16

Ho creato un codice di esempio che crea nuove variabili usando una varietà di modi a cui posso pensare. Sembra che i primi 3 modi creino valori e gli ultimi due creino riferimenti.

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)
}

che produce:

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

6

Puoi anche controllare come struct_var == (struct{}). Questo non ti consente di confrontare con zero ma controlla se è inizializzato o meno. Fai attenzione quando usi questo metodo. Se la tua struttura può avere valori zero per tutti i suoi campi, non avrai grandi momenti.

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

Le specifiche della lingua menzionano i comportamenti degli operatori di confronto:

operatori di confronto

In qualsiasi confronto, il primo operando deve essere assegnabile al tipo di secondo operando o viceversa.


cedibilità

Un valore x è assegnabile a una variabile di tipo T ("x è assegnabile a T") in uno di questi casi:

  • il tipo di x è identico a T.
  • i tipi V e T di x hanno identici tipi sottostanti e almeno uno di V o T non è un tipo denominato.
  • T è un tipo di interfaccia e x implementa T.
  • x è un valore di canale bidirezionale, T è un tipo di canale, i tipi di V V e T hanno tipi di elementi identici e almeno uno di V o T non è un tipo con nome.
  • x è l'identificatore predefinito nil e T è un puntatore, una funzione, una sezione, una mappa, un canale o un tipo di interfaccia.
  • x è una costante non tipizzata rappresentabile da un valore di tipo T.

1

In Go 1.13 e versioni successive, è possibile utilizzare il Value.IsZerometodo offerto nel reflectpacchetto.

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

Oltre ai tipi di base, funziona anche con Array, Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer e Struct. Vedi questo per riferimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.