Variable

格式: var``变量名``变量类型

  • 变量需要声明后才可使用
  • 同作用域中不可重复声明
  • 变量声明后必须使用
    1. // 声明一个
    2. var v1 string
    3. // 批量声明
    4. var (
    5. v2 string
    6. v3 int
    7. )

类型推导

省略类型声明时,编译器会根据类型等号右侧的值来推导变量来进行初始化

  1. var v1 = "Daived"

短变量声明

函数内部可以使用更简略的:=方式声明并初始化变量

  1. v1 := "Dong"

匿名变量

可用于函数或方法返回多个值时忽略赋值

  1. func foo() (int,string){
  2. return 10,"Tom"
  3. }
  4. func main(){
  5. age,_ := foo()
  6. fmt.Println(age)
  7. }

Const


格式: const``常量名=

  • 常量在声明时必须赋值
  • 声明后值不可改变
  1. // 声明一个
  2. const PI = 3.1415
  3. // 声明多个
  4. const (
  5. E = 2.7111
  6. BASE_URL="http://www.baidu.com/"
  7. )

常量计数器: iota

  • 仅可在常量表达式中使用
  • 常用于定义枚举常量
  • const关键字出现时重置为0, 每新增一行将使iota计数一次
  1. const (
  2. January = iota // 0
  3. February // 1, 可忽略声明iota
  4. _ // 忽略本次赋值
  5. March // 3
  6. )
  7. const May = iota // 0

Base Type


整型

有符号

int8 -128 ~ 127
int16 -32768 ~ 32767
int32 -21亿 ~ 21亿, rune是int32 的别名
int64 -2^63 ~ 2^63-1

无符号

uint8 0 ~ 255, byte是uint8 的别名
uint16 0 ~ 65535
uint32 0 ~ 42亿
uint64

浮点型

golang支持两种浮点类型: float32float64

float32

  • 最大范围约为3.4e38
  • 可以使用常量math.MaxFloat32定义

    float64

  • 最大范围约为1.8e308

  • 可以使用常量math.MaxFloat64定义

    布尔值

    使用bool声明

  • 仅有true真和false假两值

  • 无法参数数值运算,并且无法参与类型转换
  • 零值为 false ```go var b1 bool

b2 := true

  1. <a name="JEFAR"></a>
  2. ### 字符串
  3. > 使用`string`声明
  4. ```go
  5. var s1 string
  6. s1 = "hello world"
  7. s2 := "hello"

字符串转义符 回车``单双引号``制表符

\r 回车符
\n 换行符
\t 制表符
\‘ 单引号
\“ 双引号
\\ 反斜杠
  1. s1 := `
  2. select
  3. name
  4. from
  5. user_info
  6. where
  7. user_id = ?`
  8. fmt.Println(s1)

常用字符串操作

  1. var s1 = "hello world"
  2. // 获取长度
  3. fmt.Printf("s1 len: %s \n",s1)
  4. // 拼接字符串
  5. fmt.Println("hello " + "world")
  6. fmt.Printf("%s %s \n","hello","world")
  7. // 分割字符串
  8. fmt.Printf("%+v \n",strings.Split(s1,""))
  9. // 判断是否包含
  10. if strings.Contains(s1, "hello") {
  11. fmt.Println("hello within s1")
  12. }
  13. // 前缀判断
  14. if strings.HasPrefix(s1, "hello") {
  15. fmt.Println("s1 begins with hello.")
  16. }
  17. // 后缀判断
  18. if strings.HasSuffix(s1, "world") {
  19. fmt.Println("s1 ends with hello.")
  20. }
  21. // 字符串出现位置
  22. fmt.Println(strings.Index(s1, "w"))
  23. // 连接字符串
  24. fmt.Println(strings.Join([]string{"hello","world"}," "))

byte & rune

Go中的字符

  • uint8类型,或者叫byte型,代表了ASCII码字符
  • rune类型,代表UTF-8字符

当处理中文、日文或者其他符合字符时,需要用rune类型,rune实际是个int32

  1. // 遍历字符串
  2. func traversalString() {
  3. s := "你可真棒"
  4. // byte
  5. for i := 0; i < len(s); i++ {
  6. fmt.Printf("%v(%c)", s[i], s[i])
  7. }
  8. fmt.Println("")
  9. // rune
  10. for _, r := range s {
  11. fmt.Printf("%v(%c) ", r, r)
  12. }
  13. }
  14. // 输出
  15. // 228(ä)189(½)160( )229(å)143(�)175(¯)231(ç)156(�)159(�)230(æ)163(£)146(�)
  16. // 20320(你) 21487(可) 30495(真) 26834(棒)
  1. // 修改字符串
  2. func changeStr() {
  3. // byte
  4. s1 := "hello"
  5. byteS1 := []byte(s1) // string强制转[]byte
  6. byteS1[0] = 'H'
  7. fmt.Println(string(byteS1))
  8. // rune
  9. s2 := "你好"
  10. runeS2 := []rune(s2) // string强制转[]rune
  11. runeS2[0] = '谁'
  12. fmt.Println(string(runeS2))
  13. }

Array


