Go 语言快速入门
已经了解Go语言的读者跳过这章。其他编程语言的读者可以了解Go语言主要功能,方便阅读书中的代码。
语法
变量
// 声明变量类型
var a int // 默认是 0
var b string // 默认是 ""
// 赋值
a = 123
b = "hi"
// 声明变量并初始化
var c int = 123
d := "hi" // 声明时自动推导类型
// 多个变量同时赋值
a, c = c, a变量没初始化,结果一定是用0字节表示的值(包括整数0,false,空字符串,nil 等)。
语句
// if-else 没有括号
if a != c {
// ...
} else {
// ...
}
// switch 不会像C一样自动跳到下一个 case
switch a {
case 123:
// ...
case 456:
// ...
default:
// ...
}// 在 if 里声明变量并使用
if err := f(); err != nil {
// ...
}// 循环
for {
if xxx {
break
}
if yyy {
continue
}
}
for i := 0; i < N; i++ {
// ...
}
for zzz {
// ...
}函数
func f(a int, b string) error {
return nil
}
// 多个返回值
func g() (string, bool) {
return "hi", false
}
// 多个返回值赋值
s, b := g()
// 返回值可以有变量名
func h() (s string, b bool) {
s = "hi"
b = false
return
}// 如果要返回错误,`error` 放在最后
func foo() (string, error) {
// ...
}
result, err := foo()
if err != nil {
// handle error
} else {
// use `result`
}数据类型
数值类型
// 固定长度
bool, byte
int8, int16, int32, int64
uint8, uint16, uint32, uint64
float32, float64
// 长度与系统相关:32位系统32位整数,64位系统64位整数
int
uint
// 类型转换
a := 1 // int
var b int64
b = a // 编译不过
b = int64(a) // 类型转换- 整数的溢出、类型转换,都按照 two’s complement(补码),不像C里会有UB。
- 不同类型之间要手动转换。
指针/引用
a := 1
var ptr *int // 声明指针类型
ptr = &a // 获取地址
b := *ptr // 读取指针数据(dereference)因为Go有GC,所以指针使用有限制。比如不能像C一样随便对指针做运算,不能将指针以其他类型储存,等等。不过只要不用 unsafe 就不会遇到这些问题。
struct, method
type C struct {
foo string,
bar int,
}
// struct 初始化
c := C{foo: "hi", bar: 123}
// 内容
c.bar = 456
bar1 := c.bar
// 指针
pc := &c
bar2 := (*pc).bar
bar3 := pc.bar // 不需要 dereference 的语法// 普通函数
func foo1(c *C) {
c.foo = "hi"
}
// 方法(method),自身以指针传递
func (c *C) foo2() {
c.foo = "hi"
}
// 方法,自身参数传值(value)
func (c C) bar() {
println(c.bar)
}
// 调用普通函数
foo1(&c)
// 调用方法
c.foo2() // 传指针
c.bar() // 传值固定大小数组
var a [2]byte // 2个元素的数组,初始化为0
var b [2][4]byte // 2个元素,每个元素是4个元素的数组
// 赋值
b = [2][4]byte{
{0, 0, 0, 0},
{0, 0, 0, 0},
}
b[1][3] = 123字符串
字符串只是一串 byte,便没有文本编码的的概念,可以和 []byte 互相转换。组成如下:
type string struct {
ptr *byte // 数据
len int // 长度
}s := "asdf" // {ptr: "asdf", len: 4}
len(s) // 4
// 字符串不能修改
s[0] = 'b' // 编译不过
// 引用一部分字符串,不会 copy 数据
s[2:4] // {ptr: "df", len: 2}
// 字符串拼接
s = "hi" + sslice
Go 里的 slice 其实是两种不相关的功能,它可以是下面其中一种:
- 表示数组的一个范围,包含指向数组的指针+范围的长度。
- 可以增长的动态数组。相当于 C++
vector,Pythonlist。
两种 slice 的组成都相当于下面这个 struct:
type sliceT struct {
ptr *T // 第0个元素的地址
len int // 引用的长度
cap int // 数组大小(从 ptr 开始算)
}len(),cap()函数分别返回 slice 中的len,cap。nilslice 其实是{ptr: nil, len: 0, cap: 0}。
两种 slice 学习时经常混淆,下面我们分开简绍。
a. 作为数组引用的 slice
var arr [8]byte = [8]byte{0, 1, 2, 3, 4, 5, 6, 7}
// 引用一个数组
s1 := arr[1:5] // {ptr: &arr[1], len: 4, cap: 7}
// 遍历
for i := 0; i < len(s1); i++ {
println(s1[i]) // 1 2 3 4
}
// 另一种遍历语法
for i := range s1 {
println(s1[i]) // 1 2 3 4
}
for i, v := range s1 {
println(v) // 1 2 3 4
}
// 越界,会导致 panic
v := s1[4]
// 修改 slice 的范围,返回一个新的 slice
s2 := s1[2:3] // {ptr: &arr[3], len: 1, cap: 5}
for _, v := range s2 {
println(v) // 3
}slice 越界检查:
s[i]:i不超过len。s[i:j]:j不超过cap。
正常的用法,只会使用 s[i:j] 来缩小引用的范围,而不会增加或移动到原来的范围之外。所以 slice 作为数组引用时,只使用 len 而不会使用 cap。
// 额外语法
s[:] // s[0:len(s)]
s[:j] // s[0:j]
s[i:] // s[i:len(s)]b. 作为动态数组的 slice
// 动态分配一个长度为2的数组,初始化为0,返回 slice
arr1 := make([]byte, 2) // {ptr: 0x100, len: 8, cap: 2}
// 动态分配一个长度为2的数组,返回的 slice 长度为0
arr2 := make([]byte, 0, 2) // {ptr: 0x200, len: 0, cap: 2}
// 末尾追加元素,返回长度+1的 slice,赋值给原来的 slice。
arr2 = append(arr2, 123) // {ptr: 0x200, len: 1, cap: 2}
arr2 = append(arr2, 123) // {ptr: 0x200, len: 2, cap: 2}
// cap 不够时自动重新分配数组
arr2 = append(arr2, 123) // {ptr: 0x300, len: 3, cap: 8}
// append 多个元素
arr2 = append(arr2, 2, 3) // {ptr: 0x300, len: 5, cap: 8}只有 slice 作为动态数组时才可以 append。作为数组引用的 slice 不能 append,因为会修改数组里的数据。
常见 slice 操作
copy(dst, src) // slice 间复制数据
s1 = slices.Clone(s) // 新分配一个 slice 并复制数据
s = slices.Concat(s1, s2, s2) // 拼接 slice
s = slices.Delete(s, i, j) // 删除 s[i:j]
s = slices.Insert(s, i, e1, e2) // 在位置 i 插入元素
idx = slices.Index(s, v) // 查找元素 v 的位置
slices.Sort(s) // 排序(inplace)作为数组引用的 slice 仅仅是一个引用,不一定可以通过 slice 去修改所指向的数组,绝对不可以 append。而作为动态数组的 slice 有对数组的 ownership,可以做任何操作。动态数组可以当成成数组引用来用,反之则不行。读者可以思考:上面的这些函数中的参数、返回值分别是哪种 slice?
interface
一个 interface 是一套约定方法(method)。任何实现了这些方法的类型都可以转换成 interface,哪怕只是碰巧匹配上了。
type Reader interface {
Read(p []byte) (n int, err error)
}
// 满足 interface 的类型
type T struct { /* ... */ }
func (s *T) Read(p []byte) (n int, err error) { /* ... */ }
// 参数使用 interface
func useReader(r ReaderWriter) { /* ... */ }
obj := &T{} // 具体类型
useReader(obj) // 转换成 interface通过 interface 调用方法,不需要依赖具体的类型,类似与 OOP 里的 abstract class。一个 interface 由2个指针组成:
type interfaceFoo struct {
object uintptr // 具体类型的数据
typeInfo uintptr // 对应的类型信息
}将某个值转换成 interface,就是引用这个值+相应的类型信息。调用方法时能通过类型信息找到方法的实现,还能判断具体的类型并取回原来的数据。
没初始化的 interface 里这两个引用都是 nil,这跟把 nil 赋值给 interface 是一样的,但跟把某个值为 nil 的指针转换成 interface 是不一样的。
判断原来的类型
- 使用
iface.(T)来测试是否匹配类型T并返回数据。 interface{}是带有0个方法的 interface。任何类型都能转换成interface{}。
var anything interface{}
anything = 1
anything = "asdf"
// 转换回原来的具体类型
integer, ok := anything.(int) // ok = false
str, ok := anything.(string) // ok = true, 可以使用 str 变量
// 用 switch 判断类型
switch origin := anthing.(type) {
case int:
integer = origin // origin 是成功转换过的类型
case string:
str = origin
}