zhcn 编程语言 Golang Golang 函数 非公開: Go语言接口和类型之间的转换

Go语言接口和类型之间的转换

Go语言使用接口断言(类型断言)将一个接口转换为另一个接口,也可以将一个接口转换为另一种类型。接口转换在开发中很常见,也经常使用。

类型断言的格式

类型断言是对接口值执行的操作。从语法上看,i.(T) 被称为断言类型。其中,i表示接口类型,T表示类型。类型断言检查正在操作的对象的动态类型是否与断言类型匹配。

类型断言的基本形式是:

 t := i.(T) 

其中,i表示接口变量,T表示转换目标的类型,t表示转换后的变量。

这里有两种可能性。首先,如果断言类型 T 是具体类型,则类型断言检查 i 的动态类型是否与 T 相同。如果此检查成功,则类型断言的结果是 i 的动态值。这当然是T型。换句话说,具体类型的类型断言从其操作数获取具体值。如果检查失败,接下来的操作就会panic。例如:

 var w io.Writer
w = os.Stdout
f := w.(*os.File) // 成功: f == os.Stdout
c := w.(*bytes.Buffer) // 死にます:インタフェースには*os.Fileが保存されているため、*bytes.Bufferに変換できません。 

然后,如果类型 T 被断言为接口类型,则类型断言将检查 i 的动态类型是否满足 T。如果此检查成功,则不会获取任何动态值。结果仍然是具有相同类型和值部分的接口值,但结果类型是 T。也就是说,接口类型上的类型断言改变了类型的表示方式并改变了(通常更大的)可用方法集,但动态类型和接口值中的一些值被保留。

在下面的第一个类型断言之后,w和rw都持有os.Stdout,因此它们都有动态类型*os.File,但变量w是io.Writer类型的Write,它只公开文件方法,但只公开文件方法。 rw 变量公开 Read 方法。

 var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // Success: *os.file has both read and write functions
w = new(ByteCounter)
rw = w.(io.ReadWriter) // Deadlock: *ByteCounter does not have a read method 

如果断言操作的对象是 nil 接口值,则无论断言的类型是什么,类型断言都会失败。对于限制较少的接口类型(一小组方法),几乎不需要断言,因为它的行为类似于赋值操作,除非接口值为 nil 。

如果你没有完全实现T接口的方法,这个语句会导致崩溃。上面的语句也可以用不同的方式编写,因为停机触发器不太友好。

 t,ok := i.(T) 

因此,如果接口未实现,ok 将设置为 false,t 将设置为 T 类型的 0。如果成功实施,ok 将为 true。这里的OK可以认为是i接口是否实现了T类型的结果。

将一个接口转换为另一个接口

实现一个接口的类型也实现另一个接口,并且此时可以在两个接口之间进行转换。

鸟和猪有不同的特性,鸟会飞,猪不会飞,但两种动物都会走路。如果 Birds 和 Pigs 使用结构体实现,那么 Fly() 和 Walk() 方法可以让 Birds 和 Pigs 拥有自己的特性(Flyer)和行走动物接口(Walker)。 ) 分别。

鸟和猪实例创建后,它们被保存在类型接口的映射中。 {}Interface{} 类型表示空接口。即该接口可以保存为任意类型。对保存鸟或猪实例的接口变量执行断言操作。如果断言对象属于断言中指定的类型,则返回转换为断言对象类型的接口。否则,返回转换为断言对象类型的接口。 {}对于指定的断言类型,断言的第二个参数返回 false。例如,以下代码:

 var obj interface = new(bird)
f, isFlyer := obj.(Flyer) 

代码中,new(bird)创建了一个*bird类型的Bird实例,该实例存储在interface{}类型的obj变量中。使用 obj.(Flyer) 类型断言将 obj 转换为 Flyer 接口。如果转换成功,f为Flyer接口的类型,isFlyer表示转换是否成功,bool类型。

详细代码(代码1)如下:

 package main

import "fmt"

// 飛行動物のインターフェースを定義する
type Flyer interface{
    Fly()
}

// 歩行動物のインターフェースを定義する
type Walker interface{
    Walk()
}

