乐趣区

关于golang:Golang-基础之基础语法梳理-三

大家好,明天将梳理出的 Go 语言根底语法内容,分享给大家。请多多指教,谢谢。

本次《Go 语言根底语法内容》共分为三个章节,本文为第三章节

  • Golang 根底之根底语法梳理 (一)
  • Golang 根底之根底语法梳理 (二)
  • Golang 根底之根底语法梳理 (三)

本章节内容

  • interface
  • 反射
  • 泛型

interface

介绍

在 Go 语言中接口 (interface) 是一种类型,一种形象的类型。

接口 (interface) 定义了一个对象的行为规范,只定义标准不实现,由具体的对象来实现标准的细节。

接口做的事件就像是定义一个协定(规定)。

Interface 是一组 method 的汇合,是 duck-type programming 的一种体现。

接口的定义

  • 接口是一个或多个办法签名的汇合
  • 接口只有办法申明,没有实现,没有数据字段
  • 接口能够匿名嵌入其余接口,或嵌入到构造中
  • 接口调用不会做 receiver 的主动转换
  • 接口同样反对匿名字段办法
  • 接口也可实现相似 OOP 中的多态
  • 任何类型的办法集中只有领有该接口 ’ 对应的全副办法 ’ 签名
  • 只有当接口存储的类型和对象都为 nil 时,接口才等于 nil
  • 用 interface{} 传递任意类型数据是 Go 语言的常规用法,而且 interface{} 是类型平安的
  • 空接口能够作为任何类型数据的容器
  • 一个类型可实现多个接口
  • 接口命名习惯以 er 结尾

应用

每个接口由数个办法组成,接口的定义如下

type 接口类型 interface {办法名 1 (参数列表 1) 返回值列表 1
  办法名 2 (参数列表 2) 返回值列表 2
  ...
}

留神

  1. 接口名:应用 type 将接口定义为自定义的类型名。Go 语言的接口在命名时,个别会在单词前面增加 er,如有写操作的接口叫 Writer,有字符串性能的接口叫 Stringer 等。接口名最好要能突出该接口的类型含意。
  2. 办法名:当办法名首字母是大写且这个接口类型名首字母也是大写时,这个办法能够被接口所在的包(package)之外的代码拜访。
  3. 参数列表、返回值列表:参数列表和返回值列表中的参数变量名能够省略。

例子

type writer interface {Write([]byte) error
}

值接收者和指针接管接口

type Mover interface {move()
}

type dog struct {}

func (d dog) move() {fmt.Println("狗狗")
}

func main() {
  var x Mover
  var wangcai = dog{}
  x = wangcai                        // x 能够接管 dog 类型
  var fugui = &dog{}                // fugui 是 *dog 类型 
  x = fugui                            // x 能够接管 *dog 类型 指针接管
  x.move()}

多个类型实现同一接口

// Mover 接口
type Mover interface {move()
}

type dog struct {name string}
type car struct {brand string}

// dog 类型实现 Mover 接口
func (d dog) move() {fmt.Printf("%s: mmmm", d.name)
}
// car 类型实现 Mover 接口
func (c car) move() {fmt.Printf("%s: mmmm", c.brand)
}

func main() {
  var x Mover
  var a = dog{name: "旺财"}
  var b = car{brand: "虾米"}
  x = a
  x.move()
  x = b
  x.move()}

一个接口的办法,不肯定须要由一个类型齐全实现,接口的办法能够通过在类型中嵌入其余类型或者构造体来实现。

接口嵌套

接口与接口间能够通过嵌套发明出新的接口。

type Sayer interface {say()
}
type Mover interface {move()
}

// 接口嵌套
type animal interface {
    Sayer
    Mover
}

// 嵌套失去的接口的应用与一般接口一样
type cat struct {name string}

func (c cat) say() {fmt.Println("ssss")
}

func (c cat) move() {fmt.Println("mmmm")
}

func main() {
    var x animal
    x = cat{name: "花花"}
    x.move()
    x.say()}

空接口

空接口是指没有定义任何办法的接口,因而任何类型都实现了空接口。

空接口类型的变量能够存储任意类型的变量。

func main() {
    // 定义一个空接口 x
    var x interface{}
    s := "test data"
    x = s
    fmt.Printf("type:%T value: %v\n", x, x)
    i := 100
    x = i
    fmt.Printf("type:%T value: %v\n", x, x)
    b := true
    x = b
    fmt.Printf("type:%T value: %v\n", x, x)
}

空接口作为函数的参数

应用空接口实现能够接管任意类型的函数对象。

