1、基础学习

(1)声明变量
var identifier type
var identifier1, identifier2 type
第一种,指定变量类型,如果没有初始化,则变量默认为零值
第二种,根据值自行判定变量类型。
第三种,省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误。

// 声明一个变量并初始化
var identifier [type] = 值
var a string = "Runoob"
var a = "RUNOOB"

(2)多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误

// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)
var (
	Debug            = false
	DebugLog         = NewLog(os.Stdout)
	DefaultRowsLimit = 1000
	DefaultRelsDepth = 2
	DefaultTimeLoc   = time.Local
	ErrTxHasBegan    = errors.New("<Ormer.Begin> transaction already begin")
	ErrTxDone        = errors.New("<Ormer.Commit/Rollback> transaction not begin")
	ErrMultiRows     = errors.New("<QuerySeter> return multi rows")
	ErrNoRows        = errors.New("<QuerySeter> no row found")
	ErrStmtClosed    = errors.New("<QuerySeter> stmt already closed")
	ErrArgs          = errors.New("<Ormer> args error may be empty")
	ErrNotImplement  = errors.New("have not implement")
)

(3)go语言常量

const identifier [type] = value
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"

常量还可以用作枚举:

const (
    Unknown = 0
    Female = 1
    Male = 2
)

(4)iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
    a = iota
    b
    c
)
等价于
const (
    a = iota
    b = iota
    c = iota
)
package main

import "fmt"

func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

运行结果:0 1 2 ha ha 100 100 7 8

⚠️注:iota简写会记录上一行的表达式,并且iota自增

package main

import "fmt"
const (
    i=1<<iota
    j=3<<iota
    k
    l
)

func main() {
    fmt.Println("i=",i)
    fmt.Println("j=",j)
    fmt.Println("k=",k)
    fmt.Println("l=",l)
}

iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。
(5)go切片
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
(6)切片截取

package main

import "fmt"

func main() {
   /* 创建切片 */
   numbers := []int{0,1,2,3,4,5,6,7,8}  
   printSlice(numbers)

   /* 打印原始切片 */
   fmt.Println("numbers ==", numbers)

   /* 打印子切片从索引1(包含) 到索引4(不包含)*/
   fmt.Println("numbers[1:4] ==", numbers[1:4])

   /* 默认下限为 0*/
   fmt.Println("numbers[:3] ==", numbers[:3])

   /* 默认上限为 len(s)*/
   fmt.Println("numbers[4:] ==", numbers[4:])

   numbers1 := make([]int,0,5)
   printSlice(numbers1)

   /* 打印子切片从索引  0(包含) 到索引 2(不包含) */
   number2 := numbers[:2]
   printSlice(number2)

   /* 打印子切片从索引 2(包含) 到索引 5(不包含) */
   number3 := numbers[2:5]
   printSlice(number3)

}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

(7)切片append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

2、进阶

(1)在定义常量组时,如果不提供初始值,则表示将使用上行的表达式。

package main

import "fmt"

const (
    a = 1
    b
    c
    d
)

func main() {
    fmt.Println(a)
    // b、c、d没有初始化,使用上一行(即a)的值
    fmt.Println(b)   // 输出1
    fmt.Println(c)   // 输出1
    fmt.Println(d)   // 输出1
}

(2)字符串去除空格和换行符

package main  
  
import (  
    "fmt"  
    "strings"  
)  
  
func main() {  
    str := "这里是 www\n.runoob\n.com"  
    fmt.Println("-------- 原字符串 ----------")  
    fmt.Println(str)  
    // 去除空格  
    str = strings.Replace(str, " ", "", -1)  
    // 去除换行符  
    str = strings.Replace(str, "\n", "", -1)  
    fmt.Println("-------- 去除空格与换行后 ----------")  
    fmt.Println(str)  
}

(3)Golang sizeof 占用空间大小

package main

import (
    "fmt"
    "unsafe"

)

type Man struct {
    Name     string
    Money    int
    Age      int32
}