// 鳥を定義する
type bird struct{
}

// 飛行動物のインターフェースを実装する
func (b * bird) Fly(){
    fmt.Println("bird: fly")
}

// 鳥にWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (b * bird) Walk(){
    fmt.Println("bird: walk")
}

// ブタを定義する
type pig struct{
}

// ブタにWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (p * pig) Walk(){
    fmt.Println("pig: walk")
}

func main(){

// 名前とインスタンスのマッピングを作成する
    animals := map[string]interface{}{
        "bird": new(bird),
        "pig": new(pig),
    }

    // マッピングを反復処理する
    for name, obj := range animals {

        // オブジェクトが飛行動物かどうかを判断する
        f, isFlyer := obj.(Flyer)
        // オブジェクトが歩行動物かどうかを判断する
        w, isWalker := obj.(Walker)

        fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker)

        // 飛行動物の場合は、飛行動物インターフェイスを呼び出す
        if isFlyer {
            f.Fly()
        }

        // 歩行動物の場合は、歩行動物インターフェースを呼び出す
        if isWalker {
            w.Walk()
        }
    }
} 

代码说明如下:

  • 第 6 行定义了飞行动物界面。
  • 第 11 行定义了动物行走的界面。
  • 第16行和第30行分别定义了鸟和猪两个对象,分别实现了飞行动物和行走动物的界面。
  • 第 41 行是将对象名称映射到对象实例的映射。例如鸟类和猪。
  • 第 47 行开始扫描地图。 obj 是一个接口类型。 {}
  • 第 50 行使用类型断言获取 f,并且 Flyer 和 isFlyer 类型的断言成功。
  • 第 52 行使用类型断言来获取 w,并且类型 Walker 和 isWalker 断言成功。
  • 第 57 行和第 62 行根据断言对于飞行动物和步行动物是否成功来调用该接口。

这是代码输出:

name: pig isFlyer: false isWalker: true
pig: walk
name: bird isFlyer: true isWalker: true
bird: fly
bird: walk

将接口转换为另一种类型

代码 1 允许您将接口转换为通用指针类型。例如,要将Walker接口转换为*pig类型,请参见以下代码。

 p1 := new(pig)

var a Walker = p1
p2 := a.(*pig)

fmt.Printf("p1=%p p2=%p", p1, p2) 

代码说明如下:

  • 第3行,由于pig实现了Walker接口,因此可以隐式转换为Walker接口类型并存储在a中。
  • 第4行,a中存储的是*pig本体,因此可以转换为*pig类型。
  • 第6行,比较发现p1和p2中的指针是相同的。

如果您尝试在上面的代码中将 Walker 类型转换为 *bird 类型,您将收到运行时错误。请参阅下面的代码。

 p1 := new(pig)

var a Walker = p1
p2 := a.(*bird) 

运行时出错:

panic: interface conversion: main.Walker is *main.pig, not *main.bird

错误信息的意思是接口转换时,main.Walker接口的内部存储将是*main.pig而不是*main.bird。

因此,当将接口转换为另一种类型时,接口中存储的实例对应的类型指针必须是对应的要转换的类型指针。

总结

接口和其他类型的转换在 Go 中是免费的,只要它们得到完全实现。

接口断言类似于流控制。但是,如果您看到大量类型断言,则应该使用更有效的类型分支切换器。

通俗易懂的讲解《Go语言接口与类型之间的转换》!您必须观看的 2 个最佳视频

一緒に学ぶGo言語入門 ~その9:メソッドとインターフェース(前編)~
https://www.youtube.com/watch?v=ntLdZ_qyqTM&pp=ygVAIEdvIOiogOiqnuOBruOCpOODs-OCv-ODvOODleOCp-ODvOOCueOBqOWei-OBrumWk-OBruWkieaPmyZobD1KQQ%3D%3D
Go (Golang) io.Reader インターフェイス
https://www.youtube.com/watch?v=O-MeKOuvzYE&pp=ugMICgJqYRABGAHKBUAgR28g6KiA6Kqe44Gu44Kk44Oz44K_44O844OV44Kn44O844K544Go5Z6L44Gu6ZaT44Gu5aSJ5o-bJmhsPUpB
Go语言使用接口断言(类型断言)将一个接口转换为另一个接口,也可以将一个接口转换为另一种类型。接口转换在开发中很常见,也经常使用。

