определение
-
Интерфейс представляет собой набор сигнатур методов, определенных для определения поведения объекта.Если объект достигает поведения, определенного методом, установленным в интерфейсе, можно сказать, что он реализует интерфейс;
-
Эти методы могут быть реализованы разными объектами в разных местах, и эти реализации могут иметь разное поведение;
-
Основная задача интерфейса — предоставить сигнатуры имен методов, входные параметры и возвращаемые типы. Наконец, метод реализуется конкретным объектом, например struct;
-
Значение инициализации интерфейса равно нулю;
-
Используйте ключевое слово type, чтобы объявить, что интерфейс представляет тип, а набор сигнатур методов интерфейса определяется в фигурных скобках.
type Animal interface { Bark() string Walk() string }
Таким образом, Dog реализует интерфейс Animal, поэтому экземпляр Animal можно использовать для получения экземпляра Dog.Он должен одновременно реализовывать методы Bark() и Walk(), иначе нельзя считать, что он реализует Animal. интерфейс.
type Dog struct { name string } func (dog Dog) Bark() string { fmt.Println(dog.name + ":wan wan wan!") return "wan wan wan" } func (dog Dog) Walk() string { fmt.Println(dog.name + ":walk to park!") return "walk to park" } func main() { var animal Animal fmt.Println("animal value is:", animal) //animal value is: <nil> fmt.Printf("animal type is: %T\n", animal) //animal type is: <nil> animal = Dog{"旺财"} animal.Bark() //旺财:wan wan wan! animal.Walk() //旺财:walk to park! fmt.Println("animal value is:", animal) //animal value is: {旺财} fmt.Printf("animal type is: %T\n", animal) //animal type is: main.Dog }
nil interface
В приведенном выше примере мы печатаем животное, которое только что определили:
- значение равно нулю
- тип тоже ноль
Официальное определение: значения интерфейса с нулевыми базовыми значениями:
- Только объявите, что неназначенный интерфейс является нулевым интерфейсом, а значение и тип равны нулю.
- Пока присваивание выполнено, даже если значению присваивается нулевой тип, это больше не нулевой интерфейс.
type I interface {
Hello()
}
type S []int
func (i S) Hello() {
fmt.Println("hello")
}
func main() {
var i I
fmt.Printf("1:i Type:%T\n", i)
fmt.Printf("2:i Value:%v\n", i)
var s S
if s == nil {
fmt.Printf("3:s Value%v\n", s)
fmt.Printf("4:s Type is %T\n", s)
}
i = s
if i == nil {
fmt.Println("5:i is nil")
} else {
fmt.Printf("6:i Type:%T\n", i)
fmt.Printf("7:i Value:%v\n", i)
}
}
output:
1:i Type:<nil>
2:i Value:<nil>
3:s Value[]
4:s Type is main.S
6:i Type:main.S
7:i Value:[]
Из результата инициализированная переменная i является нулевым интерфейсом, когда переменной s со значением nil присваивается i, i больше не является нулевым интерфейсом.
Внимательные школьники найдут деталь, третью строчку вывода
3:s Value[]
Очевидно, что значение s равно nil, но вывод представляет собой []. Это связано с тем, что fmt использует отражение для определения содержимого печати. Поскольку тип s — срез, fmt представлен [].
empty interface
Go допускает интерфейсы без каких-либо методов, такой тип интерфейса называется пустым интерфейсом. Все типы реализуют пустой интерфейс, потому что любой тип реализует как минимум 0 методов.
Типичным сценарием приложения является метод Println пакета fmt, который поддерживает получение различных типов данных и вывод их на консоль, что является заслугой interface{}. Рассмотрим случай ниже:
func Print(i interface{}) {
fmt.Println(i)
}
func main() {
var i interface{}
i = "hello"
Print(i)
i = 100
Print(i)
i = 1.29
Print(i)
}
Тип параметра метода Print — interface{}, который можно получить, когда мы передаем string, int, float и другие типы.
Хотя interface{} может принимать параметры любого типа, может ли срез типа interface{} принимать любой тип слайса. Следующий код вызовет паническую ошибку,
var dataSlice []int = foo()
var interfaceSlice []interface{} = dataSlice
// cannot use dataSlice (type []int) as type []interface { } in assignment
По определенным причинам официальный сайт вики (GitHub.com/gowaves/go/me…) описан, и общий смысл в том, что есть две причины ошибки:
- []interface{} — это не интерфейс, это слайс, но элементы слайса — это интерфейсы
- Размер памяти типа []interface{} определяется во время компиляции (N*2), в то время как размер других типов слайсов равен N * sizeof(MyType), поэтому тип []MyType не выделяется быстро для [] ]интерфейс{}.
Определите, какой тип хранит переменная интерфейса
Интерфейс может быть реализован несколькими типами.Иногда нам нужно различать, какой тип значения хранит переменная интерфейса? Утверждения типа обеспечивают доступ к базовому конкретному значению значения интерфейса.
t := i.(T)
Этот оператор утверждает, что значение интерфейса i содержит конкретный тип T, и присваивает базовое значение T переменной t. Если значение, хранящееся в i, не относится к типу T, будет вызвана паническая ошибка. Чтобы избежать панических ошибок, вы можете выполнить проверку утверждений, выполнив следующие действия:
t, ok := i.(T)
Утверждение выполнено успешно, значение ok равно true, утверждение не выполнено, значение t является нулевым значением типа T, и панической ошибки не возникает.
func main() {
var i interface{}
i = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
i = 100
t, ok := i.(int)
fmt.Println(t, ok)
t2 := i.(string) //panic
fmt.Println(t2)
}
Type switch
Другой удобный способ определить конкретный тип переменной интерфейса — использовать оператор switch. Следующее:
func Print(i interface{}) {
switch i.(type) {
case string:
fmt.Printf("type is string,value is:%v\n", i.(string))
case float64:
fmt.Printf("type is float32,value is:%v\n", i.(float64))
case int:
fmt.Printf("type is int,value is:%v\n", i.(int))
}
}
func main() {
var i interface{}
i = "hello"
Print(i)
i = 100
Print(i)
i = 1.29
Print(i)
}
Гибкий и эффективный динамический тип интерфейса позволяет языку Go гибко и безопасно преобразовывать различные совместимые типы, сохраняя при этом безопасность и эффективность сильных статических типов.