初始化表达式:var arr [n]T{v1,v2} 定义:数据类型的固定长度序列 注意点:

  • 数组长度必须是常量,一旦定义,长度不可改变
  • 长度是数组的组成部分,同类型不同长度数组,为不同类型,例如:var arr0 [4]intvar arr1 [10]int为不同类型数组
  • 通过下标访问,初始为0
  • 下标访问越界后会出发panic
  • 指针数组: [n]*T, 数组指针: *[n]T
  1. // 初始化数组
  2. var arr0 [5]int = [5]int{1, 2, 3, 4, 5}
  3. var arr1 = [5]int{1, 2, 3, 4, 5}
  4. var arr2 = [...]int{1, 2, 3, 4, 5} // 使用...将自动计算数组长度(仅可在初始化时使用)
  5. var arr3 = [5]string{0: "hello", 1: "world"} // 通过下标初始化, 未初始化为零值
  6. var arr4 = [...]struct {
  7. name string
  8. age int
  9. }{
  10. {
  11. name: "Dong",
  12. age: 18,
  13. },
  14. {
  15. name: "Xi",
  16. age: 1000,
  17. },
  18. }
  19. fmt.Printf("%v\n%v\n%v\n%v\n%+v\n", arr0, arr1, arr2, arr3, arr4)

函数传值

值类型, 赋值传参会复制整个数组,并不是指针

  • 尽量使用slice替代array pointer传递,以确保最大内存共享
  1. func noChange(arr [3]int) {
  2. // 值传递, 原变量不会改变
  3. arr[0] = 10
  4. fmt.Printf("noChange arr: %v %p\n", arr, &arr)
  5. }
  6. func change(arr *[3]int) {
  7. arr[0] = 10
  8. fmt.Printf("change arr: %v %p\n", arr, arr)
  9. }
  10. func main() {
  11. arr := [3]int{1, 2, 3}
  12. arr0 := arr
  13. fmt.Printf("arr: %v %p\n", arr, &arr)
  14. fmt.Printf("arr0: %v %p\n", arr0, &arr0)
  15. noChange(arr) // 数组值传递, 不改变原始变量
  16. change(&arr) // 指针传递, 改变原始变量
  17. }

官方也建议使用slice用来替换array pointer

截屏2022-02-07 下午2.56.15.png

遍历数组的方式

  • for range: 遍历数组获取下标和值
  • for i:通过数组长度获取下标,然后获取值
  1. for i, v := range arr {
  2. fmt.Printf("%d:%d\n", i, v)
  3. }
  4. for i := 0; i < len(arr); i++ {
  5. fmt.Printf("%d:%d\n", i, arr[i])
  6. }

Slice


表达式:var slice []T``slice := arr[0:2] 定义:切片是数组的一个引用,通过内部指针和相关属性引用数组片段 注意点:

  • 切片长度可改变,因此切片为可变数组
  • cap内置函数可以求出切片最大扩张容量

初始化切片

  1. // 声明切片
  2. var s1 []int
  3. // 初始化数组
  4. arr0 := [3]int{1,2,3}
  5. // 初始化切片(零容量)
  6. // :=
  7. s2 := []int{}
  8. // make(T,Len), 省略Cap时,Cap=Len
  9. s3 := make([]int,0)
  10. // make(T,Len,Cap)
  11. s4 := make([]int,0,0)
  12. // 从数组切片
  13. s5 := arr0[0:3]
  14. // 数组开始到指定下标
  15. s6 := arr0[:1] // 等于 arr0[0:1]
  16. // 数组执行下标到结尾
  17. s7 := arr0[2:] // 等于 arr0[2:3]
  18. // 数组开始到结尾
  19. s8 := arr0[:] // 等于 arr0[0:3]

通过append函数添加元素

  1. slice := make([]int, 0)
  2. slice = append(slice, 1)
  3. fmt.Println(slice)

通过make创建切片

在切片中添加元素超出容量时,自动扩容容量的一倍( 超出原 slice.cap 限制,就会重新分配底层数组) 注意:

  • 尽量一次性分配最够大的空间,避免内存分配和数据复制的开销
  • 及时释放不再使用的slice对象,避免持有过期数组,造成GC无法回收
  1. sm1 := make([]int, 9, 10)
  2. fmt.Printf("sm1 len:%d cap:%d\n", len(sm1), cap(sm1))
  3. sm1 = append(sm1, []int{1, 2, 3}...)
  4. fmt.Printf("sm1 len:%d cap:%d\n", len(sm1), cap(sm1))
  5. sm1 = append(sm1, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}...)
  6. fmt.Printf("sm1 len:%d cap:%d\n", len(sm1), cap(sm1))
  7. // sm1 len:9 cap:10
  8. // sm1 len:12 cap:20
  9. // sm1 len:28 cap:40

内存布局

切片来自数组时,引用数组一个片段,并指向同一内存地址 以下是切片的内存布局↓

m_1bfa3d18f23b01c62058f9d62875b972_r.jpg

  1. data := [...]int{1,2,3}
  2. // 从数组创建切片
  3. s1 := data[0:1]
  4. // 修改切片
  5. s1[0] = 10
  6. fmt.Printf("s1: %v",s1)
  7. // 引用数组同时改变
  8. fmt.Printf("data: %v",data)

直接修改struct slice成员属性值

  1. s := []struct{
  2. name string
  3. }{
  4. {name: "daived"}
  5. }
  6. s[0].name = "Dong"
  7. fmt.Println(s)