类型断言的格式

类型断言是对接口值执行的操作。从语法上看,i.(T) 被称为断言类型。其中,i表示接口类型,T表示类型。类型断言检查正在操作的对象的动态类型是否与断言类型匹配。

类型断言的基本形式是:

 t := i.(T) 

其中,i表示接口变量,T表示转换目标的类型,t表示转换后的变量。

这里有两种可能性。首先,如果断言类型 T 是具体类型,则类型断言检查 i 的动态类型是否与 T 相同。如果此检查成功,则类型断言的结果是 i 的动态值。这当然是T型。换句话说,具体类型的类型断言从其操作数获取具体值。如果检查失败,接下来的操作就会panic。例如:

 var w io.Writer
w = os.Stdout
f := w.(*os.File) // 成功: f == os.Stdout
c := w.(*bytes.Buffer) // 死にます:インタフェースには*os.Fileが保存されているため、*bytes.Bufferに変換できません。 

然后,如果类型 T 被断言为接口类型,则类型断言将检查 i 的动态类型是否满足 T。如果此检查成功,则不会获取任何动态值。结果仍然是具有相同类型和值部分的接口值,但结果类型是 T。也就是说,接口类型上的类型断言改变了类型的表示方式并改变了(通常更大的)可用方法集,但动态类型和接口值中的一些值被保留。

在下面的第一个类型断言之后,w和rw都持有os.Stdout,因此它们都有动态类型*os.File,但变量w是io.Writer类型的Write,它只公开文件方法,但只公开文件方法。 rw 变量公开 Read 方法。

 var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // Success: *os.file has both read and write functions
w = new(ByteCounter)
rw = w.(io.ReadWriter) // Deadlock: *ByteCounter does not have a read method 

如果断言操作的对象是 nil 接口值,则无论断言的类型是什么,类型断言都会失败。对于限制较少的接口类型(一小组方法),几乎不需要断言,因为它的行为类似于赋值操作,除非接口值为 nil 。

如果你没有完全实现T接口的方法,这个语句会导致崩溃。上面的语句也可以用不同的方式编写,因为停机触发器不太友好。

 t,ok := i.(T) 

因此,如果接口未实现,ok 将设置为 false,t 将设置为 T 类型的 0。如果成功实施,ok 将为 true。这里的OK可以认为是i接口是否实现了T类型的结果。

将一个接口转换为另一个接口

实现一个接口的类型也实现另一个接口,并且此时可以在两个接口之间进行转换。

鸟和猪有不同的特性,鸟会飞,猪不会飞,但两种动物都会走路。如果 Birds 和 Pigs 使用结构体实现,那么 Fly() 和 Walk() 方法可以让 Birds 和 Pigs 拥有自己的特性(Flyer)和行走动物接口(Walker)。 ) 分别。

鸟和猪实例创建后,它们被保存在类型接口的映射中。 {}Interface{} 类型表示空接口。即该接口可以保存为任意类型。对保存鸟或猪实例的接口变量执行断言操作。如果断言对象属于断言中指定的类型,则返回转换为断言对象类型的接口。否则,返回转换为断言对象类型的接口。 {}对于指定的断言类型,断言的第二个参数返回 false。例如,以下代码:

 var obj interface = new(bird)
f, isFlyer := obj.(Flyer) 

代码中,new(bird)创建了一个*bird类型的Bird实例,该实例存储在interface{}类型的obj变量中。使用 obj.(Flyer) 类型断言将 obj 转换为 Flyer 接口。如果转换成功,f为Flyer接口的类型,isFlyer表示转换是否成功,bool类型。

详细代码(代码1)如下:

 package main

import "fmt"

// 飛行動物のインターフェースを定義する
type Flyer interface{
    Fly()
}

// 歩行動物のインターフェースを定義する
type Walker interface{
    Walk()
}

// 鳥を定義する
type bird struct{
}

