golang数据类型
2020-07-10 22:08:56 0 举报
AI智能生成
Golang the good parts
作者其他创作
大纲/内容
词法元素
token
token是go语言的词汇构成
四类token
标识符 identifiers
关键字 keywords
运算符与标点符号 operators and punctuation
字面量 literals
字面量
值字面量
用于表示值本身的字符序列
基本(值)字面量
布尔
true | false
整数
123
浮点数
3.14
字符
‘中’
字符串
“中国”
复数
1.2e-3i
复合(值)字面量
固定数组
[3]int{1,2,3}
[...]int{1,2,3}
可变数组(切片)
[]int{1,2,3}
字典
map[int]string{1:"a", 2:"b", 3:"c"}
函数
func(x,y int)int{
return x+y
}
return x+y
}
结构体
struct{
Name string
Age int
}{"alice",21}
Name string
Age int
}{"alice",21}
整数字面量本身是无类型的,不过
赋给某个变量,如:
var num int8=1 (显示类型指定,num是int8)
var num =1(隐式类型,num是int)
有显示的类型转换,如
int16(100), 表达式结果类型是uint16
类型字面量
用于表示(自定义)类型本身的字符序列
示例
type Person interface{
Name() string
Age() int
}
Name() string
Age() int
}
chan Person
*Person
常量
常量的类型可以是基本类型,可被赋予对应的基本(值)字面量
常量声明
const nun=1
分组声明
const( ....
...
)
...
)
iota
代表连续的、无类型的整数常量
固定步幅赋值
const{
a = 1 << iota
b
c
}
a = 1 << iota
b
c
}
以const开始的常量声明语句为单位
从0开始,每赋给一个常量就递增一次
一旦跨越以const开始的常量声明语句就归零
常量表达式
仅以常量作为操作数的表达式
合法的操作数
无类型的布尔常量
无类型的数值常量
无类型的字符串常量
结果
整数+浮点数
浮点数常量
字符+整数
字符常量
整数或浮点数移位
整数常量
比较大小
布尔常量
变量
var 关键字
var name type 定义单个变量
var name1,name2, name3 type 定义多个变量
var name1,name2, name3 type = v1, v2, v3 初始化
var 类型简化
var name1, name2, name3 = v1, v2, v3
:= 符号定义
只能在函数内部使用
经常用于if for switch的初始化语句
不能用于全局变量的声明和定义
全部变量只能用var
多变量声明
name1,name2, name3 := v1, v2, v3
重复声明
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset) // redeclares offset
field2, offset := nextField(str, offset) // redeclares offset
只能用于多变量声明,至少有一个是新的non-blank变量
分组声明
var( ....
...
)
...
)
类型判断
类型断言表达式
单变量形式
value := em.(T)
有可能引发panic
双变量形式
value, ok := em.(T)
em必须为initerface类型才可以进行类型断言
s := "BrainWu"
if v, ok := interface{}(s).(string); ok {
fmt.Println(v)
}
if v, ok := interface{}(s).(string); ok {
fmt.Println(v)
}
类型开关
type-switch
type Element interface {}
func main() {
var e Element = 100
switch value := e.(type) {
case int:
fmt.Println("int", value)
case string:
fmt.Println("string", value)
default:
fmt.Println("unknown", value)
}
}
func main() {
var e Element = 100
switch value := e.(type) {
case int:
fmt.Println("int", value)
case string:
fmt.Println("string", value)
default:
fmt.Println("unknown", value)
}
}
类型转换
类型转换表达式
valueOfTypeB = typeB(valueOfTypeA)
例子
a := 5.0 //float
b := int(a)
b := int(a)
转换规则
低精度转换为高精度时是安全的,高精度的值转换为低精度时会丢失精度。
例如int32转换为int16,float32转换为int
例如int32转换为int16,float32转换为int
底层结构相同的两个类型之间可以互转
与字符串有关的转换
strconv包
基本类型
布尔类型
bool
数值类型
int 、uint
有符号和无符号目前都是32位
rune, int8, int16, int32, int64
byte, uint8, uint16, uint32, uint64
byte, uint8, uint16, uint32, uint64
不同类型 不允许相互赋值操作
float32, float64
没有float, 默认为float64
complex64,complex128
字符串
string
两种形式
反引号字符串
raw string literal
其中字符是未解释的
例子
`中国`
多行字符串用反引号 ` `
双引号字符串
Interpreted string literals
其中字符是已解释的
string[1:] 切片操作
string与[]byte 互转
字符串转数组 arr := []byte(str)
数组转字符串 str := string(arr)
参见
容器类型
数组 array
变量声明
var name [n]type
type为元素类型, 不同长度的数组视为不同类型
[...] 可以省略长度
a := [...]int{12, 78, 50}
初始值为0
var a [3]int //a:[0 0 0]
var name [n][m]type 多维数组声明
字面值
a := [3]int{1,2,3}
a := [...]int{1,2,3}
长度
len(a)
遍历
for...len
for i := 0; i < len(a); i++ {
...
}
...
}
for...range
for i, v := range a {
...
}
...
}
for i := range a { // ignores value
...
}
...
}
for _, v := range a { // ignores index
...
}
...
}
for range a { // ignores index and value
...
}
...
}
array 是值类型
数组间的赋值是值的赋值,而不是指针
切片 slice
变量声明
var name []type
切片的零值为nil。 对于nil,len和cap函数都将返回0
字面值
s := []byte { 'a', 'b', 'c' }
s := []int{1,2,3}
make
func make([]T, len, cap) []T
s := make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}
// s == []byte{0, 0, 0, 0, 0}
s := make([]byte, 5)
// len==cap==5
// len==cap==5
append
func append(s []T, x ...T) []T
容量不足则自动扩展
a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}
copy
func copy(dst, src []T) int
切片
s := []int{1,2,3,4,5}
s1 := s[2:4] //s1 {3,4}
s1 := s[2:4] //s1 {3,4}
遍历
for...len
同array
for...range
同array
slice 属于引用类
参考资料
字典 map
变量声明
var name map[string]int
字面量
m := map[int]string{1:"a", 2:"b", 3:"c"}
make
name := make( map[string]int )
设置|修改
m["key"] = 1
读取
v := m["key"]
如果k在m中不存在,则将value类型的零值赋值给v
逗号表达式
if v, ok := m["key"]; ok {
fmt.Printf("v:%+v\n", v)
} else {
fmt.Printf("不存在\n")
}
fmt.Printf("v:%+v\n", v)
} else {
fmt.Printf("不存在\n")
}
删除
delete(m,"key")
遍历
for k := range m {
...
}
...
}
for k,v := range m {
...
}
...
}
for _,v := range m {
...
}
...
}
map 属于无序的, 每次打印都会不一样
map 属于引用类型
容器库
list
heap
ring
抽象数据类型(ADT)
ADT
定义
为类型的属性和可对类型执行的操作提供一个抽象的描述。不受特定的实现和编程语言的约束。
这种正式的抽象描述被称为抽象数据类型(Abstract Data Type,ADT)
这种正式的抽象描述被称为抽象数据类型(Abstract Data Type,ADT)
组成
数据的定义
操作的定义
封装数据和操作。
使用步骤
定义一个数据类型。数据+操作
开发一个实现该ADT的编程接口
编写代码实现这个接口
Go中的ADT
数据与操作的结合,通过method
方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
使用method,再结合interface,可以实现多态
数据与操作的结合,通过function
将指向数据的指针,作为函数的第一个参数
例子
注:经编译器处理后,两种方式是等价的,方法会被转换成函数
结构体
定义结构体
例子
type Circle struct {
x int
y int
Radius int
}
x int
y int
Radius int
}
占位(padding)
struct {
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}
匿名内嵌字段(embedded fields)
struct {
T1 // field name is T1
*T2 // field name is T2;T2不能是接口不能是指针
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}
T1 // field name is T1
*T2 // field name is T2;T2不能是接口不能是指针
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}
标记(tag)
struct {
x, y float64 "" // an empty tag string is like an absent tag
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
x, y float64 "" // an empty tag string is like an absent tag
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
访问控制
导出(exported)
结构名、字段名 首字母大写:包外可见
非导出(non-exported)
结构名、字段名 首字母小写:仅包内可见
通过字面量变量创建
KV形式
c :=Circle {
x: 100,
y: 100,
Radius: 50, // 注意这里的逗号不能少
}
x: 100,
y: 100,
Radius: 50, // 注意这里的逗号不能少
}
c :=Circle {
Radius: 50,
}
// x,y 省略,初始化为对应的零值
Radius: 50,
}
// x,y 省略,初始化为对应的零值
顺序形式
c := Circle {100, 100, 50}
通过关键字创建变量
var
var c Circle
new
c := new(Circle)
// c是一个指针,等同于下面这种形式
c := &Circle{}
// c是一个指针,等同于下面这种形式
c := &Circle{}
结构体复值
按值拷贝,浅拷贝。
例子
a := Circle {100, 100, 50}
b := a
b := a
a 和 b 是两个独立变量,a的值被拷贝到b(按位拷贝)
结构体大小
fmt.Println(unsafe.Sizeof(c))
参考链接
函数
定义
函数定义
func Add(x,y int)int{
return x+y
}
return x+y
}
返回多值
如果声明了返回变量名, 则返回时可以不带变量名 【不推荐用】
如果有error,通常放在最后
可变参数
func sum(arg ...int){ }
sum(1, 2, 3)
sum([]int{1, 2, 3)...)
支持: sum(slice...)
不支持:sum(array...)
访问控制
函数名首字母大写是public,函数名首字母小写private私有方法
注意事项
不支持默认值参数。
不支持函数重载。
不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数
函数类型
函数是一类对象
定义函数类型
type Op func(int,int) int
显示函数签名
函数类型又叫函数签名
func add(a,b int) int {
return a + b
}
func main() {
fmt.Printf("%T\n",add) //打印效果 func(int, int) int
}
return a + b
}
func main() {
fmt.Printf("%T\n",add) //打印效果 func(int, int) int
}
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数和类型相同)
函数变量
场景:引用匿名函数
f := func(a,b int){
return a+b
}
return a+b
}
场景:设置回调函数
函数变量经常作为回调函数,以入参传入
func do(f Op,a,b int) int { //定义一个函数,第一个参数是函数类型Op
return f(a,b) //函数类型变量可以直接用来进行函数调用
}
return f(a,b) //函数类型变量可以直接用来进行函数调用
}
匿名函数与闭包
核心概念
闭包示意图
闭包是匿名函数与匿名函数所引用环境的组合 。
匿名函数有动态创建的特性,该特性使得匿名函数不用通过参数传递的方式,就可以直接引用外部的变量。
匿名函数即函数类型的字面量
匿名函数,一定是出现在闭包里;也就是只能在函数体里面定义一个匿名函数
场景1-返回
return func
func fib() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
场景2-并发
go func
func foo( ){
go func( ){
}( )
}
go func( ){
}( )
}
场景3-延迟
defer func
func foo( ){
defer func( ){
}( )
}
defer func( ){
}( )
}
场景4-函数变量
f :=func(){}
func foo( ){
f :=func( ){ }
}
f :=func( ){ }
}
场景5-函数字面量
func(){}()
func foo( ){
func( ){
}( )
}
func( ){
}( )
}
错误处理
错误不像异常,因为错误没有什么特别之处,而未处理的异常可能导致程序崩溃. -Rob Pike
错误
错误用error来表示,error是一个接口
type error interface {
Error() string
}
Error() string
}
程序有错误则返回error,无错误则返回nil
多值返回时,将error放最后
惯用法
v,ok = foo()
if !ok{
return;
}
bar()
if !ok{
return;
}
bar()
生成error
errors.New("error ...")
fmt.Errorf(format string, a ...interface{})
go内置error很简单,有个开源库提供了强大的错误处理能力
https://github.com/pkg/errors
异常
异常处理机制( panic/recover )
panic
panic 表示程序遇到严重错误
发生 panic 时,程序的行为
1)程序正常执行终止,进入 panic 状态.
2)从栈顶到栈底倒序执行 defer 函数.
3)如果有 defer 函数调用了 recover 函数, 那么 panic 被捕获, panic状态解除, defer 所在函数正常返回(程序进入正常状态).
4)否则直到 main 函数,然后异常退出.
5)打印栈跟踪信息,之后清理栈.
2)从栈顶到栈底倒序执行 defer 函数.
3)如果有 defer 函数调用了 recover 函数, 那么 panic 被捕获, panic状态解除, defer 所在函数正常返回(程序进入正常状态).
4)否则直到 main 函数,然后异常退出.
5)打印栈跟踪信息,之后清理栈.
recover
仅在 defer函数中有效,获取panic 输入值
配合使用
panic 函数的参数 interface{} 类型,可以传给它任何值.
对应的处理函数是recover,如果它捕获了panic,那么返回值是传给panic函数的值.
如果没捕获panic,那么返回值是nil. 尽量用error接口的值传给panic函数,以备recover函数捕获后分析.
对应的处理函数是recover,如果它捕获了panic,那么返回值是传给panic函数的值.
如果没捕获panic,那么返回值是nil. 尽量用error接口的值传给panic函数,以备recover函数捕获后分析.
defer func(){
if err := recover(); err != nil{
}
}
panic(errors.New("some errors"))
if err := recover(); err != nil{
}
}
panic(errors.New("some errors"))
例子
regexp 是Go语言的正则表达式包,正则表达式需要编译后才能使用,而且编译必须是成功的,表示正则表达式可用。
处理为error
func Compile(expr string) (*Regexp, error)
处理为panic
func MustCompile(str string) *Regexp
Must惯用法
Must 开头的函数一般要求正确的输入,如果输入不正确则引发 panic
defer
函数执行到最后时, 会按照逆序执行defer
场景1-释放资源
defer file.close()
场景2-捕获异常
defer func(){
if err := recover(); err != nil{
}
}
panic(errors.New("some errors"))
if err := recover(); err != nil{
}
}
panic(errors.New("some errors"))
方法
例子
// Receiver of custom defined struct type.
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
方法声明
声明形式
func 接收器 方法原型 方法正文
可以给类型T和*T声明一个方法
要求
T必须是定义的类型
T的定义必须跟方法定义的在同一个包中
T不能是指针类型
T不能是interface类型。
T的定义必须跟方法定义的在同一个包中
T不能是指针类型
T不能是interface类型。
这些类型不能声明方法
内置基本类型,例如int, string,因为我们不能在标准库里声明方法;
interface类型,但是一个interface可以拥有方法。
未定义类型,包括各种未命名的组合类型
interface类型,但是一个interface可以拥有方法。
未定义类型,包括各种未命名的组合类型
类型别名
用type定义类型的别名时,别名类型不会拥有原始类型的方法。
方法原型
方法原型可以视为一个没有func关键字的函数原型
例子
Pages和SetPages的方法原型如下:
Pages() int
SetPages(pages int)
Pages() int
SetPages(pages int)
方法值和方法调用
例子
b := Book{100}
方法值
b.Pages | b.SetPages
方法调用
b.Pages() | b.SetPages(99)
接收器(receiver)
T和*T被称作为他们定义的相应方法的接收器(Receiver).
T被称为类型T和*T声明的所有方法的接收器的基础类型
T被称为类型T和*T声明的所有方法的接收器的基础类型
值接收器
T
只要receiver是值类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝整个底层数据结构的,
方法内部访问的和修改的都是实例的副本。所以,如果有修改操作,不会影响外部原始实例。
方法内部访问的和修改的都是实例的副本。所以,如果有修改操作,不会影响外部原始实例。
例子
指针接收器
*T
只要receiver是指针类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝指针,
方法内部访问的和修改的都是原始的实例数据结构。所以,如果有修改操作,会影响外部原始实例。
方法内部访问的和修改的都是原始的实例数据结构。所以,如果有修改操作,会影响外部原始实例。
例子
Go中所有参数都是值传递,接收器参数(T和*T)也是通过拷贝传入的
T 还是 *T ?
如果很难确定方法是否应该使用指针接收器还是值接收器,那么一般选择指针接收器(*T)方式。
一些考虑因素
指针副本太多可能会导致垃圾收集器的工作量增加。
如果值接收器类型的大小很大,则接收器参数复制成本可能不可忽略。指针类型都是小型的。
如果值接收器类型的大小很大,则接收器参数复制成本可能不可忽略。指针类型都是小型的。
惯用法
在其他语言中接收器一直被明确为this,在go语言中不推荐这么做,一般用一个简单字母表示
方法集
隐式函数
对每一个方法来说,编译器将为它声明一个相应的隐式函数
func (b Book) Pages() int {
return b.pages
}
return b.pages
}
方法->函数
func Book.Pages(b Book) int {
return b.pages // the body is the same as the Pages method
}
return b.pages // the body is the same as the Pages method
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
b.pages = pages
}
方法->函数
func (*Book).SetPages(b *Book, pages int) {
b.pages = pages // the body is the same as the SetPages method
}
b.pages = pages // the body is the same as the SetPages method
}
隐式方法
对于声明给值接收器类型T的每一个方法,编译器会隐式的给*T声明一个有相同名字的方法。
func (b Book) Pages() int {
return b.pages
}
return b.pages
}
生成隐式方法,T->*T
func (b *Book) Pages() int {
return Book.Pages(*b)
}
return Book.Pages(*b)
}
方法集
每个类型都有一个方法集。
方法集是由所有的声明的方法的方法原型组成的。这些方法包含显示声明的和隐式声明的
方法集是由所有的声明的方法的方法原型组成的。这些方法包含显示声明的和隐式声明的
规则
T的方法集: 由接收器T定义的方法
T=T
*T的方法集: 由接收器T和*T定义的方法
*T=T+*T
例子
Book的方法集
Pages() int
*Book的方法集
Pages() int //隐式生成
SetPages(pages int)
SetPages(pages int)
嵌套struct中的方法集
若类型 S 包含匿名字段 T,则 S 的方法集包含 T 的方法集。
若类型 S 包含匿名字段 *T,则 S 的方法集包含 T 和 *T 方法集。
不管类型 S 中嵌入的匿名字段是 T 还是*T,*S 方法集总是包含 T 和 *T 方法集。
若类型 S 包含匿名字段 *T,则 S 的方法集包含 T 和 *T 方法集。
不管类型 S 中嵌入的匿名字段是 T 还是*T,*S 方法集总是包含 T 和 *T 方法集。
例子
这些类型的方法集是空的
内置基本类型
定义的指针类型
基础类型是interface或者指针类型的指针
未定义的数组,切片,函数和通道类型
定义的指针类型
基础类型是interface或者指针类型的指针
未定义的数组,切片,函数和通道类型
interface类型的方法集定义了它的接口;
非interface类型的方法集 决定了 该类型实现了哪些interface
非interface类型的方法集 决定了 该类型实现了哪些interface
参考链接
链接1
链接2
接口
什么是接口
Go 接口是一组方法的集合,可以理解为抽象的类型。
非侵入性
任何类型,只要实现了该接口中方法集,那么就属于这个类型。
无需在该类型上添加显示声明。
无需在该类型上添加显示声明。
定义
type Name interface{
//一组方法集合
}
//一组方法集合
}
例子
多态
多态就是同一个接口,使用不同的实例而执行不同操作
鸭子类型
go没有 implements, extends 这些显式的关键字,go的实现方案是一种鸭子类型:看起来像鸭子, 那么它就是鸭子
泛型
Go可以用空接口和类型断言来实现一种动态泛型(区别于基于模板的静态泛型)
空接口
interface{}
空接口可以指向任何数据对象
例子
类型断言
接口值可有包含各种不同的具体类型值,而类型断言就是用于从接口中动态的区分出各种具体的类型,从而可以使用具体的类型。
参考链接
单变量
y := x.(T)
检查x的动态类型是否是T,其中x必须是接口值。
T是具体类型
类型断言检查x的动态类型是否等于具体类型T。
断言成功
类型断言返回的结果是x的动态值,其类型是T。
断言失败
panic
T是接口类型
类型断言检查x的动态类型是否满足T
断言成功
x的动态值不会被提取,返回值是一个类型为T的接口值
断言失败
panic
无论T是什么类型,如果x是nil接口值,则类型断言失败
双变量
y, ok := x.(T)
断言成功
ok=true,y值同单值形式
断言失败
ok=false,y=nil,不会panic
惯用法
if f, ok := w.(*os.File); ok {
// ... use f ...
}
// ... use f ...
}
类型开关
type switch
例子
switch x.(type){
case nil: // 如果x是nil
case int, uint:
case bool:
case string;
default: //没有匹配上
}
//case的顺序是有意义的,因为可能同时满足多个接口,不可以用fallthrough, default的位置无所谓。
case nil: // 如果x是nil
case int, uint:
case bool:
case string;
default: //没有匹配上
}
//case的顺序是有意义的,因为可能同时满足多个接口,不可以用fallthrough, default的位置无所谓。
例子
提取具体的值
switch x := x.(type) { /* ... */ }
//前面的x是一个局部变量,因为switch创建了一个词法域
//x的类型就是每个case的类型
//前面的x是一个局部变量,因为switch创建了一个词法域
//x的类型就是每个case的类型
值类型与引用类型
值类型
所有基本类型
固定数组 array
结构体
引用类型
可变数组 slice
字典
函数
接口
指针
channel
0 条评论
下一页
为你推荐
查看更多