Golang 进阶用法

[Golang]: 进阶用法

主要介绍在 Golang 中相对进阶的用法,如interface、reflection、Tag。善用这些技巧可以使得程式码更加简洁。ex, 透过 interface 的技巧使得 func 的参数更加有弹性;使用 reflection 进一步资料属于的型态、甚至达到无须知道 type 也能够修改资料; Tag 让你 mapping 资料更加方便。

From personal blog

Golang Interface

Interface{} 型别转换

在Golang 中Interface资料结构是相当重要的,由于 Golang 属于强型别语言,因此在func 中的 parameter 与 return value 时常会因为结构受限造成许多的不方便,然而interface就是来解决问题。(它本身可以是Golang 语言中任一 type 进而解决该问题。) @playground

package mainimport ("fmt""strconv")type str stringfunc (s str) String() string {return string(s)}type Stringer interface {String() string}func ToString(any interface{}) string {if v, ok := any.(Stringer); ok {return v.String()}switch v := any.(type) {case int:return strconv.Itoa(v)case float64:return strconv.FormatFloat(v, 'g', -1, 64)}return "???"}func main() {var ex int = 1fmt.Println(ToString(ex))var ex2 float64 = 0.1fmt.Println(ToString(ex2))var ex3 Stringer = str("1")fmt.Println(ToString(ex3))}

如上述程式码,实作ToString 方法时,需要传入各种型态(type)的参数,此时Interface 的弹性就派上用场了,藉由还原的语法进行实作,绕过强型别参数型态固定的问题。

Interface{} 多形

此外在Golang中若要做到 abstract method 的话则也需要 interface,它提供抽象方法的功能,并且可以在compile time 就能排除抽象方法实作上部份错误,ex. 少定义方法等...。换句话说,在Golang中实作多型 须仰赖interface。@playground

package mainimport ("fmt")type Car interface {AddOil(gallon int)Run()}type VovoCar struct {}func (v *VovoCar) AddOil(gallon int) {fmt.Printf("vovo car add %d gallon\n", gallon)}func (v *VovoCar) Run() {fmt.Printf("vovo car add run\n")}type ToyotaCar struct {}func (t *ToyotaCar) AddOil(gallon int) {fmt.Printf("Toyota car add %d gallon\n", gallon)}func (t *ToyotaCar) Run() {fmt.Printf("Toyota car add run\n")}func main() {var c Car = &VovoCar{}var c2 Car = &ToyotaCar{}c.AddOil(10)c.Run()c2.AddOil(100)c2.Run()}

Golang Reflection

Reflection 是一种用于描述程式语言的工具。由于在Golang 中任何型别都可以是一种Interface,因此时常需要与Reflection 进行搭配,故笔者认为是一种Interface的配套工具。主要有三种用法:

映射出Interface 的资料型别 @playground
package mainimport ("fmt""reflect")type Example struct {}func main() {fmt.Println(reflect.TypeOf(&Example{}))// *main.Eaxmple}
映射出Interface 的值 @playground
package mainimport ("fmt""reflect")type Example struct {name string}func main() {val := reflect.ValueOf(&Example{ name: "Example name"}).Interface().(*Example) fmt.Println(val) // &{Example name}}
将某interface 资料注入到其他interface 中。 @palyground主要透过Call by Reference 的原理进行修改,因此dest 型态必须为Ptr。搭配reflect.ValueOf.Elem.Field 找出struct filed 的位置进而修改资料。
package mainimport ("fmt""reflect")type Example struct {Id   stringName string}func ChangeValue(dest interface{}) {valDest := reflect.ValueOf(dest)for i := 0; i < valDest.Elem().NumField(); i++ {if i == 0 {valDest.Elem().Field(i).Set(reflect.ValueOf("change_id"))} else {valDest.Elem().Field(i).Set(reflect.ValueOf("change_name"))}}}func main() {destVal := &Example{Id: "test_id", Name: "test_name"}fmt.Println(destVal) // &{test_id test_name}ChangeValue(destVal)fmt.Println(destVal) // &{change_id change_name}}

Golang Tag

在Golang Struct 资料结构中,可自定义 Tag,有点类似于其他语言的Annotation,例如在判读 Json 的Key 值时,须利用`json:"name"`的方式填入。然而在学习Golang 的初期时常会勿以为 Tag 是不可定义的,因为并不清楚如何取用。然而Reflection 工具此时就得到了一大作用,由于是描述程式语言的工具,因此可透过该工具将Tag 取出。@playground

package mainimport ("fmt""reflect")type Example struct {Id string `json:"id"`}func main() {jsonExample := &Example{Id: "test"}field := reflect.TypeOf(*jsonExample).Field(0)fmt.Printf("name: %v ,tag:'%v'\n", field.Name, field.Tag) // name: Id ,tag:'json:"id"'}

Golang 低阶指标用法 ( uintptr, unsafe.Pointer)

在Golang 语言中除了正规的正常的写法之外,也提供低阶的程式设计模式,如可直接调用Unix 系统的C程式的cgo。但这种方式是不被建议使用的,因为直接调用很容易出现非预期的错误,除非特殊需要。若要Golang 中使用这一类低阶的程式设计,则需宣告import "unsafe"字样,语意上表示从外部汇入unsafe 套件,但实际上是由编译器直接调用隐藏功能。ex: net, syscall, os, runtime 等大多程式设计不太需要用到的。

uintptr 属于Golang 基础型态的一种,事实上是 Integer 型别,且可以接各种型态的指标。unsafe.Pointer 是将任何型别的东西转换成*ArbitraryType 而 ArbitraryType 的定义为type ArbitraryType int ,因此实际上也是一种 Integer 型别。

Escape Analysis 演算法

指的是在编译器端,对于程式码编译后,针对指标 (Pointer) 资料结构进行优化,并计算出需要多少的Heap 储存。往往在Golang 中,时常使用到指标类型的结构,然而只要有该函式之外的呼叫,编译器则会预先预先多配发空间,进而触发GC(Grabage Collection)机制。

例如以下图式为例: 单纯透过指标的方式印出整数阵列,正规写法如Listing 1 直接使用外部func 印出,优化做法透过 unsafe 的方式呼叫外部func。

在Golang中使用unsafe 直接调用Compile 的Pointer编译器则不会预先产出Heap,也因此能避免该问题,但坏处是可能会遇到预期之外的错误(警语)。如上图程式码,都只是印出数字,但透过 unsafe pointer 调用的,使用的记忆体大小是预设的50倍,Heap 的大小是 3倍(下图所示)。

Reference

[1] Wang, Cong, et al. "Escape from escape analysis of Golang." Proceedings of the ACM/IEEE 42nd International Conference on Software Engineering: Software Engineering in Practice. 2020.
[2] https://go.dev/blog/laws-of-reflection
[3] https://research.swtch.com/interfaces


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章