непонимание с интерфейсом golang [дубликат]

Большинство ответов здесь затрагивают этот вопрос в очень сухих технических терминах. Я хотел бы остановиться на этом в терминах, которые могут понять обычные люди.

Представьте, что вы пытаетесь нарезать пиццу. У вас есть роботизированный резак для пиццы, который может разрезать кусочки пиццы ровно пополам. Он может вдвое сократить целую пиццу, или он может сократить вдвое существующий кусочек, но в любом случае половина всегда точна.

У этого резца пиццы очень хорошие движения, и если вы начнете с целой пиццы, затем уменьшите вдвое и продолжайте вдвое уменьшать наименьший срез каждый раз, вы можете сделать половину 53 раза , прежде чем срез слишком мал для даже его высокоточных способностей. В этот момент вы уже не можете вдвое уменьшить этот тонкий срез, но должны либо включать, либо исключать его, как есть.

Теперь, как бы вы отделили все срезы таким образом, чтобы добавить один (0,1) или одну пятую (0,2) пиццы? На самом деле подумайте об этом и попробуйте разобраться. Вы даже можете попытаться использовать настоящую пиццу, если у вас есть мифическая пресса для резки пиццы под рукой. : -)


Большинство опытных программистов, конечно же, знают реальный ответ, который заключается в том, что нет возможности собрать кусок точной десятой или пятой пиццы используя эти срезы, независимо от того, насколько мелко вы их нарезаете. Вы можете сделать довольно хорошее приближение, и если вы добавите аппроксимацию 0,1 с аппроксимацией 0,2, вы получите довольно хорошее приближение 0,3, но это все равно именно это, приближение.

Для двойного -оценки (это точность, которая позволяет вам вдвое сократить вашу пиццу 53 раза), цифры сразу меньше и больше 0,1 - 0.09999999999999999167332731531132594682276248931884765625 и 0,1000000000000000055511151231257827021181583404541015625. Последнее немного ближе к 0,1, чем первое, поэтому числовой синтаксический анализатор, учитывая ввод 0,1, благоприятствует последнему.

(Разница между этими двумя числами - это «самый маленький срез», который мы должны решить либо включить, что вводит восходящее смещение, либо исключить, что приводит к смещению вниз. Техническим термином для этого наименьшего среза является ulp .)

В случай 0,2, числа все одинаковы, просто увеличиваются в 2 раза. Опять же, мы одобряем значение, которое немного выше 0,2.

Обратите внимание, что в обоих случаях приближения для 0,1 и 0.2 имеют небольшое смещение вверх. Если мы добавим достаточно этих предубеждений, они будут толкать число дальше и дальше от того, что мы хотим, а на самом деле, в случае 0,1 + 0,2, смещение достаточно велико, чтобы получившееся число больше не было самым близким числом до 0,3.

в частности, 0,1 + 0,2 действительно 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, тогда как число ближе к 0,3 фактически 0,299999999999999988897769753748434595763683319091796875.

П.С. Некоторые языки программирования также предоставляют резаки для пиццы, которые могут разделять фрагменты на точные десятки . Хотя такие резаки для пиццы необычны, если у вас есть доступ к одному, вы должны использовать его, когда важно получить ровно одну десятую или одну пятую части среза.

( Первоначально опубликовано на Quora.)

ч>

30
задан icza 16 February 2016 в 23:50
поделиться

2 ответа

Проблема здесь в том, что shower - это тип interface. Типы интерфейсов в Go удерживают фактическое значение и его динамический тип. Подробнее об этом: Законы отражения # Представление интерфейса .

Выбранный фрагмент содержит 2 значения не nil. Второе значение представляет собой значение интерфейса, пару (значение; тип), содержащее значение nil и тип *display. Цитирование из спецификации языка g1] Go: Операторы сравнения :

Значения интерфейса сопоставимы. Два значения интерфейса равны, если они имеют одинаковые динамические типы и равные динамические значения или оба имеют значение nil.

Поэтому, если вы сравните его с nil, это будет false. Если вы сравните его со значением интерфейса, представляющим пару (nil;*display), это будет true:

if x == (*display)(nil) {
    panic("everything ok, nil found")
}

Это кажется неосуществимым, так как вам нужно знать, какой тип интерфейса имеет.

Почему это реализовано таким образом?

Интерфейсы, в отличие от других конкретных типов (не-интерфейсы), могут содержать значения разных типов конкретных типов (разные статические типы). Время выполнения должно знать динамический или runtime-тип значения, хранящегося в переменной типа интерфейса.