func main() {

    m := Man{Name:"james", Money:1000000, Age:30}

    fmt.Println("man size:", unsafe.Sizeof(m))
    fmt.Println("name size:", unsafe.Sizeof(m.Name))
    fmt.Println("money size:", unsafe.Sizeof(m.Money))
    fmt.Println("age size:", unsafe.Sizeof(m.Age))
}

运行结果:
man size: 32
name size: 16
money size: 8
age size: 4

⚠️注:
go语言的string是一种数据类型,这个数据类型占用16字节空间前8字节是一个指针,指向字符串值的地址后八个字节是一个整数,标识字符串的长度;注意go语言的字符串内部并不以'\0'作为结尾,而是通过一个长度域来表示字符串的长度。

type mystr struct {
    strbuf uintptr;
    strlen uint64;
}

(4)iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。

package main

import "fmt"

const (
    a = 1
    b
    c
    d
)

func main() {
    fmt.Println(a)
    // b、c、d没有初始化,使用上一行(即a)的值
    fmt.Println(b)   // 输出1
    fmt.Println(c)   // 输出1
    fmt.Println(d)   // 输出1
}

(5)Go 的自增,自减只能作为表达式使用,而不能用于赋值语句。

a++ // 这是允许的,类似 a = a + 1,结果与 a++ 相同
a-- //与 a++ 相似
a = a++ // 这是不允许的,会出现变异错误 syntax error: unexpected ++ at end of statement

(6)切片初始化

s :=[] int {1,2,3 } 直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3。其 cap=len=3。

s := arr[:] 初始化切片 s,是数组 arr 的引用。

s := arr[startIndex:endIndex] 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

s := arr[startIndex:] 缺省 endIndex 时将表示一直到 arr 的最后一个元素。

s := arr[:endIndex] 缺省 startIndex 时将表示从 arr 的第一个元素开始。

s1 := s[startIndex:endIndex] 通过切片 s 初始化切片 s1

s :=make([]int,len,cap) 通过内置函数 make() 初始化切片 s,[]int 标识为其元素类型为 int 的切片。

(7)创建结构体

// 创建一个新的结构体
Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407}
// 也可以使用 key => value 格式
Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407}

(8)go语言递归函数

func recursion() {
   recursion() /* 函数调用自身 */
}

func main() {
   recursion()
}

(9)类型转换
type_name(expression)

3、高阶

(1)panic 、recover 、defer

1.引发panic有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。
2.发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数。
3.panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer里面的panic能够被后续执行的defer捕获。
4.recover用来捕获panic,阻止panic继续向上传递。recover()和defer一起使用,但是defer只有在后面的函数体内直接被掉用才能捕获panic来终止异常,否则返回nil,异常继续向外传递。

(2)panic使用场景
一般情况下有两种情况用到:
1.程序遇到无法执行下去的错误时,抛出错误,主动结束运行。
2.在调试程序时,通过 panic 来打印堆栈,方便定位错误。
(3)panic总结

1、panic 在没有用 recover 前以及在 recover 捕获那一级函数栈,panic 之后的代码均不会执行;一旦被 recover 捕获后,外层的函数栈代码恢复正常,所有代码均会得到执行;
 2、panic 后,不再执行后面的代码,立即按照逆序执行 defer,并逐级往外层函数栈扩散;defer 就类似 finally;
 3、利用 recover 捕获 panic 时,defer 需要在 panic 之前声明,否则由于 panic 之后的代码得不到执行,因此也无法 recover;

(4)关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入
(5)通道无缓冲和有缓冲的区别
无缓冲是同步的,例如 make(chan int),就是一个送信人去你家门口送信,你不在家他不走,你一定要接下信,他才会走,无缓冲保证信能到你手上。

有缓冲是异步的,例如 make(chan int, 1),就是一个送信人去你家仍到你家的信箱,转身就走,除非你的信箱满了,他必须等信箱空下来,有缓冲的保证信能进你家的邮箱。

学习链接:
https://studygolang.com/articles/10835

https://www.runoob.com/go/go-basic-syntax.html