切片拷贝

  • copy函数可在两个slice间复制数据,并且两个slice可以指向同一数组
  • 应及时将数据copy到较小的slice中,以便释放内存
  1. s1 := []int{1,2,3}
  2. s2 := make([]int,0,3)
  3. s3 := []int{2,3,4}
  4. copy(s1,s2)
  5. copy(s2,s3)
  6. fmt.Printf("s1:%v %d %d\n",s1,len(s1),cap(s1))
  7. fmt.Printf("s2:%v %d %d\n",s2,len(s2),cap(s2))
  8. fmt.Printf("s3:%v %d %d\n",s3,len(s3),cap(s3))

Map


表达式: map[KeyType]ValueType 定义: map是一种无序的基于key-value的数据结构,引用类型,必须初始化后才能使用

  1. m := map[string]string{}
  2. fmt.Println(m)
  3. // 可通过make创建
  4. // make(map[KeyType]ValueType,Cap)
  5. m1 := make(map[string]string,10)
  6. fmt.Println(m1)
  7. // 声明时初始化
  8. m2 := map[string]string{"name":"dong"}

判断某个key是否存在

value, ok := map[key]

  1. m := map[string]string{"name":"dong"}
  2. // v: ValueType ok: Bool
  3. v,ok := m["name"]
  4. if ok {
  5. fmt.Println(v)
  6. }

根据key删除键值对

delete(map,key)

  1. m := map[string]string{"name": "dong", "age": "18"}
  2. delete(m,"name")
  3. v,ok := m["name"]
  4. if !ok {
  5. fmt.Println("key name not found")
  6. }

遍历

  1. m := map[string]string{"name": "dong", "age": "18"}
  2. for k, v := range m {
  3. fmt.Printf("k: %s v:%s \n", k, v)
  4. }

排序后顺序遍历

顺序遍历,因为map本身是无序的,所以需要借用array进行排序操作后再按照key进行取值操作 生产环境中可以考虑使用struct array来替待map

  1. m := map[string]string{
  2. "1": "dong",
  3. "2": "xixi",
  4. "3": "nan",
  5. "4": "bei",
  6. }
  7. // 默认遍历是无序的
  8. for k, v := range m {
  9. fmt.Printf("key: %s value: %s\n", k, v)
  10. }
  11. // 获取map key切片
  12. keys := make([]string, 0)
  13. for k, _ := range m {
  14. keys = append(keys, k)
  15. }
  16. // 排序keys
  17. sort.Strings(keys)
  18. // 排序后遍历key获取value
  19. for _, k := range keys {
  20. fmt.Printf("key: %s value: %s\n", k, m[k])
  21. }

Pointer


表达式: &取地址 *根据地址取值

  • Go语言中函数为值拷贝,当想要修改变量值时,可以创建一个指向该变量的指针变量。
  • 传递数据使用指针则无需拷贝数据。
  • 类型指针不能进行偏移运算

指针地址

指针地址是物理内存中标识,用于操作内存中的变量

取变量指针地址: ptr := &v

  1. s := "hello"
  2. ptr := &s
  3. fmt.Printf("s: %v\n", s)
  4. fmt.Printf("s ptr: %p\n", ptr)

取指针变量值:v := *ptr

  1. s := "hello"
  2. ptr := &s
  3. fmt.Printf("ptr value: %v\n", *ptr)

指针类型

带有指针地址的变量类型

  1. //指针取值
  2. a := 10
  3. b := &a // 取变量a的地址,将指针保存到b中
  4. c := *b // 指针取值(根据指针去内存取值)
  5. fmt.Printf("type of b:%T\n", b)
  6. fmt.Printf("type of c:%T\n", c)
  7. fmt.Printf("value of c:%v\n", c)s

创建指针类型,指针类型变量不可用var关键字创建,否则会创建出空指针 ,而触发panic

  1. s := new(string)
  2. fmt.Println(s)

函数传递指针变量

  1. func change(arr *[3]string) {
  2. arr[0] = "hello"
  3. }
  4. func main() {
  5. arr := [3]string{"1", "2", "3"}
  6. fmt.Printf("arr: %v\n", arr)
  7. change(&arr)
  8. fmt.Printf("arr: %v\n", arr)
  9. }

Type


自定义类型

表达式: type MyType T

  1. // 创建自定义类型Num
  2. type Num int
  3. func main() {
  4. var n Num
  5. fmt.Printf("n type is %T\n",n)
  6. }

类型别名