interface - это всего лишь набор методов, любой тип реализует его если одни и те же методы являются частью метода , заданного типа. Существуют типы, которые не могут быть nil, например struct или пользовательский тип с int в качестве базового типа. В этих случаях вам не нужно будет сохранять значение nil этого конкретного типа.

Но любой тип также включает конкретные типы, где nil является действительным (например, фрагменты, карты, каналы, все типы указателей), поэтому, чтобы сохранить значение во время выполнения, которое удовлетворяет интерфейсу, разумно поддерживать сохранение nil внутри интерфейса. Но помимо nil внутри интерфейса мы должны хранить его динамический тип, поскольку значение nil не несет такую ​​информацию. Альтернативным вариантом будет использование nil в качестве самого значения интерфейса, когда значение, которое будет храниться в нем, nil, но это решение недостаточно, так как оно потеряет информацию о динамическом типе.

Некоторые говорят, что интерфейсы Go являются динамически типизированными, но это вводит в заблуждение. Они статически типизированы: переменная типа интерфейса всегда имеет один и тот же статический тип, и хотя во время выполнения значение, хранящееся в переменной интерфейса, может изменять тип, это значение всегда будет удовлетворять интерфейсу.

В общем случае, если вы хотите указать nil для значения типа interface, используйте явное значение nil, а затем вы можете проверить равенство nil. Наиболее распространенным примером является встроенный тип error , который является интерфейсом с одним методом. Всякий раз, когда нет ошибки, вы явно устанавливаете или возвращаете значение nil, а не значение какой-либо конкретной (неинтерфейсной) ошибки типа типа (что было бы очень плохой практикой, см. Демонстрацию ниже).

В вашем примере путаница возникает из фактов, что:

  • вы хотите иметь значение в качестве типа интерфейса (shower)
  • , но значение, которое вы хотите хранить в срезе не тип shower, а конкретный тип

Поэтому, когда вы помещаете *display тип в срез shower, будет создано значение интерфейса, которое представляет собой пару (значение; тип), где значение равно nil, а тип - *display. Значение внутри пары будет nil, а не значением самого интерфейса. Если вы поместите значение nil в срез, то значение интерфейса себя будет nil, а условием x == nil будет true.

Демонстрация

См. этот пример: Игровая площадка

type MyErr string

func (m MyErr) Error() string {
    return "big fail"
}

func doSomething(i int) error {
    switch i {
    default:
        return nil // This is the trivial true case
    case 1:
        var p *MyErr
        return p // This will be false
    case 2:
        return (*MyErr)(nil) // Same as case 1
    case 3:
        var err error // Zero value is nil for the interface
        return err    // This will be true because err is already interface type
    case 4:
        var p *MyErr
        return error(p) // This will be false because the interface points to a
                        // nil item but is not nil itself.
    }
}

func main() {
    for i := 0; i <= 4; i++ {
        err := doSomething(i)
        fmt.Println(i, err, err == nil)
    }
}

Выход:

0 <nil> true
1 <nil> false
2 <nil> false
3 <nil> true
4 <nil> false

В случае 2 указатель nil но сначала он преобразуется в тип интерфейса (error), поэтому создается значение интерфейса, которое содержит значение nil и тип *MyErr, поэтому значение интерфейса не является nil.

44
ответ дан Howl 27 August 2018 в 04:42
поделиться

Давайте рассмотрим интерфейс как указатель.

Скажем, у вас есть указатель a, и он равен нулю, и ничего не указывает.

var a *int // nil

Тогда у вас есть указатель b, и он указывает на a.

var b **int
b = &a // not nil

Посмотрите, что произошло? b указывает на указатель, который указывает на ничего. Так что даже если это указатель nil в конце цепочки, b указывает на что-то - это не ноль.

Если вы заглянете в память процесса, это может выглядеть так: это:

address | name | value
1000000 | a    | 0
2000000 | b    | 1000000

Видите? a указывает на адрес 0 (это означает, что он nil), а b указывает на адрес a (1000000).

То же самое относится к интерфейсам (за исключением того, что они немного в памяти ).

Как и указатель, интерфейс, указывающий на указатель на nil, не будет нить.

Здесь см. себя , как это работает с указателями и , как это работает с интерфейсами .

4
ответ дан Moshe Revah 27 August 2018 в 04:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: