Ir: métodos e interfaces

Método

Un método es una función con un argumento de receptor especial.

func (v Vertex) Abs() float64{
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
	// In this example, the `Abs` method has a receiver of type `Vertex` named `v`
}
// is equivalent to (in terms of functionality)
func Abs(v Vertex) float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// You can declare a method on non-struct types, too.
func (f MyFloat) Abs() float64 {
    
    
	if f < 0 {
    
    
		return float64(-f)
	}
	return float64(f)
}
// You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

Receptor de puntero

El receptor de un método puede ser un puntero. Esto significa que el tipo de receptor tiene la sintaxis literal *Tde algún tipo T. (Además, Tno puede ser un puntero como *int.)

type Vertex struct {
    
    
	X, Y float64
}
// golang pointer receiver method
func (v* Vertex) Scale(f float64) {
    
    
	v.X = v.X * f
	v.Y = v.Y * f // recall that v.X is the same as dereferencing (*v).X
}
// it can be written as, again, a function
func ScaleFunc(v* Vertex, f float64) {
    
    
	v.X = v.X * f
	v.Y = v.Y * f
}

Dado que los métodos a menudo necesitan modificar su receptor, los receptores de puntero son más comunes que los receptores de valor.

Método con receptor de puntero versus función con un argumento de puntero

Hay una diferencia entre el método con receptor de puntero y una función con argumento de puntero .

// func with ptr arg
var v Vertex
ScaleFunc(v, 5) // compile error
ScaleFunc(&v, 5) // works
// method with ptr receiver
var v Vertex
p := &v
p.Scale(5) // works
v.Scale(5) // also works; auto-referencing

Es decir, por conveniencia, Go interpreta la declaración v.Scale(5)como si (&v).Scale(5)el Scalemétodo tiene un receptor de puntero. Esto no funcionará para las funciones normales.

Lo equivalente sucede en sentido inverso. Lo mismo sucede cuando tienes un método con receptor de valor y una función con un argumento de valor .

type Vertex struct {
    
    
	X, Y float64
}
func (v Vertex) Abs() float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// func with val arg
var v Vertex
fmt.Println(AbsFunc(v))  // works
fmt.Println(AbsFunc(&v)) // compile error

// method with val receiver
var v Vertex
fmt.Println(v.Abs()) // works
p := &v
fmt.Println(p.Abs()) // also works; (*p).Abs(); auto-dereferencing

En este caso, la llamada al método p.Abs()se interpreta como (*p).Abs().

Elegir un valor o un receptor de puntero

Dos razones para utilizar un receptor de puntero:

  1. El método con un receptor de puntero puede modificar el valor al que apunta su receptor.
  2. Evite copiar el valor en cada llamada al método. Esto puede ser más eficiente si el receptor es una estructura grande.
type Vertex struct {
    
    
	X, Y float64
}
func (v *Vertex) Abs() float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

En este ejemplo, aunque el Absmétodo no necesita modificar su receptor, es con el tipo de receptor *Vertex.

En general, todos los métodos de un tipo determinado deben tener receptores de valor o de puntero, pero no una combinación de ambos.

Interfaces

Interfaces básicas

Un tipo de interfaz se define como un conjunto de firmas de métodos.
Un valor de tipo de interfaz puede contener cualquier valor que implemente esos métodos.