表达式: type MyTypeAlias = T 于自定义类型差异:

  • 类型别名仅会在代码阶段存在(编译完成后不会存在
  1. // 创建自定义类型Num
  2. type Num int
  3. // 创建类型别名
  4. type NumT = Num
  5. func main() {
  6. var n1 Num
  7. var n2 NumT
  8. // 可使用T(v)的方式转换类型
  9. if NumT(n1) == n2 {
  10. fmt.Println("n1 equla n2")
  11. }
  12. fmt.Printf("n1 type is %T\n", n1) // n2 type is main.Num
  13. fmt.Printf("n2 type is %T\n", n2) // n2 type is main.Num
  14. }

Struct


表达式: type TypeName struct { Filed1 Type Filed2 Type } 定义: 自定义数据类型,用于封装多个基础数据类型,本质上是一种聚合型的数据类型,可实现面向对象

  1. type User struct {
  2. Name string
  3. Age int
  4. City,Address string // 可将字段写在一行(不建议使用,会导致tag使用不便)
  5. }

实例化

表达式: var 结构体实例 结构体类型

  1. type User struct {
  2. Name string
  3. Age int
  4. }
  5. func main() {
  6. var u User
  7. u.Name = "dong"
  8. u.Age = 18
  9. fmt.Printf("%+v\n", u)
  10. }

匿名字段

又称嵌入字段

  1. type Student struct {
  2. ClassRoom string
  3. }
  4. type User struct {
  5. *Student
  6. Name string
  7. Age int
  8. }
  9. func main() {
  10. u := User{&Student{"one class"}, "dong", 10}
  11. fmt.Println(u)
  12. fmt.Println(u.Student)
  13. }

匿名结构体

临时数据结构场景下使用

  1. var u struct{Name string age int8}
  2. u.Name = "dong"
  3. u.Age = 18
  4. fmt.Printf("%+v\n", u)

指针类型结构体

通过内置方法new创建 可直接访问结构体成员,底层实现(*u).name = "dong"语法糖

  1. u := new(User)
  2. u.Name = "dong"
  3. u.Age = 18
  4. fmt.Printf("%+v\n", u)

键值对初始化

可选择字段初始化

  1. u := User{
  2. Name: "dong",
  3. Age: 18,
  4. }
  5. fmt.Printf("u : %#v", u)

值列表初始化

不能同键值对初始化同用 初始值填写顺序必须与结构体声明字段顺序一致 必须初始化所有字段

  1. u := User{
  2. "dong",
  3. 18,
  4. }
  5. fmt.Printf("u : %#v", u)

构造函数

go 本身没有构造函数,可以模拟实现

  1. func NewUser() *User{
  2. return &User{
  3. "dong",
  4. 18,
  5. }
  6. }
  7. func main() {
  8. u := NewUser()
  9. fmt.Printf("%+v\n",u)
  10. }

方法与接收者

表达式: func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体} 方法定义: 用作特定类型变量的函数 接收者(Receiver)定义: 特定类型变量,类似于其他语言中的this或者self

  • 接收者名首字符建议小写(官方)
  • 接收者可以是指针或者非指针类型(这里实例化类型可以和接收者类型不同,使用上与函数参数类似)
  1. type user struct {
  2. name string
  3. age int8
  4. }
  5. func NewUser() *user{
  6. return &User{
  7. "dong",
  8. 18,
  9. }
  10. }
  11. func (u user) format() string {
  12. return fmt.Sprintf("user name is %s, age is %d",u.name,u.age)
  13. }
  14. func main() {
  15. u := NewUser()
  16. fmt.Println(u.format())
  17. }

指针类型接收者

用于实际修改变量属性 什么时候应该使用指针类型接收者:

  • 接收者值拷贝代价比较大的对象
  • 需要修改接收者值
  • 保证一致性,如果某个方法使用了指针接收者,其他方法也应使用
  1. type user struct {
  2. name string
  3. age int8
  4. }
  5. func NewUser() *user {
  6. return &user{
  7. "dong",
  8. 18,
  9. }
  10. }
  11. func (u *user) change() string {
  12. u.age = 100
  13. return fmt.Sprintf("user name is %s, age is %d", u.name, u.age)
  14. }
  15. func main() {
  16. u := NewUser()
  17. fmt.Println(u.change())
  18. }

接收者可以是任意类型

  1. type TableName string
  2. // RS哈希取模分表
  3. func (t TableName) SubTable(id string) string {
  4. var b uint32 = 378551;
  5. var a uint32 = 63689;
  6. var hash uint32 = 0;
  7. for i := 0; i < len(id); i++ {
  8. hash = hash * a + uint32(str[i]);
  9. a *= b;
  10. }
  11. v := crc32.ChecksumIEEE([]byte(id))
  12. return fmt.Sprintf(
  13. "%s_%d",
  14. t, hash&0x7FFFFFFF%20,
  15. )
  16. }
  17. func main() {
  18. tb := TableName("user")
  19. for i := 0; i < 100; i++ {
  20. fmt.Println(tb.SubTable(strconv.Itoa(i)))
  21. }
  22. }

Flow


if … else if … else

条件语句 通过指定一个或者多个条件,并测试是否为true来决定是否执行指定语句

语法

  1. if 布尔表达式1 {
  2. // 布尔表达式1为true时运行
  3. // 执行完成后跳出判断
  4. } else if 布尔表达式2 {
  5. // 布尔表达式2为true时运行
  6. } else {
  7. // 以上表达式均为false时运行
  8. }

示例

  1. var name string
  2. var age int
  3. fmt.Printf("Please input your name and age: ")
  4. // 扫描来自标准输入文本, 使用空格分隔值
  5. fmt.Scanln(&name, &age)
  6. fmt.Printf("your name is %s\n", name)
  7. fmt.Printf("your age is %d\n", age)

switch … case … default

匹配不同值来执行不同动作,每个case语句是唯一的,从上至下逐一测试,知道匹配配置 未匹配到,执行defalut分支

