大家好,明天将梳理出的 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 ...}
留神
- 接口名:应用type将接口定义为自定义的类型名。Go语言的接口在命名时,个别会在单词前面增加er,如有写操作的接口叫Writer,有字符串性能的接口叫Stringer等。接口名最好要能突出该接口的类型含意。
- 办法名:当办法名首字母是大写且这个接口类型名首字母也是大写时,这个办法能够被接口所在的包(package)之外的代码拜访。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名能够省略。
例子
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"] = 1Info["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 mainimport ( "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: stringname value: 帽儿山的枪手
struct 类型反射用法
package mainimport ( "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=stringkey=Desc value=技术分享 type=string办法 Name=Detail Type=func(main.Info)
通过反射判断类型用法
package mainimport ( "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 typeint type
通过反射批改内容
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt")func pointerOf[T any](v T) *T { return &v}func main() { name := pointerOf("帽儿山的枪手") fmt.Println(*name) id := pointerOf(100) fmt.Println(*id)}
输入
帽儿山的枪手100
技术文章继续更新,请大家多多关注呀~~