go学习笔记(基础篇)

我们一起学猫叫 一起喵喵喵喵喵~

参考资料

基本结构和数据类型

常量

常量的定义格式:const identifier [type] = value

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

变量

使用 var 关键字:var identifier type

init 函数

init 是一类非常特殊的函数, 不能够被人为调用, 会在包初始化后自动执行, 优先级高于 main.

指针

1
var intP *int

控制结构

if-else

1
2
3
4
5
6
7
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}

常用例子

判断一个字符串是否为空:

  • if str == "" { ... }
  • if len(str) == 0 {...}

函数 Abs() 用于返回一个整型数字的绝对值:

1
2
3
4
5
6
func Abs(x int) int {
if x < 0 {
return -x
}
return x
}

测试多返回值函数的错误

参考教程

swich

1
2
3
4
5
6
7
8
switch var1 {
case val1:
...
case val2:
...
default:
...
}

for结构

1
for 初始化语句; 条件语句; 修饰语句 {}

Break 与 continue

Break 与 continue

标签与 goto

标签与 goto

函数

传递变长参数

1
func myFunc(a, b, arg ...int) {}

示例函数和调用:

1
2
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")

在 Greeting 函数中,变量 who 的值为 []string{"Joe", "Anna", "Eileen"}

函数作为参数

1
2
3
4
5
6
7
8
9
10
11
func main() {
callback(1, Add)
}

func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}

使用闭包调试

1
2
3
4
5
6
7
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()

通过内存缓存提升性能

牺牲空间换时间, 示例 fibonacci_memoization.go

数组和切片

声明

一个切片在未初始化之前默认为 nil,长度为 0。

切片的初始化格式是:var slice1 []type = arr1[start:end]

使用 make() 创建去切片

func make([]T, len, cap),其中 cap 是可选参数。

所以下面两种方法可以生成相同的切片:

1
2
make([]int, 50, 100)
new([100]int)[0:50]

new()make() 的区别

  • new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 * T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}
  • make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。

bytes

For-range 结构

这种构建方法可以应用于数组和切片

1
2
3
for ix, value := range slice1 {
...
}

切片重组(reslice)

改变切片长度的过程称之为切片重组reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)

参考示例

Map

声明、初始化和 make

map 是引用类型

1
2
3
var map1 map[keytype]valuetype

var map1 map[string]int

未初始化的 map 的值是 nil

var map1 = make(map[keytype]valuetype)

不要用 new 构造 Map

测试键值是否存在及删除元素

判断 key 是否存在

1
_, ok := map1[key1] // 如果key1存在则ok == true,否则ok为false

或者和 if 混合使用:

1
2
3
if _, ok := map1[key1]; ok {
// ...
}
删除 key

delete(map1, key1)

key 不存在, 不会产生错误.

For-range

1
2
3
for key, value := range map1 {
...
}

map 类型的切片

想获取一个 map 类型的切片, 必须使用两次 make(), 第一次分配切片, 第二次分配切片中每个 map 元素, 示例

排序

代码示例

键值对调

代码示例

GFW 牛逼 …

有些方法得研究研究

结构和方法

定义

1
2
3
4
5
type identifier struct {
field1 type1
field2 type2
...
}

new()

t := new(T),变量 t 是一个指向 T的指针,此时结构体字段的值是它们所属类型的零值

工厂方法创建结构体实例

按惯例,工厂的名字以 new 或 New 开头.

1
2
3
4
type File struct {
fd int // 文件描述符
name string // 文件名
}

这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

1
2
3
4
5
6
7
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}

return &File{fd, name}
}

然后这样调用它:

1
f := NewFile(10, "./test.txt")

强制使用工厂方法

1
2
3
4
5
6
7
8
type matrix struct {
...
}

func NewMatrix(params) *matrix {
m := new(matrix) // 初始化 m
return m
}

在其他包里使用工厂方法:

1
2
3
4
5
package main
import "matrix"
...
wrong := new(matrix.matrix) // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式

带标签的结构体

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体

方法

定义方法的一般格式如下:

1
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法

函数和方法的区别

函数将变量作为参数:Function1(recv)

方法在变量上被调用:recv.Method1()

指针或值作为接收者

pointer_value

方法和未导出字段

提供 getter 和 setter 方法

person2.go

类型的 String() 方法和格式化描述符

如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print()fmt.Println() 也会自动使用 String() 方法。

接口

通过如下格式定义接口:

1
2
3
4
5
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}

上面的 Namer 是一个 接口类型

(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NETJava 中那样)

Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。

类型断言

使用以下形式来进行类型断言:

1
2
3
4
5
if v, ok := varI.(T); ok {  // checked type assertion
Process(v)
return
}
// varI is not of type T

如果转换合法,vvarI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,okfalse,也没有运行时错误发生。

类型判断:type-switch

1
2
3
4
5
6
7
8
9
10
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}

测试值是否实现了某个接口

1
2
3
4
5
6
7
type Stringer interface {
String() string
}

if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}

方法集和接口

例子: 使用 Shorter 接口排序

例子: 读和写

空接口

空接口或者最小接口 不包含任何方法,它对实现不做任何要求:

1
type Any interface {}

任何其他类型都实现了空接口(它不仅仅像 Java/C#Object 引用类型),anyAny 是空接口一个很好的别名或缩写。

反射包