Подробное объяснение интерфейса языка Go (1)

Go
Подробное объяснение интерфейса языка Go (1)

Это 19-я статья из серии "Изучаем язык Go".

что такое интерфейс

В некоторых объектно-ориентированных языках программирования, таких как Java, PHP и т. д., интерфейс определяет поведение объекта и указывает только то, что объект должен делать. Точная реализация поведения зависит от объекта.

В языке Go интерфейс — это набор методов, но он не содержит реализации методов, он абстрактен, а интерфейс не может содержать переменных. Говорят, что тип T реализует интерфейс, когда он предоставляет определения всех методов интерфейса. Интерфейс указывает, какие методы должен иметь тип, а тип решает, как реализовать эти методы.

объявление интерфейса

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

type Name interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

На самом деле определить интерфейс:

type Shape interface {
    Area() float32
}

Приведенный выше код определяет тип интерфейса Shape, который содержит метод Area(), не принимающий параметров и возвращающий float32. Любой тип T, который реализует метод Area(), мы говорим, что он реализует интерфейс Shape.

type Shape interface {
	Area() float32
}

func main() {
	var s Shape
	fmt.Println("value of s is", s)
	fmt.Printf("type of s is %T\n", s)
}

вывод:

value of s is <nil>
type of s is <nil>

В приведенном выше коде, поскольку интерфейс является типом, может быть создана переменная s типа Shape Вам интересно, почему тип s равен nil? Переходим к следующему разделу!

значение типа интерфейса

статическая и динамическая типизация

Тип переменной указывается в момент объявления и не может быть изменен, что называется статическим типом. Статический тип интерфейса — это сам интерфейс. Интерфейс не имеет статических значений, он указывает на динамические значения. Переменная типа интерфейса хранит значение типа, реализующего интерфейс. Значение является динамическим значением интерфейса, а тип, реализующий интерфейс, является динамическим типом интерфейса.

type Iname interface {
	Mname()
}

type St1 struct {}
func (St1) Mname() {}
type St2 struct {}
func (St2) Mname() {}

func main() {
	var i Iname = St1{}
	fmt.Printf("type is %T\n",i)
	fmt.Printf("value is %v\n",i)
	i = St2{}
	fmt.Printf("type is %T\n",i)
	fmt.Printf("value is %v\n",i)
}

вывод:

type is main.St1
value is {}
type is main.St2
value is {}

Статический тип переменной i — Iname и не может быть изменен. Динамический тип не фиксирован.После первого выделения динамический тип i — St1, после второго выделения динамический тип i — St2, а все динамические значения — пустые структуры.

Иногда динамическая типизация интерфейса называетсяконкретный тип, когда мы обращаемся к типу интерфейса, возвращается тип базового динамического значения.

нулевое значение интерфейса

Давайте посмотрим на пример:

type Iname interface {
	Mname()
}
type St struct {}
func (St) Mname() {}
func main() {
	var t *St
	if t == nil {
		fmt.Println("t is nil")
	} else {
		fmt.Println("t is not nil")
	}
	var i Iname = t
	fmt.Printf("%T\n", i)
	if i == nil {
		fmt.Println("i is nil")
	} else {
		fmt.Println("i is not nil")
	}
	fmt.Printf("i is nil pointer:%v",i == (*St)(nil))
}

вывод:

t is nil
*main.St
i is not nil
i is nil pointer:true

Разве не удивительно, что значение, которое мы присваиваем переменной i, равно нулю, хотя i не равно нулю. Давайте посмотрим, что происходит!

Динамические типы были упомянуты выше, динамические значения — это значения, которые фактически выделены. Помните это:Значение типа интерфейса равно нулю тогда и только тогда, когда и динамическое значение, и динамический тип равны нулю.. В приведенном выше коде после присвоения значения переменной i динамическое значение i равно нулю, но динамический тип — *St, а i — нулевой указатель, поэтому условие равенства не выполняется.

Взгляните на спецификацию языка Go:

 var x interface{}  // x is nil and has static type interface{}
 var v *T           // v has value nil, static type *T
 x = 42             // x has value 42 and dynamic type int
 x = v              // x has value (*T)(nil) and dynamic type *T

Изучив этот раздел, я полагаю, вы уже знаете, почему выходной тип s переменной типа Shape в предыдущем разделе равен nil, поскольку при объявлении var s Shape динамический тип s равен nil.

реализовать интерфейс

См. пример:

type Shape interface {
	Area() float32
}

type Rect struct {
	width  float32
	height float32
}

func (r Rect) Area() float32 {
	return r.width * r.height
}

func main() {
	var s Shape
	s = Rect{5.0, 4.0}
	r := Rect{5.0, 4.0}
	fmt.Printf("type of s is %T\n", s)
	fmt.Printf("value of s is %v\n", s)
	fmt.Println("area of rectange s", s.Area())
	fmt.Println("s == r is", s == r)
}

вывод:

type of s is main.Rect
value of s is {5 4}
area of rectange s 20
s == r is true

Приведенный выше код создает интерфейс Shape, структуру Rect и метод Area(). Так как Rect реализует все методы, определенные интерфейсом, хотя есть только один, Rect реализует интерфейс Shape.

В основной функции создается переменная s типа интерфейса со значением nil и инициализируется структурой типа Rect, которая допустима, поскольку структура Rect реализует интерфейс. После присваивания динамический тип s становится Rect, а динамическое значение — значением структуры {5.0, 4.0}.

можно использовать напрямую.Синтаксис вызывает метод Area(), поскольку конкретный тип s — Rect, а Rect реализует метод Area().

пустой интерфейс

Интерфейс, который не содержит никаких методов, называется пустым интерфейсом, например: interface{}. Поскольку пустой интерфейс не содержит никаких методов, любой тип по умолчанию реализует пустой интерфейс.

Например, функция Println() в пакете fmt может принимать несколько типов значений, таких как: int, string, array и т. д. Почему, потому что его формальный параметр является интерфейсным типом, который может принимать значения любого типа.

func Println(a ...interface{}) (n int, err error) {}

Давайте посмотрим на пример:

type MyString string
type Rect struct {
	width  float32
	height float32
}
func explain(i interface{}) {
	fmt.Printf("type of s is %T\n", i)
	fmt.Printf("value of s is %v\n\n", i)
}
func main() {
	ms := MyString("Seekload")
	r := Rect{5.0, 4.0}
	explain(ms)
	explain(r)
}

вывод:

type of s is main.MyString
value of s is Seekload

type of s is main.Rect
value of s is {5 4}

Приведенный выше код создает пользовательский тип строки MyString , структуру Rect и функцию объяснения(). Формальный параметр функции объяснения() — это пустой интерфейс, поэтому он может принимать значения любого типа.

Это первая часть про использование интерфейса, и я открою статью, чтобы рассказать вам об остальном, продолжайте обращать внимание!


(Конец полного текста)

Оригинал статьи, если нужно перепечатать, указывайте источник!
Добро пожаловать, чтобы отсканировать код и подписаться на официальный аккаунт »Голанг здесь” или двигатьсяseekload.net, см. больше замечательных статей.

Публичная учетная запись «Голанг идет» подготовила для вас подарочный пакет для изучения тайны, и ответьте на [электронную книгу] в фоновом режиме, чтобы получить его!

公众号二维码