type Abser interface {
    
    
	Abs() float64
}
func main() {
    
    
	var a1 Abser // a value of Abs() interface type
	var a2 Abser // another value of Abs() interface type
	f := MyFloat(-1.414) // default constructor
	v := Vertex{
    
    3, 4}
	a1 = f // a MyFloat implements Abser
	a2 = &v // a *Vertex implements Abser
	a2 = v // invalid! Vertex (the value type) doesn't implement Abser because the Abs method is defined only on *Vertex (the pointer type).
	fmt.Println(a1.Abs())
	fmt.Println(a2.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
    
    
	if f < 0 {
    
    
		return float64(-f)
	}
	return float64(f)
}
type Vertex struct {
    
    
	X, Y float64
}
func (v *Vertex) Abs() float64 {
    
    
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

Las interfaces se implementan implícitamente

Un tipo implementa una interfaz implementando sus métodos. No hay una declaración explícita de intenciones, ni una palabra clave "implements".

Las interfaces implícitas desacoplan la definición de una interfaz de su implementación, que luego podría aparecer en cualquier paquete sin un arreglo previo.

Valores de interfaz

Bajo el capó, los valores de interfaz se pueden considerar como una tupla de un valor y un tipo concreto:
(value, type)
un valor de interfaz contiene un valor de un tipo concreto subyacente específico.

Llamar a un método en un valor de interfaz ejecuta el método del mismo nombre en su tipo subyacente.

Valores de interfaz con valores subyacentes nulos

type I interface {
    
    
	M()
}
type T struct {
    
    
	S string
}
func (t *T) M() {
    
    
	if t == nil {
    
    
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}
func main() {
    
    
	var i I
	var t *T
	i = t
	describe(i)
	i.M() // If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
	i = &T{
    
    "hello"}
	describe(i) // Note that an interface value that holds a nil concrete value is itself non-nil.
	i.M()
}
func describe(i I) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

En algunos lenguajes, esto desencadenaría una excepción de puntero nulo, pero en Go es común escribir métodos que manejen con gracia las llamadas con un receptor nulo (como con el método Men este ejemplo).

Un nilvalor de interfaz no tiene valor ni tipo concreto.
Llamar a un método en una interfaz nula es un error en tiempo de ejecución porque no hay ningún tipo dentro de la tupla de la interfaz para indicar a qué método concreto llamar.

type I interface {
    
    
	M()
}
func main() {
    
    
	var i I
	describe(i)
	i.M() // panic: runtime error: invalid memory address or nil pointer dereference, i is nil itself!
}
func describe(i I) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

La interfaz vacía

El tipo de interfaz que especifica métodos cero se conoce como interfaz vacía:
interface{}
una interfaz vacía puede contener valores de cualquier tipo. (Cada tipo implementa al menos cero métodos).

func main() {
    
    
	var i interface{
    
    }
	describe(i)
	
	i = 42
	describe(i)
	
	i = "hello"
	describe(i)
}
func describe(i interface{
    
    }) {
    
    
	fmt.Printf("(%v, %T)\n", i, i)
}

Las interfaces vacías son utilizadas por código que maneja valores de tipo desconocido. Por ejemplo, fmt.Printtoma cualquier número de argumentos de tipo interface{}.

Tipo de aserciones

Una aserción de tipo proporciona acceso al valor concreto subyacente de un valor de interfaz.

func main() {
    
    
	var i interface{
    
    } = "hello"
	s := i.(string) // type assertion; this statement asserts that the interface value i holds the concrete type string and assigns the underlying string value to the variable s.
	fmt.Println(s)
	s, ok := i.(string)
	fmt.Println(s, ok) // "hello" and true
	f, ok := i.(float64)
	fmt.Println(f, ok) // 0 and false
	f = i.(float64) // panic!
}

Tipo de interruptor

func do(i interface{
    
    }) {
    
    
	switch v := i.(type) {
    
    
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}
func main() {
    
    
	do(21)
	do("hello")
	do(true)
}

Largueros

El paquete Stringerdefine una de las interfaces más ubicuas fmt.

type Stringer interface {
    
    
    String() string
}

A Stringeres un tipo que puede describirse a sí mismo como una cadena. El fmtpaquete (y muchos otros) busca esta interfaz para imprimir valores.

type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
type Stringer interface {
    
    
    String() string
}
func (ip IPAddr) String() string {
    
    
	return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
func main() {
    
    
	hosts := map[string]IPAddr{
    
    
		"loopback":  {
    
    127, 0, 0, 1},
		"googleDNS": {
    
    8, 8, 8, 8},
	}
	for name, ip := range hosts {
    
    
		fmt.Printf("%v: %v\n", name, ip)
	}
}

Errores

Los programas Go expresan el estado de error con errorvalores.

El errortipo es una interfaz integrada similar a fmt.Stringer:

type error interface {
    
    
    Error() string
}

Al igual que con fmt.Stringer, el paquete fmt busca la errorinterfaz al imprimir valores.
Las funciones a menudo devuelven un errorvalor y el código de llamada debe manejar los errores probando si el error es igual nil.

i, err := strconv.Atoi("42")
if err != nil {
    
    
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

A nil errordenota éxito; a non-nil errordenota fracaso.

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    
    
	return fmt.Sprintf("cannot Sqrt negative number: %v\n", float64(e))
}

func Sqrt(x float64) (float64, error) {
    
    
	if x < 0 {
    
    
		var e ErrNegativeSqrt = ErrNegativeSqrt(x)
		return -1, e
	}
	return math.Sqrt(x), nil
}

func main() {
    
    
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

Lectores

package main

import (
	"fmt"
	"io"
	"strings"
)

func NewReader(s string) *Reader // this is from "strings" package. NewReader returns a new Reader reading from s. It is similar to bytes.NewBufferString but more efficient and read-only.

func main)() {
    
    
	r := strings.NewReader("Hello, Reader!")
	b := make([]byte, 8) // init a byte slice of size 8, i.e., 8 bytes
	/*

	type Reader interface {
    	Read(p []byte) (n int, err error)
	}
	Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.

	*/
	for {
    
    
		n, err := r.Read(b)
		fmt.Printf("n = %v, err = %v, b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
    
    
			break
		}
	}
}

Conceptos omitidos: imágenes

Las imágenes se omiten por ahora.

Supongo que te gusta

Origin blog.csdn.net/Hita999/article/details/112614554
Recomendado
Clasificación