类型断言的格式
类型断言是对接口值执行的操作。从语法上看,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 中是免费的,只要它们得到完全实现。
接口断言类似于流控制。但是,如果您看到大量类型断言,则应该使用更有效的类型分支切换器。




![2021 年如何设置 Raspberry Pi Web 服务器 [指南]](https://i0.wp.com/pcmanabu.com/wp-content/uploads/2019/10/web-server-02-309x198.png?w=1200&resize=1200,0&ssl=1)