func show(a interface{}){fmt.Printf("type:%T value: %v\n", a, a)
}

空接口作为 map 的参数

应用空接口实现能够保留任意值的字典

var Info = make(map[string]interface{})
Info["id"] = 1
Info["name"] = "帽儿山的枪手"
fmt.Println(Info)

获取空接口值

判断空接口中值,能够应用类型断言,语法如下

x.(T)

x 示意类型为 interface{} 的变量

T 示意断言 x 可能是的类型

该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则示意断言胜利,false 则示意失败。

func main() {var x interface{}
    x = "data"
    v, ok := x.(string)
    if ok {fmt.Println(v)
    } else {fmt.Println("类型断言失败")
    }
}

如果要断言屡次,能够写 if 判断,也能够用 switch 语句实现。

反射

介绍

什么是反射?

例如:有时候咱们须要晓得某个值是什么类型,能力用对等逻辑去解决它。

以下是罕用的解决办法:

// 伪代码
switch value := value.(type){
    case string:
        // 解决操作
    case int:
        // 解决操作
    ...
}

这样解决,会写的十分长,而且还可能存在自定的类型,也就是说这个判断日后可能还要始终改,因为无奈晓得未知值到底属于什么类型。

如果应用反射来解决,应用规范库 reflect 中的 TypeOf 和 ValueOf 函数从接口中获取指标对象的信息,就能够轻松解决这个问题。

更多介绍,可参考 reflect 官网地址

https://pkg.go.dev/reflect

Go 语言提供了一种机制,在编译时 不晓得类型的状况下 ,可 更新变量 、运行时 查看值 调用办法 以及间接对他们的 布局进行操作 的机制,称为反射。

应用

应用反射查看对象类型

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "帽儿山的枪手"
    nameType := reflect.TypeOf(name)
    nameValue := reflect.ValueOf(name)

    fmt.Println("name type:", nameType)
    fmt.Println("name value:", nameValue)
}

输入

name type:  string
name value:  帽儿山的枪手

struct 类型反射用法

package main

import (
    "fmt"
    "reflect"
)

type Info struct {
    Name string
    Desc string
}

func (i Info) Detail() {fmt.Println("detail info")
}

func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}
    
    t := reflect.TypeOf(i) // 获取指标对象
    v := reflect.ValueOf(i) // 获取 value 值
    for i := 0; i < v.NumField(); i++ { // NumField()获取字段总数
        key := t.Field(i) // 依据下标, 获取蕴含的 key
        value := v.Field(i).Interface() // 获取 key 对应的值
        fmt.Printf("key=%s value=%v type=%v\n", key.Name, value, key.Type)
    }

    // 获取 Info 的办法
    for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)
        fmt.Printf("办法 Name=%s Type=%v\n", m.Name, m.Type)
    }
}

输入

key=Name value= 帽儿山的枪手 type=string
key=Desc value= 技术分享 type=string
办法 Name=Detail Type=func(main.Info)

通过反射判断类型用法

package main

import (
    "fmt"
    "reflect"
)

type Info struct {
    Name string
    Desc string
}

func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}
    t := reflect.TypeOf(i)

    // Kind()函数判断值的类型
    if k := t.Kind(); k == reflect.Struct {fmt.Println("struct type")
    }
    num := 100
    switch v := reflect.ValueOf(num); v.Kind() {
    case reflect.String:
        fmt.Println("string type")
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        fmt.Println("int type")
    default:
        fmt.Printf("unhandled kind %s", v.Kind())
    }
}

输入

struct type
int type

通过反射批改内容

package main

import (
    "fmt"
    "reflect"
)

type Info struct {
    Name string
    Desc string
}

func main() {i := &Info{Name: "帽儿山的枪手", Desc: "技术分享"}
    v := reflect.ValueOf(i)

    // 批改值必须是指针类型
    if v.Kind() != reflect.Ptr {fmt.Println("不是指针类型")
        return 
    }
    v = v.Elem() // 获取指针指向的元素
    name := v.FieldByName("Desc") // 获取指标 key 的值
    name.SetString("好好工作")
    fmt.Printf("批改后数据: %v\n", *i)
}

输入

批改后数据: {帽儿山的枪手 好好工作}

通过反射调用办法

package main

import (
    "fmt"
    "reflect"
)

type Info struct {
    Name string
    Desc string
}

func (i Info) Detail() {fmt.Println("detail info")
}

func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}
    v := reflect.ValueOf(i)

    // 获取办法控制权
    mv := v.MethodByName("Detail")
    mv.Call([]reflect.Value{}) // 这里是无调用参数 []reflect.Value{}
}

