go 入门
-
基础
- 目录结构src pkg bin,gopath是工作目录环境变量,查看go env
- main.main() 函数(这个函数位于主包)是每一个独立的可运行程序的入口点。
- 除了 main 包之外,其它的包最后都会生成 *.a 文件(也就是包文件)并放置在 G O P A T H / p k g / GOPATH/pkg/ GOPATH/pkg/GOOS_$GOARCH 中
- 用 var 方式来定义全局变量,:= 进行在函数内的简短声明,_ 特殊的变量名,任何赋予它的值都会被丢弃。
- 字符串使用""或``,其不可变,但可以进行切片操作;内置error错误类型;
-
分组声明
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
prefix = "Go_"
)
var(
i int
pi float32
prefix string
)
-
iota枚举关键字与常量
- 默认开始值是 0,const 中每增加一行加 1
- 每遇到一个 const 关键字,iota 就会重置
- 常量声明省略值时,默认和之前一个值的字面相同
- 在同一行值相同
-
go程序设计规则
- 大写字母开头的变量,公有变量,可导出。小写则是私有变量。
-
array, slice, map
-
声明 slice 时,方括号内没有任何字符。slice 的 index 只能是 int 类型
-
数组之间的赋值是值的赋值,传入的其实是该数组的副本,而不是它的指针
//array var arr [10]int // 声明了一个int类型的数组 b := [10]int{1, 2, 3} // 声明了一个长度为 10 的 int 数组,其中前三个元素初始化为 1、2、3,其它默认为 0 c := [...]int{4, 5, 6} // 可以省略长度而采用 `...` 的方式,Go 会自动根据元素个数来计算长度 //slice, 和声明 array 一样,只是少了长度 var fslice []int // 声明并初始化 slice := []byte {'a', 'b', 'c', 'd'} // a 指向数组的第 3 个元素开始,并到第五个元素结束,左开右闭 a = ar[2:5] // 第三个参数指定切片容量 slice = array[2:4:7]
-
-
slice 引用类型 常用简便操作
- slice 的默认开始位置是 0,ar[:n] 等价于 ar[0:n]
- slice 的第二个序列默认是数组的长度,ar[n:] 等价于 ar[n:len(ar)]
- 如果从一个数组里面直接获取 slice,可以这样 ar[:],即等价于 ar[0:len(ar)],包含了全部的元素
-
slice 常用内置函数
- len 获取 slice 的长度
- cap 获取 slice 的最大容量
- append 向 slice 里面追加一个或者多个元素,然后返回一个和 slice 一样类型的 slice
- copy 函数 copy 从源 slice 的 src 中复制元素到目标 dst,并且返回复制的元素的个数
-
map
var numbers map[string]int numbers = make(map[string]int) numbers["one"] = 1 // 赋值 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // map 有两个返回值,第二个返回值,如果不存在 key,那么 ok 为 false,如果存在 ok 为 true csharpRating, ok := rating["C#"]
- 通过delete删除map的元素
- map 也是一种引用类型,如果两个 map 同时指向一个底层,那么一个改变,另一个也相应的改变
- map 和其他基本型别不同,它不是 thread-safe,在多个 go-routine 存取时,必须使用 mutex lock 机制
-
make vs new
- new 用于各种类型的内存分配。new 返回指针,即*T类型的值
- make 用于内建类型(map、slice 和 channel)的内存分配。make 返回一个有初始值(非零)的T类型,而不是 *T
- 零值并非是指空值,而是一种变量未填充前的默认值,通常为0
// 常用零值列表 int 0 int8 0 int32 0 int64 0 uint 0x0 rune 0 // rune 的实际类型是 int32 byte 0x0 // byte 的实际类型是 uint8 float32 0 // 长度为 4 byte float64 0 // 长度为 8 byte bool false string "
流程控制
- if 条件判断(许初始化变量)
- 条件判断语句里面允许声明一个变量,该变量的作用域只能在该条件逻辑块内
- goto 无条件跳转
- 用goto 跳转到必须在当前函数内定义的标签
- 标签名是大小写敏感的。
func myFunc() { i := 0 Here: // 这行的第一个词,以冒号结束作为标签 println(i) i++ goto Here // 跳转到 Here 去 }
- for
- 循环读取数据
- 当作while控制逻辑
sum := 1 for sum < 1000 { sum += sum }
- 迭代操作
- for 配合 range 可以用于读取 slice 和 map 的数据
- break 和 continue 还可以跟着标号,用来跳到多重循环中的外层循环
- 标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。
- switch
- 表达式不必是常量或整数
- 执行的过程从上至下,直到找到匹配项
- 如果 switch 没有表达式,它会匹配 true。
- switch 默认相当于每个 case 最后带有 break,匹配成功跳出整个switch,可以使用 fallthrough 强制执行后面的 case 代码。
i := 10 switch i { case 1: fmt.Println("i is equal to 1") case 2, 3, 4: fmt.Println("i is equal to 2, 3 or 4") case 10: fmt.Println("i is equal to 10") fallthrough default: fmt.Println("All I know is that i is an integer") }
- func 函数
- 支持多值返回,命名返回值,必须声明类型
- 函数可以有一个或者多个参数,每个参数后面带有类型,通过,分隔
- 可命名返回参数的变量,若函数是导出建议命名返回值
func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B return } // 支持变参 func myfunc(arg ...int) {} type testInt func(int) bool // 声明了一个函数类型
- 传值与传指针
- 传参实际上是传值,其值可以是变量的值,也是可以是变量的地址,即指针
- 变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存
- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级 (8bytes),只是传内存地址,指针传递体积大的结构体
- 若用参数值传递的话, 在每次 copy 上面就会花费相对较多的系统开销(内存和时间)
- Go 语言中 channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针
- defer
- 多个defer调用 defer,那么 defer 是采用后进先出模式
- defer 后指定的函数会在函数退出前调用
- 当函数执行到最后时,这些 defer 语句会按照逆序执行,最后该函数返回。
- 函数作为值类型
- 写通用接口
- panic Recover
- main init
- init 函数 能够应用于所有的 package,可选
- main 函数(只能应用于 package main,必选)
- 这两个函数在定义时不能有任何的参数和返回值。
- 程序的初始化和执行都起始于 main 包
- 流程
- main导入包
- 对导入包内的常量和变量进行初始化
- 执行导入包内的init函数
- 对main包中的包级常量和变量进行初始化,执行main包init,main函数
- import
- GOROOT 环境变量指定目录下去加载该模块
// 当前文件同一目录的 model 目录,但是不建议这种方式来 import import "./model" // 绝对路径 加载 gopath/src/shorturl/model 模块 import "shorturl/model"
- import 简化行为
- 点 省略前缀的包名
- 别名
- _ 调用包里面的 init 函数
import ( // 即调用 fmt.Println("hello world") 可以省略的写成 Println("hello world") . "fmt" ) import( // 即可 f.Println("hello world") f "fmt" ) import ( "database/sql" // 调用了该包内的init函数,但不对其它内容进行导入处理 _ "github.com/ziutek/mymysql/godrv" )
- GOROOT 环境变量指定目录下去加载该模块
struct类型
-
struct类型初始化
- 按照顺序提供初始化值
- 通过
field:value
的方式初始化,这样可以任意顺序 - 通过 new 函数分配一个指针,此处P的类型为 *person
-
struct 的匿名字段(嵌入字段)
- 匿名字段能够实现字段的继承
- 所有的内置类型和自定义类型都可以作为匿名字段
- 同名字段,外层的优先访问,可重载
type Skills []string type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,那么默认 Student 就包含了 Human 的所有字段 Skills // 匿名字段,自定义的类型 string slice int // 内置类型作为匿名字段 speciality string } func main() { // 我们初始化一个学生 mark := Student{Human{"Mark", 25, 120}, "Computer Science"} }
面向对象
-
method
- 定义:带有接收者的函数
func (r ReceiverType) funcName(parameters) (results) // 自定义类型,类似于别名 type typeName typeLiteral type ages int type months map[string]int
- 可以在任何的自定义类型中定义任意多的 method
- 定义:带有接收者的函数
-
指针作为receiver
- go 知道是否调用指针的 method 还是非指针的 method,会自转
- 如果一个 method 的 receiver 是 *T, 你可以在一个 T 类型的实例变量 V 上面调用method,而不需要 &V 去调用这个 method
-
method继承 vs 重写
interface类型
- interface 是一组 method 签名的组合
- 空interface
- interface{} 存储任意类型的数值 (类C void*)
- interface 函数参数
- interface 变量存储的类型
- Comma-ok 断言
- value,ok = element(T)
- element 为interface变量,T断言的类型
- element.(type) 语法不能在switch外的任何逻辑里面使用
- Comma-ok 断言
- 嵌入interface
- 相同的逻辑引入到interface内
type Interface interface { sort.Interface // 嵌入字段 sort.Interface Push(x interface{}) // a Push method to push elements into the heap Pop() interface{} // a Pop elements that pops elements from the heap }
- 相同的逻辑引入到interface内
- 反射
- 反射某一类型的值(这些值都实现了空interface)
- 转化为reflect对象(reflect.Type 或 reflect.Value)
t := reflect.TypeOf(i) // 得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.ValueOf(i) // 得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值
- 将reflect转化为相应的值
tag := t.Elem().Field(0).Tag // 获取定义在 struct 里面的标签 name := v.Elem().Field(0).String() // 获取存储在第一个字段里面的值
- 反射的字段必须是可修改(可读写)
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) //修改相应的值 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1)
- 反射某一类型的值(这些值都实现了空interface)
并发
-
goroutine
- 并行程序设计,Go语言层面支持
- goroutine 比线程更小,比thread更易用,高效,轻便
- Go语言内部实现了goroutine之间的内存共享
- goroutine 是通过 Go 的 runtime 管理的一个线程管理器
- 不通过共享来通信,而通过通信来共享。
-
channels
- 同goroutine 运行在相同的地址空间
- 阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收
- 默认情况下,channel 接收和发送数据都是阻塞的,不需要显式的 lock
ch <- v // 发送 v 到 channel ch. v := <-ch // 从 ch 中接收数据,并赋值给v
-
Buffered Channels
- go 允许指定channel缓冲大小,即channel可存储的元素
- 在有缓冲的情况下,缓冲数之下,元素可以无阻塞写入通道,超过代码会阻塞,直到有其它goroutine从channel中读取一些元素,让出空间
-
Range 和 Close
- for i := range c 能够不断的读取 channel 里面的数据,直到该 channel 被显式的close关闭
- v,ok := <-ch 测试channel是否被关闭
- 在生产者的地方关闭 channel
-
多个channel
- select 默认阻塞,监听channel,随机选择一个执行
-
超时
- 避免整个程序进入阻塞方案
- 用 select 监听来设置超时
- 避免整个程序进入阻塞方案
-
runtime goroutime
- Goexit
- 退出当前执行的 goroutine,但是 defer 函数还会继续调用
- Gosched
- 让出当前 goroutine 的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行
- NumCPU
- 返回 CPU 核数量
- NumGoroutine
- 返回正在执行和排队的任务总数
- GOMAXPROCS
- 用来设置可以并行计算的 CPU 核数的最大值,并返回之前的值。
- Goexit
golang关键字小结
- defer 用于类似析构函数
- go 用于并发
- select 用于选择不同类型的通讯
- interface 用于定义接口
- struct 用于定义抽象数据类型
- chan 用于 channel 通讯
- type 用于声明自定义类型
- map 用于声明 map 类型数据
- range 用于读取 slice、map、channel 数据