Большинство ответов здесь затрагивают этот вопрос в очень сухих технических терминах. Я хотел бы остановиться на этом в терминах, которые могут понять обычные люди.
Представьте, что вы пытаетесь нарезать пиццу. У вас есть роботизированный резак для пиццы, который может разрезать кусочки пиццы ровно пополам. Он может вдвое сократить целую пиццу, или он может сократить вдвое существующий кусочек, но в любом случае половина всегда точна.
У этого резца пиццы очень хорошие движения, и если вы начнете с целой пиццы, затем уменьшите вдвое и продолжайте вдвое уменьшать наименьший срез каждый раз, вы можете сделать половину 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.)
ч>
Проблема здесь в том, что shower
- это тип interface
. Типы интерфейсов в Go удерживают фактическое значение и его динамический тип. Подробнее об этом: Законы отражения # Представление интерфейса .
Выбранный фрагмент содержит 2 значения не nil
. Второе значение представляет собой значение интерфейса, пару (значение; тип), содержащее значение nil
и тип *display
. Цитирование из спецификации языка g1] Go: Операторы сравнения :
Значения интерфейса сопоставимы. Два значения интерфейса равны, если они имеют одинаковые динамические типы и равные динамические значения или оба имеют значение
blockquote>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 являются динамически типизированными, но это вводит в заблуждение. Они статически типизированы: переменная типа интерфейса всегда имеет один и тот же статический тип, и хотя во время выполнения значение, хранящееся в переменной интерфейса, может изменять тип, это значение всегда будет удовлетворять интерфейсу.
blockquote>В общем случае, если вы хотите указать
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
.
Давайте рассмотрим интерфейс как указатель.
Скажем, у вас есть указатель 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, не будет нить.
Здесь см. себя , как это работает с указателями и , как это работает с интерфейсами .