语法

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

示例

  1. var level int
  2. START:
  3. fmt.Printf("Please input your level: ")
  4. // 扫描来自标准输入文本, 使用空格分隔值
  5. fmt.Scanln(&level)
  6. switch level {
  7. case 1:
  8. fmt.Println("王者")
  9. case 2:
  10. fmt.Println("青铜")
  11. case 3:
  12. fmt.Println("白银")
  13. default:
  14. println("Please input 1...3!")
  15. goto START
  16. }
  17. // 同时测试多个值
  18. switch level {
  19. case 1:
  20. fmt.Println("王者")
  21. case 2:
  22. fmt.Println("青铜")
  23. case 3,4,5,6:
  24. fmt.Println("白银")
  25. default:
  26. println("Please input 1...6!")
  27. goto START
  28. }
  29. // 判断interface变量类型

select … case … default

同switch类似,不同的是随机执行一个可执行的case,如果没有case可执行会阻塞,直到case可执行 常用于等待协程执行,通过channel通知进行下一步处理

语法

  • case语句必须是channel操作(读 or 写)
  • default总是可用的
  • 如果有多个case可以运行,select会随机公平选取一个执行,其他不会执行
    1. select { // 不停的在这里检测
    2. case <-chanl : // 检测有没有数据可以读
    3. // 如果chanl成功读取到数据,则进行该case处理语句
    4. case chan2 <- 1 : // 检测有没有可以写
    5. // 如果成功向chan2写入数据,则进行该case处理语句
    6. // 假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞
    7. // 一般default会不写在里面,select中的default子句总是可运行的,因为会很消耗CPU资源
    8. default:
    9. // 如果以上都没有符合条件,那么则进行default处理流程
    10. // 如果没有default,则阻塞程序
    11. }

示例-default分支

  1. var c1 chan int // nil
  2. var i1 int
  3. select {
  4. case i1 = <-c1:
  5. fmt.Printf("i1 value is %d", i1)
  6. case c1 <- 1:
  7. fmt.Println("write to c1")
  8. default:
  9. fmt.Println("no communication")
  10. }

示例-case分支

  1. var i1 int
  2. c1 := make(chan int, 1)
  3. go func() {
  4. c1 <- 1
  5. }()
  6. go func() {
  7. select {
  8. case i1 = <-c1:
  9. fmt.Printf("i1 value is %d", i1)
  10. case c1 <- 1:
  11. fmt.Println("write to c1")
  12. }
  13. }()
  14. time.Sleep(1 * time.Second)

示例-超时判断

  1. func doSomeing(c *chan int) {
  2. time.Sleep(2 * time.Second)
  3. *c <-
  4. }
  5. func main() {
  6. c := make(chan int)
  7. go doSomeing(&c)
  8. select {
  9. case i := <-c:
  10. fmt.Printf("i value is %d\n", i)
  11. case <-time.After(3 * time.Secsond):
  12. fmt.Println("doSomeing function timeout")
  13. }
  14. }

for

循环控制语句,可根据条件循环执行

for init; condition; post {}

init: 控制变量初始值 condition: 循环控制条件 post: 控制变量赋值,增量或减量

  1. list := [3]int{1, 2, 3}
  2. for i := 0; i < len(list); i++ {
  3. fmt.Printf("list[%d] = %d \n", i, list[i])
  4. }

for condition {}

根据控制条件循环

  1. list := [3]int{1,2,3}
  2. i := 0
  3. for i<len(list) {
  4. fmt.Printf("list[%d] = %d \n", i, list[i])
  5. i++
  6. }

for i,v := range array {}

结合range遍历数组

  1. list := [3]int{1,2,3}
  2. for i,v := range list {
  3. fmt.Printf("list[%d] = %d \n", i, v)
  4. }

for {}

无限循环

  1. list := [3]int{1,2,3}
  2. i := 0
  3. for {
  4. if i >= len(list) {
  5. break
  6. }
  7. fmt.Printf("list[%d] = %d \n", i, list[i])
  8. i++
  9. }

range

类似迭代器,返回(索引,值) 或(键,值) 结合for可对slice``map``array``string等进行迭代循环,格式如下

  1. for key,value := range m {
  2. fmt.Printf("key: %s value",key,value)
  3. }

可使用_忽略不想要的返回值

  1. list := []int{1,2,3}
  2. // 忽略index
  3. for _,v := range list {
  4. fmt.Println(v)
  5. }
  6. // 全部忽略 仅迭代
  7. for range list {
  8. fmt.Println("do someing")
  9. }

遍历map返回keyvalue

  1. m := map[string]int{"a":1,"b":2}
  2. for k,v := range m {
  3. fmt.Printf("%s:%d\n",k,v)
  4. }

注意! range会复制值的副本指针,应使用for+index的方式

  1. list := [3]int{1, 2, 3}
  2. newList := make([]*int, 3)
  3. for i, v := range list {
  4. newList[i] = &v
  5. }
  6. fmt.Println(*newList[0])
  7. fmt.Println(*newList[1])
  8. fmt.Println(*newList[2])

  1. c := make(chan int, 5)
  2. wg := &sync.WaitGroup{}
  3. wg.Add(2)
  4. go func() {
  5. for i := 0; i < 5; i++ {
  6. c <- i
  7. }
  8. // 停止写入后,一定到关闭channel
  9. // 否则range 语句会一直执行
  10. close(c)
  11. wg.Done()
  12. }()
  13. go func() {
  14. for v := range c {
  15. fmt.Println(v)
  16. }
  17. wg.Done()
  18. }()
  19. wg.Wait()