输入

detail info

泛型

介绍

泛型的概念,能够从多态看起,多态是同一模式体现出不同行为的一种个性,在编程语言中被分为两类,临时性多态和参数化多态。

依据实参生成不同的版本,反对任意数量的调用,即泛型,简言之,就是把元素类型变成了参数。

golang 版本须要在 1.17 版本或以上,才反对泛型应用。

(1.17 版本泛型是 golang 推出的尝鲜版,1.18 是正式版本)

举例

func Add(a, b int) int{}
func AddFloat(a, b float64) float64{}

在泛型的帮忙下,下面代码就能够简化成为:

func Add[T any](a, b T) T

Add 前面的[T any],T 示意类型的标识,any 示意 T 能够是任意类型。

a、b 和返回值的类型 T 和后面的 T 是同一个类型。

为什么用[],而不是其余语言中的 <>,官网有过解释,大略就是 <> 会有歧义。已经打算应用(),因为太容易混同,最初应用了[]。

泛型 3 大概念

  • 类型参数
  • 类型束缚
  • 类型推导

个性

  • 函数能够通过 type 关键字引入额定的类型参数 (type parameters) 列表:func F(type T)(p T) {...}
  • 这些类型参数能够像个别的参数一样在函数体中应用
  • 类型也能够领有类型参数列表:type M(type T) []T
  • 每个类型参数能够领有一个束缚:func F(type T Constraint)(p T) {...}
  • 应用 interface 来形容类型的束缚
  • 被用作类型束缚的 interface 能够领有一个预申明类型列表,限度了实现此接口的类型的根底类型
  • 应用泛型函数或类型时须要传入类型实参
  • 类型推断容许用户在调用泛型函数时省略类型实参
  • 泛型函数只容许进行类型束缚所规定的操作

应用

对泛型进行输入

如果 Go 以后版本是 1.17 版本,运行时须要加参数 -gcflags=-G=3

# 残缺命令
go run -gcflags=-G=3 example.go 

示例

package main

import ("fmt")

func print[T any](s []T) {
    for _, v := range s {fmt.Printf("%v", v)
    }
    fmt.Printf("\n")
}

func main() {print[int]([]int{1,2,3,4})
    print[float64]([]float64{1.01, 2.02, 3.03, 4.04})
    print[string]([]string{"a", "b", "c", "d"})
}

输入

1 2 3 4 
1.01 2.02 3.03 4.04 
a b c d 

Go1.18 中,any 是 interface{} 的别名

应用泛型束缚,管制类型的应用范畴

原先的语法中,类型束缚会用逗号分隔的形式来展现

type int, int8, int16, int32, int64

在新语法中,联合定义为 union element(联结元素),写成一系列由竖线”|“分隔的类型或近似元素。

int | int8 | int16 | int32 | int64

示例

package main

import ("fmt")

type CustomType interface {int | int8 | int16 | int32 | int64 | string}

func add[T CustomType] (a, b T) T{return a + b}

func main() {fmt.Println(add(1, 2))
    fmt.Println(add("帽儿山的枪手", "技术分享"))
}

输入

3
帽儿山的枪手技术分享

上述 CustomType 接口类型也能够写成以下格局

type CustomType interface {~int | ~string}

上述申明的类型集是 ~int,也就是所有类型为 int 的类型(如:int、int8、int16、int32、int64)都可能满足这个类型束缚的条件。

泛型中自带 comparable 束缚

因为不是所有的类型都能够 == 比拟,所以 Golang 内置提供了一个 comparable 束缚,示意可比拟的。

官网阐明

comparable 是由所有可比拟类型(布尔、数字、字符串、指针、通道、可比拟类型的数组、字段均为可比拟类型的构造)实现的接口。可比拟接口只能用作类型参数束缚,不能用作变量的类型。

https://pkg.go.dev/builtin@ma…

package main

import ("fmt")

func diff[T comparable](a []T, v T) {
    for _, e := range a {
        if e == v {fmt.Println(e)
        }
    }
}

func main() {diff([]int{1, 2, 3, 4}, 3)
}

输入

3

泛型中操作指针

package main

import ("fmt")

func pointerOf[T any](v T) *T {return &v}

func main() {name := pointerOf("帽儿山的枪手")
    fmt.Println(*name)
    id := pointerOf(100)
    fmt.Println(*id)
}

输入

帽儿山的枪手
100

技术文章继续更新,请大家多多关注呀~~

退出移动版