Golang基础-Structs与Methods、Interfaces

Structs

  • 类型定义(type)相当于一种别名
  • 将struct定义为一种类型Car
  • NewCar函数return &Car{},返回指针
// car.go
package elon

// Car implements a remote controlled car.
type Car struct {
	speed        int
	batteryDrain int

	battery  int
	distance int
}

// NewCar creates a new car with given specifications.
func NewCar(speed, batteryDrain int) *Car {
	return &Car{
		speed:        speed,
		batteryDrain: batteryDrain,
		battery:      100,
	}
}

struct的零值,When constructing the zero value for a struct type, all of the struct's fields will be set to their zero value:

type Person struct {
	Name string
	Age  int
}

var myPerson Person
fmt.Printf("Zero value Person: %#v", myPerson)
// Output: Zero value Person: main.Person{Name:"", Age:0}

// 给有数值的struct赋零值
myPerson = Person{}

Methods

  • 给Car类型绑定方法
  • 绑定方法到*Car,说明在指针上,函数内可以改变结构体的值;如果是Car,则是拷贝,不能改变原始变量的值。
package elon

import "fmt"

// TODO: define the 'Drive()' method
func (car *Car) Drive() {
	if car.battery < car.batteryDrain {
		return
	}
	car.distance += car.speed
	car.battery -= car.batteryDrain
}

// TODO: define the 'DisplayDistance() string' method
func (car *Car) DisplayDistance() string {
	return fmt.Sprintf("Driven %d meters", car.distance)
}

// TODO: define the 'DisplayBattery() string' method
func (car *Car) DisplayBattery() string {
	return fmt.Sprintf("Battery at %d%%", car.battery)
}

// TODO: define the 'CanFinish(trackDistance int) bool' method
func (car *Car) CanFinish(trackDistance int) bool {
	if (trackDistance+car.speed-1)/car.speed*car.batteryDrain > car.battery {
		return false
	} else {
		return true
	}
}
  • 给新的自定义类型绑定方法不一定要求类型是struct,也可以是int等一般类型。

下面定义的DistanceUnit类型的原型是int。给两个自定义类型实现了Stringer interface,也就是自定义打印时输出什么。Stringer接口只有一个方法,String() string

type DistanceUnit int

const (
	Kilometer    DistanceUnit = 0
	Mile         DistanceUnit = 1
)
 
type Distance struct {
	number float64
	unit   DistanceUnit
} 

func (sc DistanceUnit) String() string {
	units := []string{"km", "mi"}
	return units[sc]
}

func (d Distance) String() string {
	return fmt.Sprintf("%v %v", d.number, d.unit)
}

Interfaces

  • 定义了Greeter interface
  • 定义了Italian和Portuguese两种类型
  • 两种类型均实现了Greeter interface
  • SayHello参数有一个Greeter,一个接口,但是却可以传入实现了Greeter所有接口的类型的变量。
package airportrobot

import "fmt"

// Write your code here.
// This exercise does not have tests for each individual task.
// Try to solve all the tasks first before running the tests.

type Greeter interface {
	LanguageName() string
	Greet(username string) string
}

func SayHello(username string, g Greeter) string {
	return fmt.Sprintf("I can speak %s: %s", g.LanguageName(), g.Greet(username))
}

type Italian struct {
}

func (I Italian) LanguageName() string {
	return "Italian"
}
func (I Italian) Greet(username string) string {
	return fmt.Sprintf("Ciao %s!", username)
}

type Portuguese struct {
}

func (P Portuguese) LanguageName() string {
	return "Portuguese"
}
func (P Portuguese) Greet(username string) string {
	return fmt.Sprintf("Olá %s!", username)
}

这里的Italian{}和Portuguese{}牵涉到golang中空结构体的一个作用,实现方法接收者。Italian和Portuguese都是struct{},一个没有任何字段的结构体,的别名,作为了两种新的类型,虽然本质是一样的,但这两种新类型将作为不同类型的存在。而这两种新类型,则各自实现了Greeter接口的两个函数。
Italian{}和Portuguese{}则都相当于struct{}{},struct{}是类型,后面的{}是初始化,最终出来的是一个空结构体。本质是空结构体,但实际上是Italian和Portuguese两种类型的两个值。

空结构体还有两个作用,将map的value作为空结构体实现set;实现空通道,常常会遇到通知型 channel,其不需要发送任何数据,只是用于协调 Goroutine 的运行,用于流转各类状态或是控制并发情况,由于该 channel 使用的是空结构体,因此也不会带来额外的内存开销。

fmt.Println(SayHello("mlg", Italian{}))
fmt.Println(SayHello("glm", Portuguese{}))

为什么这里给type绑定方法用的不是指针?

For implementing the interface, it does not matter whether the method has a value or pointer receiver.

如果要改成指针形式的,则相应的调用函数也应改变。

type Portuguese struct {
}

func (P *Portuguese) LanguageName() string {
	return "Portuguese"
}
func (P *Portuguese) Greet(username string) string {
	return fmt.Sprintf("Olá %s!", username)
}

// 这里的调用传参也换成了指针(地址)
fmt.Println(SayHello("glm", &Portuguese{}))

所以interface类型的参数可以传值也可以传指针?
经过实验,即使上面代码中Portuguese绑定的是值而不是指针,在调用SayHello时仍然可以传入&Portuguese{}。但是如果实现的方法绑定的是指针,则在调用SayHello时只能传入指针,不能传值。

Type Assertion

Interfaces in Go can introduce ambiguity about the underlying type. In fact, the empty interface can take any concrete value at all (including primitives). A type assertion allows us to extract the interface value's underlying concrete value using this syntax: interfaceVariable.(concreteType).
上面的SayHello函数的第二个参数是Greeter interface,但传入的值是却是其他类型的变量,可以使用Type Assertion来判断或转化为相应的类型。

func seeType(g Greeter) {
	switch g.(type) {
	case Italian:
		fmt.Println("Italian type")
	case Portuguese:
		fmt.Println("Portuguese type")
	}
}

seeType(Italian{})    // => Italian type
seeType(Portuguese{})    // => Portuguese type


var i interface{} = 12 // try: 12.3, true, int64(12), []int{}, map[string]int{}

switch v := i.(type) {
case int:
    fmt.Printf("the integer %d\n", v)
case string:
    fmt.Printf("the string %s\n", v)
default:
    fmt.Printf("type, %T, not handled explicitly: %#v\n", v, v)
}
posted @ 2023-02-19 15:41  roadwide  阅读(29)  评论(0编辑  收藏  举报