виявлення нуля в Go


165

Я бачу дуже багато коду в Go для виявлення нуля, як-от так:

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

однак у мене є така структура:

type Config struct {
    host string  
    port float64
}

і config - це примірник Config, коли я роблю:

if config == nil {
}

є помилка компіляції, кажучи: не вдається перетворити нуль у тип Config


3
Я не розумію, чому порт типу float64?
аламін

2
Це не повинно бути. Перехід JSON api імпортує будь-яке число з JSON в float64, я повинен перетворити float64 в int.
Qian Chen

Відповіді:


179

Компілятор вказує на помилку, ви порівнюєте екземпляр структури та нуль. Вони не одного типу, тому він вважає це невірним порівнянням і кричить на вас.

Що ви хочете зробити тут, це порівняти покажчик з вашим екземпляром конфігурації до нуля, що є правильним порівнянням. Для цього ви можете або використовувати новий вбудований голанг , або ініціалізувати вказівник на нього:

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
Томаш Плонка

var config *Configзбої з invalid memory address or nil pointer dereference. Можливо, нам потрібноvar config Config
kachar

Я розумію, що міркування цього вибору можуть бути не вашими, але для мене немає сенсу, що "if! (Config! = Nil)" є дійсним, але "if config == nil" - це не так. Обидва роблять порівняння між однією і тією ж структурою та неструктурою.
retorquere

@retorquere обидва вони недійсні, див. play.golang.org/p/k2EmRcels6 . Незалежно від того, що це "! =" Або "==", це не має значення; що має значення - це те, чи конфігурація - це структура, або вказівник на структуру?
тушканчик

Я думаю , що це не так, як це завжди брехня: play.golang.org/p/g-MdbEbnyNx
Madeo

61

Окрім Oleiade, див. Специфікацію щодо нульових значень :

Коли пам'ять виділяється для зберігання значення або через декларацію, або за викликом make або new, і явна ініціалізація не передбачена, пам'яті надається ініціалізація за замовчуванням. Кожному елементу такого значення встановлено нульове значення для його типу: false для булевих значень, 0 для цілих чисел, 0,0 для плавців, "" для рядків та нуль для покажчиків, функцій, інтерфейсів, фрагментів, каналів та карт. Ця ініціалізація проводиться рекурсивно, тому, наприклад, у кожного елемента масиву структур буде поле нульових, якщо значення не вказане.

Як бачите, 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{}щоб перевірити всі поля для вас (структура рівності коректно визначено в Go). Однак це не ефективно, якщо у вас багато полів. І, можливо, нульове значення є здоровим і корисним значенням, тому передача одного в нього не є особливим.
Дейв C

1
Функція ініціалізації не вдасться, якщо отримати доступ до Config як покажчика. Це можна змінити наfunc (c *Config) Initialized() bool { return !(c == nil) }
Сундар

@Sundar в цьому випадку це може бути зручно зробити так, тому я застосував зміни. Однак, як правило, я не очікував, що отриманий кінець виклику методу перевіряє, чи не є його нульовим, оскільки це має бути завданням абонента.
немо

16

Я створив зразок коду, який створює нові змінні, використовуючи різні способи, про які я можу придумати. Схоже, перші три способи створюють значення, а останні два створюють посилання.

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{}). Це не дозволяє вам порівнювати з нулем, але він перевіряє, чи він ініціалізований чи ні. Будьте обережні, використовуючи цей метод. Якщо ваша структура може мати нульові значення для всіх її полів, ви не матимете великого часу.

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

У мовній специфікації згадується поведінка операторів порівняння:

оператори порівняння

У будь-якому порівнянні перший операнд повинен бути віднесений до типу другого операнда, або навпаки.


Призначення

Значення x призначається змінній типу T ("x може бути присвоєно T") у будь-якому з цих випадків:

  • тип x ідентичний T.
  • x-тип V і T мають однакові базові типи, і принаймні один з V або T не є іменованим типом.
  • T - тип інтерфейсу і x реалізує T.
  • x - двонаправлене значення каналу, T - тип каналу, x-тип 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. Дивіться це для довідки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.