数据类型
- 布尔型:只能取
false
或true
.例var b bool = true
. - 数字类型:
- int - 整型
- float32/float64 - 浮点型
- 原生支持复数
- 字符串类型
- 派生类型
- 指针类型
- 数组类型
- 切片类型
- 结构类型
struct
channel
类型- 函数类型
- 接口类型
interface
map
类型
go
语法中一些特殊的数字类型
类型 | 长度 |
---|---|
byte | 类似 `uint8` |
rune | 类似 `int32` |
uint32 | 或 64位 |
int | 取决于操作系统 |
uintptr | 无符号整型,用于存放一个指针 |
字符串类型
go
中字符串类型是只读的.采用UTF-8
编码.每个字符
对应一个rune
类型.
1 |
|
使用range
关键字迭代字符串时,每次得到一个字符,rune
类型,循环的索引值是字节为单位的偏移量.该rune
类型的字符被称为代码点.
1 |
|
变量及声明
当一个变量被var
声明之后,系统自动赋予它零值:
bool
为false
- 整型为
0
- 浮点型为
0.0
- 字符串为空
""
- 派生类型均为
nil
如果你想交换两个变量的值,则可以简单的使用:a,b = b,a
nil
值
语法错误:未指定类型 var x = nil // 错误
给一个nil
指的slice
添加元素是合法的,但map
不行.
1 |
|
字符串拼接
运算符
+
1
str := "go" + "语" + "言" // 输出 "go语言"
由于字符串是只读的,因此会产生很对碎片化的无用字符串,等待垃圾回收,性能较差.
fmt.Sprintf()
内部使用[]byte实现,不像运算符会产生很多临时字符串,但内部逻辑复杂,很多判断,且为了兼容数据类型使用了interface
,性能一般strings.Join()
会计算最终字符串的大小,先申请合适大小的内存,在逐个字节的填入,为了构造这个数组,性能一般.bytes.Buffer
1
2
3
4
5
6var buf bytes.Buffer
buf.WriteString("go")
buf.WriteString("语")
buf.WriteString("言")
fmt.Print(buffe.String())比较理想.内部实现对内存增长有优化,若能预估最终长度,可以使用buffer.Grow()来设置capacity.
string.Builder
1
2
3
4
5
6var bs strings.Builder
bs.WriteString("go")
bs.WriteString("语")
bs.WriteString("言")
fmt.Print(bs.String())比较理想.内部使用slice来保存和管理内容,slice指向底层的数组.同样提供了Grow()来设置容量.但
strings.Builder
是非线程安全的,性能上和bytes.Buffer
相差无几.
数组
类型 + 长度 定义一个数组类型.
所以[5]int
和[10]int
是两个不同的类型.
注:特殊的,我们可以使用[10]interface{}
来定一个存放任意类型数据的数组,但使用值时需要先做类型判断.
声明数组
var
关键字var arr1 [5]int
arr1的类型是[5]int
new
关键字var arr1 = new([5]int)
arr1的类型是*[5]int
其他
1
2
3var arrAge = [5]int{18, 20, 30, 35}
var arrLazy = [...]int{5, 6, 7, 8, 9}
var arKeyValue = [5]string{3:"Chris", 4:"Ron"}
切片
切片的底层是一个数组,自身包含:①对数组的引用 ②起始偏移量 ③中止偏移量 ④切片容量
可以理解为切片是对数组的一个动态窗口.
有点
(多个)切片可以引用同一个数组,因此不需要额外的内存空间,比直接使用数组高效,因此go
中更多的使用切片.
注:切片本身就是一个引用类型, 指向切片的指针没有意义.类似指向interface
的指针也没有意义.
声明切片
var slice1 []int
只需要指定类型,而不能指定长度.(指定了长度就是数组了!!!)
一个切片在未初始化前,值是nil
,长度是0
.
初始化切片的操作:var slice1 []int = arr1[start:end]
包含start
索引,不包含end
索引.即slice1包含arr1[start..end-1]的元素.
var slice2 = []int{1,2,3}
也可以初始化切片.切片将指向底层创建的一个匿名数组.
注:这里的var slice2
是var slice2 []int
的简写.
作为数据源的数组还未定义时,可以使用make
关键字创建切片:var slice2 = make([]int, 5)
第二参数表示切片的长度,同样这里将切片类型var slice2 []int
省略了,简写作var slice2
.
更常用的,简写为slice2 := make([]int, 5)
.
make
关键字创建切片的完整语法
make([]int, 10, 50)
,第三个参数50
指定了底层数组的大小,第二个参数10
指定了切片的初始长度.
从数组或切片中生成一个新的切片:a[low : high : max]
例如:
1 |
|
这里的切片b的长度是3-1=2,容量是5-1=4. 1
, 3
, 5
都是索引.
切片重组(reslice)
slice1 := make([]type, start_length, capacity)
start_length
是切片的初始长度,capacity
是底层数组的大小也是切片的容量.
改变切片长度的操作称之为切片重组,但切片重组时不能超过底层数组的容量.
1 |
|
切片扩容
func append(s[]T, x ...T) []T
当使用append
函数时,可能会引发切片扩容,扩容后的切片将指向一个新的底层数组,跟之前的底层数组不再有关联.
字典map
声明语法:var map1 map[string]int
初始化语法: var map2 = make(map[string]int, 10)
对值为nil
的切片append
是合法的(触发了切片扩容),但对值为nil
的map赋值是非法的.
1 |
|
访问map的元素
下标访问: val = aMap[key]
判断下标是否存在:
1 |
|
1 |
|
删除key
delete(map1, "key")
.若key不存在也不会报错.
go
语法陷阱
range
循环中生成的值是真实集合的拷贝,它不是原有元素的引用.更新这些值并不会修改原来得值.使用这些值的地址并不会得到原有数据的指针.
1 |
|
defer
- 规则一 当
defer
被声明时,其参数就会被实时解析 - 规则二
defer
执行顺序为先进后出,即栈LIFO
- 规则三
defer
可以读取有名返回值,即可以改变有返回参数的值(不建议如此增加代码复杂度)
函数
- 普通带函数名的函数
- 匿名函数或者
lambda
函数 - 方法(methods)
函数可以作为类型使用type addFunc func(int, int) int
这里不需要函数体.
函数是一等值(first-class value
):它们可以赋值给变量:add := addFunc
go
默认使用值传递来传递形参.
函数调用时,若希望在函数内改变原来的数据,则可以传递数据的指针到函数.
切片/字典/接口/通道(slice/map/interface/channel)模式使用引用传递(不需显示指出指针)
名称 | 说明 |
---|---|
close | 用于关闭`channel` |
len、cap | 返回某个类型的长度/数量(字符串、数组、切片、map和channel); 返回某个类型的容量(只能用于切片、map) |
new、make | 用于分配内存: 1. `new`用于值类型和用户定义类型,如自定义结构体.`new(T)`分配类型为T的零值并返回其地址,即指向类型T的指针. 其也可以被用于基本类型:`v := new(int)`. 2. `make`用于内置引用类型,如切片、map、channel.`make(T)`返回类型T的初始化后的值,因此它比`new`进行更多的工作. 二者都是内存的分配(堆上),但`make`只用于slice、map、channel的初始化(非零值);而`new`用于类型的内存分配,并且内存置为零. |
copy、append | `copy`复制切片,`append`添加元素到切片 |
panic、recover | 用于错误处理机制 |
递归与回调
使用递归函数常常遇到栈溢出的错误,一般使用惰性求值的技术解决.在go
中可以使用channel
和gorutine
.
闭包
1 |
|
type
关键字
复习:go
语言中的基本数据类型:布尔值、数值(整数、浮点数、复数)、字符串.
另有一些基于上述基本类型衍生的:byte、uintptr、rune、error等
自定义类型
type IZ int
然后可以使用var a IZ = 5
变量a
拥有底层类型int
,类型IZ
和int
之间也可以显示的转换.
例如:将IZ
类型转换成int
类型:b := int(a)
.
1 |
|
可以看出来,类型IZ
和类型int
是两个不同的类型.
类型别名
type IZ int
语法中,新类型IZ
无法访问原类型int
的方法.
此时使用别名语法:type IZ = int
.
常用的类型:
- 类型可以是基本类型, 如:bool、int、float、string;
- 结构化的(复合类型), 如:struct、array、slice、map、channel;
- 只描述类型的行为的, 如:interface;
结构化的类型没有真正的值,使用nil
作为零值.
注意在go
语言中不存在类型继承.
结构体struct
结构体所包含的数据在内存中是连续块存在的.
接口interfce
接口中包含若干只有定义而没有实现的函数.
结构体不需要显示的声明自己实现了某接口.相反,只要结构体实现了某接口的所有函数,即可以认为结构体实现了该接口.
1 |
|
接口可以嵌套接口,但不能嵌套结构体.
嵌套接口时,多个不同的接口可以有包含相同签名的函数go 1.14
实现.
接口类型变量的类型转换
v := a.(T)
没有断言的强制类型转换,可能触发panic
:invalid type assertion
正确的做法是:
1 |
|
当a
可能含有多种可能的类型时:
1 |
|
测试一个值是否实现了某接口
1 |
|
接收器
1 |
|
对于一个接结构体T
, 不论变量是T类型
还是*T类型
,都可以调动值方法或指针方法.
1 |
|
但若变量是一个接口类型
1 |
|
接口类型的变量无法调用指针接收器的函数:
1 |
|
- 何时使用值类型接收器
- 接收器本身就是引用类型(map、func、channel).
- 接收器是切片,且该方法不会触发切片重组或扩容.
- 接收器是一个小数组或原生的结构体类型且没有可修改的字段或指针,又或接收器是基本数据类型(bool、int、float、string)
- 何时使用指针类型
- 若方法需要修改接收器,则必须是指针类型.
- 若接收器包含
sync.Mutex
或包含了锁的结构体,则必须是指针类型,以避免拷贝. - 若接收器是一个大结构体或大数组,为性能考量,需要只用指针类型.
- 若接收器是结构体、数组、切片,blablabla…,使用指针类型.
嵌套结构体中方法提升
S(T) | S(*T) | |
---|---|---|
S包含T的方法集 | Yes | Yes |
S包含*T的方法集 | No | Yes |
*S包含T的方法集 | Yes | Yes |
*S包含*T的方法集 | Yes | Yes |
当变量类型本身就是引用类型是,上述方法集约束是强约束.
否则,go
有语法糖会自动转换.
挖坑待读
- 图解Go语言内存分配
- 码农桃花源
- 为什么遍历 Go map 是无序的?
for range map
启动时会随机选择map的起始位置fastrand()
- Golang垃圾回收
- 图解Golang的GC算法
- 为什么golang的gc不用stw?
- Golang 垃圾回收剖析
- Go 语言的实现为何使用Plan 9的汇编器?
- Golang调度器
- Go 语言调度(一): 系统调度
- Go调度器系列(2)宏观看调度器