goto & label

  1. slice := []int{1, 2, 3}
  2. i := 0
  3. for {
  4. if i >= len(slice) {
  5. goto END
  6. }
  7. fmt.Println(slice[i])
  8. i++
  9. }
  10. END:
  11. fmt.Println("end")

break & continue

  1. slice := []int{1, 2, 3}
  2. for i, v := range slice {
  3. if i == 1 {
  4. continue
  5. }
  6. fmt.Println(v)
  7. }
  1. slice := []int{1, 2, 3}
  2. for i, v := range slice {
  3. if i == 1 {
  4. break
  5. }
  6. fmt.Println(v)
  7. }

Function


函数传参

  1. // 单参数
  2. func DoSomeing(n int) {
  3. ...
  4. }
  5. // 多参数
  6. func DoSomeing(n int,s string) {
  7. ...
  8. }
  9. // 多参数简写
  10. func DoSomeing(n1,n2 int,s string) {
  11. ...
  12. }
  13. // 参数列表
  14. func DoSomeing(n1 int, nums ...int) {
  15. fmt.Println(n1)
  16. fmt.Printf("%T\n", nums)
  17. }
  18. func main() {
  19. DoSomeing(1,2,3,4)
  20. }

函数返回值

  1. // 单返回值
  2. func DoSomeing(n int) int {
  3. return n / 10
  4. }
  5. // 多返回值
  6. func DoSomeing(n int) (string,int){
  7. return "取余10",n/10
  8. }
  9. // 隐式返回(命令返回参数)
  10. func DoSomeing(n int) (num int){
  11. num = n/10
  12. return
  13. }
  14. // 局部变量遮蔽命名返回参数(需要显示返回)
  15. func DoSomeing(n int) (num int){
  16. {
  17. var num = n/10
  18. return
  19. }
  20. }

匿名函数

不用定义函数名,可作为结构体字段或在channel中传输,有很大的灵活性

  1. sumF := func(n1,n2 int) int{
  2. return n1 + n2
  3. }
  4. sumF(1,10)

以上是go中匿名函数的简单示例,可以在代码片段复用性低的情况下合理使用,增加代码可读性

  1. type User struct {
  2. name string
  3. level int // 岗位等级
  4. workYear int // 工资年限
  5. baseSalary int // 基础薪资
  6. bonusMethod func(w,b int) int
  7. }
  8. func main() {
  9. us := []User{
  10. {"Julia",1,3,20000}
  11. {"Daived",3,4,10000}
  12. }
  13. // 根据不同等级设置不同奖金发放方法
  14. // TODO: 思考更好的使用场景
  15. for _,u := range us {
  16. switch u.level {
  17. case 1:
  18. u.bonusMethod = func (w,b int) int {
  19. return w*3*b
  20. }
  21. case 2:
  22. u.bonusMethod = func (w,b int) int {
  23. return w*1.5*b
  24. }
  25. default:
  26. u.bonusMethod = func (w,b int) int {
  27. return w*b
  28. }
  29. }
  30. }
  31. }

通过给结构体字段设置匿名函数类型,可以灵活的切换计算逻辑,如上代码,可根据不同人群设置不同的计算方式

  1. // 这里结合上个示例,使用go runtime、channel、sync.WaitGroup{}设置并发处理奖金
  2. bonusPool := struct{
  3. total int
  4. }
  5. // 互斥锁,防止并发安全问题
  6. l := sync.Mutex{}
  7. wg := sync.WaitGroup{}
  8. for _,u := range us {
  9. wg.Add(1)
  10. go func(){
  11. l.Lock()
  12. bonusPool.total = bonusPool.total + u.bonusMethod()
  13. l.Lock()
  14. wg.Done()
  15. }()
  16. }

有的时候我们并不想即时的处理代码片段,可以像上面代码一样(虽然不是很贴切)将他们放到channel中去处理

  1. doFs := []func(x, y int) interface{}{
  2. func(x, y int) interface{} {
  3. return x + y
  4. },
  5. func(x, y int) interface{} {
  6. return x * y
  7. },
  8. func(x, y int) interface{} {
  9. return x / y
  10. },
  11. }
  12. for _, doF := range doFs {
  13. fmt.Println(doF(1, 10))
  14. }

现在我们有一堆任务,但是他们执行的方法各不相同,可以将它们放到一个数组或者切片中等待处理

闭包

  1. function a(){
  2. var i=0;
  3. function b(){
  4. console.log(++i);
  5. }
  6. return b;
  7. }

js中的闭包和go中的类似,只不过是基于嵌套函数实现,子函数可以引用局部变量并改变值,但go并不支持这样

  1. def a():
  2. i = 1
  3. def b():
  4. i = i + 1
  5. print(i)
  6. return b
  7. x = a()
  8. x()
  9. x()
  10. x()

py中的闭包是对引用变量的保护,不可直接修改引用变量的值,所以在执行以上代码时会抛出UnboundLocalError

  1. func a() func() {
  2. i := 1
  3. b := func() {
  4. fmt.Println(i)
  5. fmt.Printf("让我们看下每次调用的指针: %p\n", &i)
  6. i++
  7. }
  8. return b
  9. }
  10. func main() {
  11. c := a()
  12. // 调用c函数时因为闭包导致函数内i变量逃逸(重复引用)
  13. c()
  14. c()
  15. c()
  16. c()
  17. // 直接调用a函数,未触发闭包
  18. a()
  19. // 新函数变量会产生新的闭包,闭包之间互不影响
  20. d := a()
  21. d()
  22. }

以上代码是go中闭包的实现,在很多场景下闭包可以减少新变量的开销

  1. // 迭代会使得i解引用,使得到值不再使用,所以会正常输出
  2. for i := 0; i < 3; i++ {
  3. fn := func() {
  4. fmt.Println(&i)
  5. }
  6. fn()
  7. }
  1. var dummy [3]int
  2. var f func()
  3. // 因为在循环结束时i = 3
  4. for i := 0; i < len(dummy); i++ {
  5. println(i)
  6. f = func() {
  7. println(i)
  8. }
  9. }
  10. f()
  11. // 以上循环可以理解为以下代码
  12. for i := 0; i < len(dummy){
  13. println(i)
  14. f = func() {
  15. println(i)
  16. }
  17. i++
  18. }
  19. f()

递归函数

运行的过程中调用自己

  1. func Accumulate(n int) int {
  2. n++
  3. if n < 100 {
  4. n = Accumulate(n)
  5. }
  6. return
  7. }
  1. func Factorial(n int) int {
  2. if n <= 1 {
  3. return 1
  4. }
  5. return n * Factorial(n-1)
  6. }

延迟调用

defer关键字,使用方法类似go关键字 作用是函数return前执行 结合闭包可以进行资源释放

  1. func demo(n int) {
  2. num := 10 * n
  3. defer fmt.Print(num)
  4. num = num / 3
  5. }
  1. f,err := os.Open("./demo.txt")
  2. if err != nil {
  3. return
  4. }
  5. defer f.Close()
  1. m := make(map[string]interface{})
  2. l := sync.Mutex{}
  3. wg := sync.WaitGroup{}
  4. for i := 0; i < 10; i++ {
  5. wg.Add(1)
  6. go func(i int) {
  7. l.Lock()
  8. defer l.Unlock()
  9. m["num"] = i
  10. defer wg.Done()
  11. }(i)
  12. }
  13. wg.Wait()
  14. fmt.Println(m)

并发情况下通常会使用互斥锁来保证变量的安全性,很容易忘记及时解锁

  1. ns := []int{1, 2, 3, 4, 5}
  2. for _, v := range ns {
  3. defer func() {
  4. fmt.Println(v)
  5. }()
  6. }

以上代码片段同时涉及range defer``closure,通过range拷贝副本值到v, 这时defer调用的闭包函数内引用的变量v始终指向唯一副本变量,变量过程中副本变量的会一直变化。

  1. var n1 int
  2. defer fmt.Printf("defer1 print: %d \n", n1)
  3. defer func() {
  4. fmt.Printf("defer2 print: %d \n", n1)
  5. }()
  6. defer func(n1 int) {
  7. fmt.Printf("defer3 print: %d \n", n1)
  8. }(n1)
  9. n1 += 10

defer在非闭包情况下不能获取最新的值

  1. x := 0
  2. defer fmt.Println("a")
  3. defer fmt.Println("b")
  4. defer func() {
  5. fmt.Println(100 / x) // 触发异常,逐步向外层传递并终止进程gopanic()
  6. }()
  7. defer fmt.Println("d")

多个defer遵循FILO 次序(Fisrt In Last Out)先进后出的方式,遇到异常会跳出继续执行,最后抛出异常

  1. var lock sync.Mutex
  2. func test() {
  3. lock.Lock()
  4. lock.Unlock()
  5. }
  6. func testDefer() {
  7. lock.Lock()
  8. defer lock.Unlock()
  9. }
  10. func main() {
  11. t1 := time.Now()
  12. for i := 0; i < 1000000000; i++ {
  13. test()
  14. }
  15. fmt.Printf("t1 use time: %v\n", time.Since(t1))
  16. t2 := time.Now()
  17. for i := 0; i < 1000000000; i++ {
  18. testDefer()
  19. }
  20. fmt.Printf("t2 use time: %v\n", time.Since(t2))
  21. }

使用defer性能竟然比平常要快,这里可能是go新版本编译器优化后的结果

  1. func doSomeing() (i int) {
  2. i = 0
  3. defer func(){
  4. fmt.Println(i)
  5. }()
  6. return 2
  7. }

以上函数在return时会将i变量的值修改为2

  1. func doSomeing() {
  2. var fc func() = nil
  3. defer fc()
  4. fmt.Println("test")
  5. }

在调用以上函数时,defer允许声明,然后我们会看到打印的test,结束函数时执行fc()触发空指针异常

  1. f,err := os.Open("./demo.txt")
  2. if err != nil {
  3. fmt.Println(err.Error())
  4. }
  5. // defer f.close // panic
  6. if f != nil {
  7. defer f.close // Success
  8. }

