🐶
Go语言精华(Essential Go)
  • 《Go语言基础》
  • 第一部分 语法基础
  • 01 准备开始
    • Windows安装和设置
    • macOS安装和设置
    • Linux安装和设置
    • Hello, World!
    • 来场有导游的Golang旅行
    • 离线访问文档
    • 在编程操场上运行Go
    • GOPATH, GOROOT, GOBIN
  • 02 基本类型
    • 布尔型
    • 整型
    • 浮点数
    • 字符和符号(rune)
    • 字符串
    • 常量
    • 枚举(不支持)
    • 数组
    • 切片
    • 映射
    • 结构体
    • 接口
    • 空接口
    • 指针
    • 联合体(不支持)
    • 数据通道
    • 零值
    • 类型转换
    • 类型别名
  • 03 变量(Variable)
    • 变量声明基本形式
    • 多个变量一起赋值
    • 空白标识符
  • 04 常量(Const)
    • 有类型vs无类型常量
    • iota
    • 模拟枚举
  • 05 字符串(String)
    • 在字符串中查找另一字符串
    • 字符串比较
    • 大小写转换
    • 字符串转整型和浮点型
    • 字符串修剪(删除字符或子串)
    • 字符串文本替换
    • 字符串分割和连接
    • 文本格式化
    • 文本解析
    • 逐行读取文件
    • 规范化换行符
  • 06 指针(Pointer)
    • 指针基础
    • 指针解引用(dereference)
    • 指针方法vs值方法
  • 07 数组(Array)
    • 创建数组
    • 数组索引
    • 多维数组
  • 08 切片(Slice)
    • 创建切片
    • 长度与容量
    • 追加元素
    • 切片过滤
    • 从切片删除元素
    • 复制切片
    • 零值切片
    • 切片的技巧
    • 通过预分配内存优化切片
  • 09 映射(Map)
    • 声明和初始化映射
    • 创建映射
    • 通过键获取值
    • 获取映射大小(映射条目数)
    • 拷贝一个映射
    • 用range遍历映射
    • 从映射中删除
    • 将映射(Map)用作集合(Set)
    • 以切片为值的映射
    • 并发访问映射
    • 映射的零值
  • 10 结构体(Struct)
    • 基本声明
    • 结构体字面量
    • 匿名结构体
    • 组合和嵌入
    • 可导出vs不可导出字段(公有vs私有)
    • 结构体标签
    • 复制结构体(创建一份拷贝)
    • 空结构体
    • 结构体方法
  • 11 接口(Interface)
    • 简单接口
    • 从接口检测基础类型
    • 确认某类型已实现某接口
  • 12 空接口(Empty Interface)
  • 13 if, switch, goto
    • if语句
    • switch语句
    • goto语句
  • 14 for, while循环
    • for循环的不同形式
    • break和continue
    • while循环
  • 15 range语句
    • 遍历字符串、切片、映射
    • 遍历字符串
    • 遍历切片
    • 遍历映射
    • 遍历数据通道
  • 16 函数
    • 参数
    • 返回值
    • 命名返回值
    • 可变参数函数
    • 函数字面量
    • 函数类型的变量
    • 闭包
  • 17 方法
  • 18 错误处理
    • 返回错误
    • 创建标准错误值
    • 自定义错误类型
    • 错误处理
    • 短程序中的错误处理
    • 将调用堆栈添加到错误消息
    • 编写良好的错误消息
  • 19 延迟调用(Defer)
    • 深入defer
    • defer的陷阱
  • 20 恐慌(panic)和恢复(recover)
    • 恐慌(panic)
    • 恢复(recover)
    • 从恐慌中恢复
    • 恐慌的使用场景
  • 21 并发
    • 你好,Go程
    • 创建协程
    • 等待协程结束
    • 用信号量限制并发
  • 22 数据通道(channel)和多路选择(select)
    • 使用range从channel读数据
    • 利用select为读channel设置超时
    • 关闭channel
    • 有缓冲vs无缓冲channel
    • 利用select非阻塞接收数据
    • 利用chan struct{}实现信号通道
    • 检查通道中是否有可用数据
    • channel使用惯例
  • 23 互斥锁(Mutex)
    • 读写互斥(RWMutex)
    • 互斥锁的陷阱
    • 检测竞争
  • 24 包
    • 使用go get将包安装到本地
    • 导入包
    • 创建包
    • 包初始化
    • 包命名最佳实践
  • 第二部分 常用标准库
    • 25 文件和I/O
      • 读文件
      • 写文件
      • 文件操作
      • 目录操作
      • 文件路径操作
      • I/O相关的接口
    • 26 时间和日期
      • 时间日期基本操作
      • 时间格式化
      • 时间字符串解析
      • 像strftime那样格式化时间
      • 时间和日期比较
    • 27 命令行传参
    • 28 记日志
    • 29 执行命令
    • 30 十六进制和base64编码
    • 31 JSON
    • 32 XML
    • 33 CSV
    • 34 YAML
    • 35 SQL
    • 36 HTTP客户端
      • HTTP POST
      • PUT请求发送JSON对象
    • 37 HTTP服务端
    • 38 文本和HTML模板
    • 39 反射(Reflection)
      • 原始类型
      • 指针
      • 结构体
      • 切片
      • reflect.Kind
      • 反射的用处
    • 40 上下文(Context)
      • 创建上下文
      • 用上下文为HTTP请求设置超时
      • 用上下文携带值
      • 编写可取消的函数
      • 上下文就是带值的树
      • context.TODO() vs. context.Background()
    • 41 fmt包
      • fmt基本用法
      • Stringer接口
    • 42 测试
      • 基本测试
      • 表格驱动的单元测试
      • 使用setUp和tearDown函数
      • 性能测试
      • 样例测试(自注释测试)
      • 测试HTTP请求
      • 在测试中设置/重置模拟(mock)函数
      • 以HTML格式查看代码覆盖率
    • 43 利用cgo在Go中调用C
      • cgo上手教程
      • 从Go调用C函数
      • 全方位连接C和Go代码
    • 44 使用pprof分析Go程序
      • CPU和内存基本分析
      • 使用benchmark生成分析文件
      • 内存基本分析
      • 设置CPU/Block分析速率
      • 访问分析(profile)文件
    • 45 交叉编译
    • 46 使用构建标签进行条件编译
      • 开发和生产环境编译
    • 47 内联函数
    • 48 用sync.Pool以获得更好性能
    • 49 gob
    • 50 插件机制
    • 51 HTTP服务中间件
      • 一般处理(handler)函数
      • 中间件里计算handler函数的耗时
      • CORS中间件
      • Auth中间件
      • 恢复handler以防止服务端崩溃
    • 52 Go中的Protobuf
      • 在Go中使用Protobuf
    • 53 控制台I/O
    • 54 密码学
      • 使用AES-GCM进行加密和解密
    • 55 图片处理(PNG, JPEG, BMP, TIFF, WEBP, VP8, GIF)
      • 基本概念
      • 加载和保存图片
      • 裁剪图片
      • 将图片由彩色转为灰度
      • 调整图片尺寸
    • 56 Go语言命令行工具
      • go fmt
      • go run
      • go build
      • go clean
      • go get
      • go env
      • go test
    • 57 利用持续集成(CI)服务测试代码
      • 利用Github Actions测试Go代码
    • 58 Windows图形界面(GUI)编程
      • 第一个Windows应用
  • 59 贡献者
由 GitBook 提供支持
在本页
  • 类型断言(Type assertion)
  • 类型交换机(type switch)

这有帮助吗?

12 空接口(Empty Interface)

上一页确认某类型已实现某接口下一页13 if, switch, goto

最后更新于5年前

这有帮助吗?

技术上讲,一个空接口(interface{})就是指一个不包含方法的。

由此得出的结论是,每种类型都符合interface{}。

在实践中,一般认为空接口就是Java或C#中的object类型的Go语言版,因为它结合了类型和类型的值。

空接口实际上是静态语言中的一种动态类型。

空接口也是在Go程序中实现联合体(union)的一种方式。

由于每种类型都适配interface{},所以您可以把任意类型的值赋给interface{}类型的变量。这时,您就不能在编译时确定变量的真正类型。

空接口的是nil。

基本例子:

package main

import "fmt"

func printVariableType(v interface{}) {
	switch v.(type) {
	case string:
		fmt.Printf("v is of type 'string'\n")
	case int:
		fmt.Printf("v is of type 'int'\n")
	default:
		// generic fallback
		fmt.Printf("v is of type '%T'\n", v)
	}
}

func main() {
	printVariableType("string") // string
	printVariableType(5)        // int
	printVariableType(int32(5)) // int32
}
v is of type 'string'
v is of type 'int'
v is of type 'int32'

在编译时,如果您拿到了接口(包括空接口)类型的数据,那您并不知道它真实的、底层的数据类型。

但您可以在运行时,通过类型断言获取其底层类型。

package main

import "fmt"

func printTypeAndValue(iv interface{}) {
	if v, ok := iv.(string); ok {
		fmt.Printf("iv is of type string and has value '%s'\n", v)
		return
	}
	if v, ok := iv.(int); ok {
		fmt.Printf("iv is of type int and has value '%d'\n", v)
		return
	}
	if v, ok := iv.(*int); ok {
		fmt.Printf("iv is of type *int and has value '%p'\n", v)
		return
	}
}

func panicOnInvalidConversion() {
	var iv interface{} = "string"

	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	// pass a string
	printTypeAndValue("string")
	i := 5
	// pass an int
	printTypeAndValue(i)
	// pass a pointer to int i.e. *int
	printTypeAndValue(&i)

	panicOnInvalidConversion()
}
iv is of type string and has value 'string'
iv is of type int and has value '5'
iv is of type *int and has value '0xc00002c008'
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion()
	/tmp/sandbox390319121/prog.go:23 +0x45
main.main()
	/tmp/sandbox390319121/prog.go:36 +0x9a
Program exited: status 2.

类型断言(Type assertion)

类型断言使得您能够检查空接口的值是否是某个给定的类型。

为了语法的完整性,类型交换机(type switch)有简短版本:v := iv.(int)(或者v, ok := iv.(int))。这又称为类型断言。

跟完整版的类型交换机不同的是,如果您所断言的类型与变量的实际类型不符时,简短版将会发生恐慌(panic)。

func panicOnInvalidConversion(iv interface{}) {
	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	panicOnInvalidConversion("string")
}
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion(0x4a0b40, 0x4dae60)
	/tmp/sandbox387618651/prog.go:6 +0xd6
main.main()
	/tmp/sandbox387618651/prog.go:11 +0x39

Program exited: status 2.

根据经验,您不应该尝试去发现接口类型的底层值,因为这样做刺破了抽象层。

类型交换机(type switch)

译者注

关于Switch本词的译法探讨,我们先看看英文语境中有哪些东西叫switch。

例如电路开关:控制电流应通向哪条线路(断路也是线路之一);轨道转辙器:控制铁轨岔口向哪个方向联通;网络交换机:控制输入的数据报文应该由哪个端口输出……

可见,Switch的原生语义逻辑是指,一个系统中流动的对象(如电路中的电流、铁轨上的火车、互联网上的数据报文),当流经“switch”时,“switch”负责把它导往合适的去向。

而中英词典中对switch的中文译法只有:开关、转换、调换、交换机、(轨道)转辙器。

此处,感觉只有“类型交换机”更贴切,毕竟大家都熟知网络交换机的作用,应当不会理解为“换位置、做交易”之类的交换。而“类型开关、类型转换、类型调换”相去甚远了。

switch语句可以根据被接口包装的值得实际类型进行派发。

package main

import (
	"fmt"
	"strconv"
)

func smartConvertToInt(iv interface{}) (int, error) {
	// inside case statements, v is of type matching case type
	switch v := iv.(type) {
	case int:
		return v, nil
	case string:
		return strconv.Atoi(v)
	case float64:
		return int(v), nil
	default:
		return 0, fmt.Errorf("unsupported type: %T", iv)
	}
}

func printSmartConvertToInt(iv interface{}) {
	i, err := smartConvertToInt(iv)
	if err != nil {
		fmt.Printf("Failed to convert %#v to int\n", iv)
		return
	}
	fmt.Printf("%#v of type %T converted to %d\n", iv, iv, i)
}

func main() {
	printSmartConvertToInt("5")
	printSmartConvertToInt(4)
	printSmartConvertToInt(int32(8))
	printSmartConvertToInt("not valid int")
}
"5" of type string converted to 5
4 of type int converted to 4
Failed to convert 8 to int
Failed to convert "not valid int" to int

Program exited.

亲自在编程操场练习上述代码。

接口
零值
👉
👈
点击此处