// 飛行動物のインターフェースを実装する
func (b * bird) Fly(){
    fmt.Println("bird: fly")
}

// 鳥にWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (b * bird) Walk(){
    fmt.Println("bird: walk")
}

// ブタを定義する
type pig struct{
}

// ブタにWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (p * pig) Walk(){
    fmt.Println("pig: walk")
}

func main(){

// 名前とインスタンスのマッピングを作成する
    animals := map[string]interface{}{
        "bird": new(bird),
        "pig": new(pig),
    }

    // マッピングを反復処理する
    for name, obj := range animals {

        // オブジェクトが飛行動物かどうかを判断する
        f, isFlyer := obj.(Flyer)
        // オブジェクトが歩行動物かどうかを判断する
        w, isWalker := obj.(Walker)

        fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker)

        // 飛行動物の場合は、飛行動物インターフェイスを呼び出す
        if isFlyer {
            f.Fly()
        }

        // 歩行動物の場合は、歩行動物インターフェースを呼び出す
        if isWalker {
            w.Walk()
        }
    }
} 

代码说明如下:

  • 第 6 行定义了飞行动物界面。
  • 第 11 行定义了动物行走的界面。
  • 第16行和第30行分别定义了鸟和猪两个对象,分别实现了飞行动物和行走动物的界面。
  • 第 41 行是将对象名称映射到对象实例的映射。例如鸟类和猪。
  • 第 47 行开始扫描地图。 obj 是一个接口类型。 {}
  • 第 50 行使用类型断言获取 f,并且 Flyer 和 isFlyer 类型的断言成功。
  • 第 52 行使用类型断言来获取 w,并且类型 Walker 和 isWalker 断言成功。
  • 第 57 行和第 62 行根据断言对于飞行动物和步行动物是否成功来调用该接口。

这是代码输出:

name: pig isFlyer: false isWalker: true
pig: walk
name: bird isFlyer: true isWalker: true
bird: fly
bird: walk

将接口转换为另一种类型

代码 1 允许您将接口转换为通用指针类型。例如,要将Walker接口转换为*pig类型,请参见以下代码。

 p1 := new(pig)

var a Walker = p1
p2 := a.(*pig)

fmt.Printf("p1=%p p2=%p", p1, p2) 

代码说明如下:

  • 第3行,由于pig实现了Walker接口,因此可以隐式转换为Walker接口类型并存储在a中。
  • 第4行,a中存储的是*pig本体,因此可以转换为*pig类型。
  • 第6行,比较发现p1和p2中的指针是相同的。

如果您尝试在上面的代码中将 Walker 类型转换为 *bird 类型,您将收到运行时错误。请参阅下面的代码。

 p1 := new(pig)

var a Walker = p1
p2 := a.(*bird) 

运行时出错:

panic: interface conversion: main.Walker is *main.pig, not *main.bird

错误信息的意思是接口转换时,main.Walker接口的内部存储将是*main.pig而不是*main.bird。

因此,当将接口转换为另一种类型时,接口中存储的实例对应的类型指针必须是对应的要转换的类型指针。

总结

接口和其他类型的转换在 Go 中是免费的,只要它们得到完全实现。

接口断言类似于流控制。但是,如果您看到大量类型断言,则应该使用更有效的类型分支切换器。

通俗易懂的讲解《Go语言接口与类型之间的转换》!您必须观看的 2 个最佳视频

一緒に学ぶGo言語入門 ~その9:メソッドとインターフェース(前編)~
https://www.youtube.com/watch?v=ntLdZ_qyqTM&pp=ygVAIEdvIOiogOiqnuOBruOCpOODs-OCv-ODvOODleOCp-ODvOOCueOBqOWei-OBrumWk-OBruWkieaPmyZobD1KQQ%3D%3D
Go (Golang) io.Reader インターフェイス
https://www.youtube.com/watch?v=O-MeKOuvzYE&pp=ugMICgJqYRABGAHKBUAgR28g6KiA6Kqe44Gu44Kk44Oz44K_44O844OV44Kn44O844K544Go5Z6L44Gu6ZaT44Gu5aSJ5o-bJmhsPUpB