以上代码片段在读取文件时,由于没有判断是否返回空指针,导致延迟关闭文件时触发异常,这很考验你的严谨性

  1. func doSomeing() {
  2. f,_ := os.Open("./demo.txt")
  3. f.close()
  4. defer func() {
  5. if err := recover(); err != nil {
  6. fmt.Println(err)
  7. }
  8. }()
  9. }
  10. func main() {
  11. doSometing()
  12. fmt.Println("success")
  13. }

Panic

go语言中没有结构化异常(类似javatry catch),通过panic抛出异常,recover 捕获异常 异常如未被捕获会终止下面代码运行

  1. panic("my painc")
  2. fmt.Println("success")

以上函数由于触发异常success不被打印

  1. defer func(){
  2. if err := recover(); err != nil{
  3. fmt.Printf("revover: %v\n",err)
  4. }
  5. }()
  6. panic("my panic")

panic不会被影响defer的执行,为了保证程序正常运行,必须手动捕获异常

  1. panic("my panic")
  2. if err := recover(); err != nil{
  3. fmt.Printf("revover: %v\n",err)
  4. }

recover函数一定要声明在defer内,并在异常发生之前

  1. panic("my panic")
  2. defer func(){
  3. if err := recover(); err != nil{
  4. fmt.Printf("revover: %v\n",err)
  5. }
  6. }()

以上代码中异常未被捕获,是因为延迟调用一定要在panic发生之前声明(有意识的在写代码前捕获异常,这很重要)

  1. defer func(){
  2. func(){
  3. if err := recover(); err != nil{
  4. fmt.Printf("revover: %v\n",err)
  5. }
  6. }()
  7. }()
  8. panic("my panic")

recover只能在defer内使用,但是不能在defer内的匿名函数中使用

  1. type User struct {
  2. name string
  3. }
  4. func(u User) info() {
  5. fmt.Printf("name : %s\n",u.name)
  6. }
  7. func main() {
  8. var u *User = nil
  9. u.info() // panic
  10. }

空指针是触发panic常见的情况,需常检查指针变量的零值或者空值,invalid memory address or nil pointer dereference

  1. ch := make(chan int, 1)
  2. close(ch)
  3. ch <- 1

Channel通道关闭后,再写入时会出发异常send on closed channel

  1. defer func() {
  2. fmt.Println(recover())
  3. }()
  4. defer func() {
  5. panic("defer panic")
  6. }()
  7. panic("my panic")

defer中的异常会被优先捕获

  1. func test(x, y int) {
  2. var z int
  3. func() {
  4. defer func() {
  5. if err := recover(); err != nil {
  6. z = 0
  7. }
  8. }()
  9. z = x / y // x or y equal to zero, panic integer divide by zero
  10. }()
  11. fmt.Printf("x/y = %d \n", z)
  12. }
  13. func main() {
  14. test(1, 0)
  15. }

defer recover可以捕获函数中的异常,自然也可以捕获匿名函数,同时可以利用匿名函数变量引用的特性,去修复代码异常

  1. func Try(fn func()) (catch string) {
  2. defer func(){
  3. if err := recover(); err !=nil {
  4. catch = err.(string)
  5. }
  6. }()
  7. fn()
  8. return
  9. }

通过匿名函数recover的方式实现,recover返回的类型是接口类型,通过强制转换成string

Error


error类型可以描述函数的调用状态,同时可以使用errors包创建实现error接口的错误对象

  1. func OpenFileTesting(filename string) error{
  2. if filename == "" {
  3. return errors.New("filename is require, and not null")
  4. }
  5. f,err := os.Open(filename)
  6. if err != nil {
  7. return errors.New(fmt.Sprintf("file open faild : %s",err.Error()))
  8. }
  9. defer f.close()
  10. return nil
  11. }

Method


Go中的没有OOP编程语言中Class声明方式,可以通过Method+Struct实现部分面向对象的特性

  1. type User struct {
  2. Name string
  3. Action string
  4. }
  5. func (u User) Do(){
  6. fmt.Printf("%s正在%s\n",u.Name,u.Action)
  7. }
  8. func main() {
  9. u := User{
  10. Name: "Dong",
  11. Action: "打篮球",
  12. }
  13. u.Do()
  14. }
  1. // ...
  2. func (u *User) ChangeAction(s string) {
  3. u.Action = s
  4. }
  5. func main() {
  6. // ...
  7. u.ChangeAction("上班")
  8. }
  1. type User struct {
  2. Name string
  3. Action string
  4. Person
  5. }
  6. func (u User) Sleep() {
  7. fmt.Printf("User Sleep Method\n")
  8. }
  9. type Person struct {
  10. }
  11. func (Person) Sleep() {
  12. fmt.Printf("Person Sleep Method\n")
  13. }
  14. func main() {
  15. u := User{
  16. Name: "Dong",
  17. Action: "打篮球",
  18. }
  19. u.Sleep()
  20. }
  1. type User struct {
  2. Name string
  3. Action string
  4. Person
  5. }
  6. func (u User) Sleep() {
  7. fmt.Printf("User Method Sleep \n")
  8. }
  9. func (u *User) Eat() {
  10. fmt.Printf("User Method Eat \n")
  11. }

Interface