golang学习笔记
- go version 查看go的版本号
- go env 查看go的环境
- 先go build xx.go 然后再./xx就可以输出 先编译在运行 或者go run xx.go 直接运行
- 先编译再运行:优点:编译出的可执行文件可以再各个终端上运行,即使没有安装go环境,依然可以运行。原因:编译后,将代码依靠的库文件一起打包到编译好的文件里,所以可以直接运行,并且代码的容量也会变大
- go build -o xxx xx.go 将编译出的文件文件名变为xxx
- 从main()开始执行,导入的包和使用的变量,如果不使用就会报错不能编译
- gofmt -w xx.go 格式化演示 类似cat
- golang全部使用utf-8编码,不会有中文乱码,常量用const修饰,定义的时候就必须赋值,a=iota则a为0 ,每使用一个iota,iota的值就会加1
- golang 英文字母1个字节(byte可以用来表示一个字节),中文三个字节
- bool类型只能用false和true 不能用0和1表示
- golang中字符串是不可改变的!一旦赋值就不可变
- ``反引号,可以输出一段话类似python的””” “””
- 字符串拼接 加号不能开头,要放在末尾(如果一行太多),原因默认会在一行末尾加上;
- map返回两个参数 第一个是value,第二个是 是否存在所找key if _,ok := HashMap[target-v];ok 如果true的话就执行
- 数据类型:int、float、string、bool、数组、slice切片、map、指针、结构体、channel管道。
- Print输出无换行、Println输出有换行、Printf是格式化输出(默认无换行,如果有需要,需要自己加)、Sprintf是格式化传入的数据并赋给变量,终端不显示。Scanf是格式化输入,Scanln直接输入,fmt.Scanln(&nm)获取一行的 fmt.Scanf()格式化获取内容(空格隔开) 获取键盘输入。
- golang所有数据类型的转换必须是强制转换,不能自动转换
- strconv 函数 可以将其他数据类型转为string类型,Formatint将整数转为其他进制数字,Formatfloat,Formatbool,ParseBool字符串转为bool,ParseXX => ParseInt,ParseFloat
- var ptr *int = &num ptr存的是num的地址
- 变量、函数名,首字母大写才能被其他包访问 首字母大写是共有的,首字母小写是私有的
- %b 二进制输出 0开头 是8进制 0x开头是16进制
- math下有一个rand包 rand.Intn(n)生成一个0-n的伪随机数,需要先设置一个随机种子,rand.Seed(time.Now().Unix())根据秒数变化 1970年0:0 Seed(time.Now().UnixNano)根据纳秒变化。
- lable2:外面循环 内部循环 lable1: (break lable2) 退出最外层 continue也可以跳到指定标签。
- goto lablex ,label :xxx 可以使用但不建议,因为会导致程序执行顺序混乱 跳转执行 中间的代码执行不了
- 每个包对应一个文件夹,引用的时候是包名加文件 package 包名 (打包包名)
- 包的引用从GOPATH下的src开始找 所以包名路径要从src下开始
- 别名 xxx(引入包名) 重命名 将xxx命名为其他别名,原来包名不能再使用
- go build -o 重命名 xx/xx/xx/xx/main.go 编译 成重命名 可以直接运行 在不同设备上 只有当package main时才可以打包 这时他会把其他需要的文件一起打包到里面
- go函数不支持函数重载 不能同一个函数名有不同参数 生成不同的函数 即函数名不能重复 跟返回值和形参类型无关
- 函数是一种变量 可以将函数赋值给变量 可以通过变量名使用 函数本身也可以作为形参传入
- 函数支持对返回值命名 func xx (num int,sun int) (adnm int,subnm int) return 就不用再加其他东西
- golang 支持可变参数 func sum (nm int, arg… int) int arg是切片(名字可以是其他,…三个点是关键),可以通过下标访问对应的数据例如:arg[0],arg[1]
- switch 后的数据类型和case的数据类型需要一样 case后面可以有多个表达式和switch后的表达式匹配 在case语句中 输入fallthrough 可以穿透下一层 x.type()和x = interface{}和switch配合可以看到x空接口指向的类型
- init函数在main函数前执行,每个函数都有一个init函数(初始化函数) 变量定义->init->main函数
- 匿名函数,一般调用一次,可以使用匿名函数,1、定义时,直接使用,只能调用一次 res := func (a int,b int) int{xxxx}(a,b)。2、匿名函数赋给变量,调用变量可以使用函数
- (在一个函数内)闭包是由函数和引用到的变量构成的,在这个闭包里,变量只声明一次,不会重复声明,重复调用闭包会重复更新数据 闭包相当于类
- 函数中的defer(延时机制) 在函数执行完毕后,快速释放所有资源。 defer 后面的语句,会进入一个单独的栈(先入后出)同时会将对应的变量数据同时拷贝进栈(深拷贝) 待函数内其他语句执行完后(return 后)【即函数执行完毕后再执行】,再从后往前一个个出栈,弹出对应语句。创建资源后,后面加上defer用于关闭资源, 可以在函数结束后再释放资源,函数内部的资源依然可以继续使用资源
- 值传递(深拷贝)基本数据类型:int,float,bool,string,数组,结构体,引用传递(浅拷贝)更快:指针,切片slice、map、管道chan,interface等都是引用类型
- 内置函数 len、nm = new(“指针类型”)分配类型,主要分配值类型,nm默认为0,new的两个作用,分配一个空间用来存储值,再分配一个空间用来存储指向值的指针
- go中错误处理机制:defer、recover、panic defer:=func(){err = recover() if err!=nil{ fmt.Println(err)}} ()定义一个匿名函数,recover()会接受报错
- err = errors.New(“自定义错误”) panic(err) 如果出错,则终止返回自定义错误并退出
- 数组是相同类型的集合,大小确定后不能超出范围,不能动态变化 切片:动态变化的数组,slice是数组的引用。var 切片名 []类型 []里不需要写大小,(引用类型,由三部分组成,头指针指向地址,长度,容量)
- cap()查看容量,能存储的最大范围
- 切片的使用方式:1、定义一个切片,让切片引用指定数组。make创建切片,var xx []类型 = make([]类型,大小,容量),对于切片,必须make后才能使用3、定义切片时,直接赋值,var xx []类型 = []类型 {数据值}
- 使用make来创建slice,map,chanel、指针
- 切片可以用append进行动态追加,slice2 = append(slice2,待追加的数据)append后必须重新赋值,它并不改变自身数据,(先创建新的数组【将待添加的数据和原来的数据都加到新数组里】,然后再用新的切片引用) ,追加切片:name = append(name,name1…),只能追加切片,不能是数组,必须加上…
- 切片是引用类型,在函数实参传给形参时,对数据进行改变,原切片也会改变,因为是引用类型
- copy(切片1,切片2),将切片2的数据拷贝给切片1,他们都是独立的空间,拷贝后互不影响,必须都是切片,切片2大于小于切片1的大小都不会报错,大的话,就只取切片1本身的长度得切片2
- 字符串底层也是切片,但是和切片不同的是,str不可变,不能用str[0]=”s”改变,切片可以改变,若想改变字符串,可以先转换为切片,然后改变数据后再转为字符串,arr = []byte(str),str = string(arr)【不能处理中文,byte按字节处理数据,而一个汉字占三个字节,所以不能处理中文】或者arr1 = []rune(str),str = string(arr1)【常用】
- 二维数组 var name [][]类型,先初始化再赋值,二维数组再内存里是每一行的头代表一个指针,指向这个数组;var name [][]类型 = [][]类型{数组xx1,数组xx2} 二维数组的遍历也可以用for xxxx和for range
- map是key-value类型,var name map[key类型]val类型 slice、map、function不可以作为key,不能用==比较。声明map是不会分配内存,只有make后才能赋值和使用 a = make(map[key类型]val类型,大小),key一样的话会后来的会覆盖前面的,key不能重复,是map是无序排列的
- map的增加和修改,name[key] = value 即可 map的删除,delete(name,key) 删除,无论有无都不会报错;查找 val,ft = name[key],如果key存在,则ft为true,val为对应key的值;如果key不存在,则ft为false
- map切片,map个数动态增加;name := []map[string]string
- sort.Ints(切片)对切片进行递增排序,map是引用类型数据,可以自动扩容,map的value经常用结构体表示会很方便
- struct可以加个tag标签 序列化struct 用来方便不同语音的交流、反引号json:字段名反引号
- 方法作用在数据类型上的,自定义类型都有方法
- func (a A) test{} A是结构体,表明test绑定为A结构体的一个方法 test方法只能通过A类型变量调用,其他不能调用 func (xx type) method (形参类型) (返回值) {}
- 如果一个方法定义了string方法,在调用fmt.Println时会默认自动调用这个string方法
- 只有函数或方法是指针时才能调用指针类型,调用者会发生改变,否则结构体和函数都还是值拷贝。函数时,函数是值类型就只能传递值类型,是指针类型就只能传递指针,方法时,函数是值类型,无论传值或者地址都是值拷贝,传递的值类型,函数是指针类型,无论传值或地址都是引用拷贝,传递的指针类型。
- 结构体淡化了指针和普通值,定义后,传递值和指针,编译器默认会加上指针的符号
- 封装:设置个Set和Get方法来存储和读取私有信息在不想对外公开的包中,给其他想引用的包流出接口
- 继承是通过在子结构体中加入 匿名的父结构体即可
- 接口:type xxx interface{ 方法…. } 接口里声明的方法不需要实现,不能使用变量,在其他结构体需要使用时,写出接口的方法,实现接口是指实现全部接口的方法。接口的实现是基于方法的,不需要指定实现哪个接口,只需要实现接口里的方法即可 interface是一个指针,是引用,使用时必须先初始化否则为nil interface{}空接口的话 可以指向任意变量类型
- 继承解决代码的复用和可维护性,接口实现代码的解耦,比继承更加灵活
- 类型断言,c = a.(b) c是b类型的,a也是b类型的,a是空接口,转为具体类型
- os.args 可以获得对应的命令行的内容,以空格隔开,遍历os.orgs 每一个对应的都是以空格隔开的内容 而flag包更常用于解析命令行参数
- json:encoding/json包 data,err = json.Marshal(待编码的数据) 返回的是byte切片 json序列化。json反序列化:err = json.unmarshal([]byte(待转换的json字符串),&接受转换的数据类型变量) 反序列化slice、map类型不需要make,因为底层已经make过了
- 对于结构体的序列化 结构体标签tag 反引号 json:”name”反引号 相当于重命名 便于接受和传递使用 在声明时使用
- 序列化:将数据类型json序列化转为json字符串,反序列化:奖json字符串转为对应的数据类型
- 单元测试:确认一个模块结果是否正确 testing测试框架 go test: 定义一个包必须以_test.go结尾,里面写入func TestXxxxx(name *testing T) { t.Fatalf(错误的信息) t.Logf(正确的信息)} go test -v运行测试用例 一个测试文件中可以有多个测试函数 go test命令:正确不输出,错误输出,go test -v命令:正确错误都输出。测试单个文件:go test -v 测试文件名 测试文件函数即可 ,测试单个方法:go test -v -test.run 测试方法名
- goroutine:线程,主线程可以有多个协程,协程是轻量的线程 ,go协程特点:有独立的栈空间,共享程序堆空间,调度由用户控制,协程是轻量级的线程。在程序中 调度函数时前面加个go即可(开启了一个协程)
- MPG:M是操作系统的主线程(物理线程),P是程序执行时的上下文环境,G是协程。golang设置cpu的个数 导入runtime包,runtime.NumCPU()获取当前逻辑cpu的数量(不一定是实际的),GOMAXPROCS(n)设置最大执行cpu的数量
- go build -race 可以查看程序竞争使用的资源数,声明全局互斥锁,lock sync.Mutex 全局的互斥锁,lock.Lock()执行前上锁,lock.Unlock()解锁,管道:channel是线程安全,多个协程操作同一个管道时,不会发生资源竞争,但是管道是有类型的,除非设置空接口,需要使用类型断言,name = 接受变量.(类型),否则不能使用不同类型数据。var 变量名 chan 数据类型,是channel的声明,必须make后才能使用。变量名<- 数据,这是向管道中写入数据,容量确定后是不能改变的,存入的数据不能超过管道的容量,但是可以存入一个取一个,name = <-变量名 取出第一个数据。写入和取出时都会堵塞,有写入就必须要有读。
- channel管道关闭后,不能向里面写入数据,但可以继续读取数据,使用close(name)可以关闭。 channel 支持for-range ,for data :=range 管道{},遍历时若channel已经关闭,会正常遍历数据,遍历时若channel没有关闭,会返回deadlock错误。如果容量不够,管道一直写的话,会报死锁错误,可以慢点读,但不能不读。
- 管道可以声明为只读或只写,var name chan<- 数据类型,name只能写入,不能读取。var name <-chan 数据类型,name只能读取不能写入。select可以解决不关闭管道中读取数据问题,for{ select { case v := <-name: xxxx case x := <-xxx default:xxxx } } 如果取不到就向下一个case,都取不到就进入default。退出的时候可以使用break label:或者使用return。err :=recover() 可以接收panic错误,这样协程发生错误时,其他线程及主线程可以继续运行不会被迫终止。
- 反射 reflect:reflect.Type类型,reflect.TypeOf(变量)获取变量,通过Type可以调用很多方法,操作变量。reflect.Value。reflect.Value和interface{}和变量相互转换。将interface{}转为Value类型,rVal := reflect.ValueOf(b),Value转为interface{}, iVal :=rVal.interface() Kind是类型,Type是类别,类型大于类别,附地址的时候,要用.Elem().Setxxx() .Elem是指向对应地址里对应的值,
- redis:远程字典服务器,默认16个数据库。数据类型:字符串、hash、列表、集合、有序集合 go链接redis,导入包后,con = redis.Dial(tcp,ip:端口) 链接目标数据库,con.Do(“set”,xxxx)存,con.Do(“get”,xxx)取:返回的是空接口,redis.String(con.Do(“get”,xxx))可以将对应的数据转换为字符串显示。
- 环形链表,数组模拟的花可用取模来存取,(tail+1)%maxsize == head时加满
-
冒泡排序: 元素两两比较,只要前一个元素大于后一个元素就对换位置。从首元素到最后元素逐个两两比较进行一轮一轮的排序。
// [冒泡排序]传入切片, func BubbleSort(nums []int, length int) { // 外层循环用来控制次数 for i := 0; i < length-1; i++ { // 内层循环用来比较相邻的,并将最大的交换到最后 for j := 0; j < length-1; j++ { if nums[j] > nums[j+1] { nums[j], nums[j+1] = nums[j+1], nums[j] } } } } -
选择排序:每次选择未进行排序的序列中最小的元素,逐个放在序列的前面。
// [选择排序]传入切片 切片是引用传递,原地排序 func SelectSort(nums []int, length int) { // 外层循环用来控制次数和确定每个待交换的数 for i := 0; i < length-1; i++ { // 记录假设的最小值 下标和值 Min_num := i Min_size := nums[i] // 内层循环找到比假设值小的,则更新记录 for j := i + 1; j < length; j++ { if nums[j] < Min_size { Min_num = j Min_size = nums[j] } } // 将更新的记录和外层待交换的数进行交换 nums[i], nums[Min_num] = Min_size, nums[i] } } -
插入排序:从序列的第二个元素开始依次遍历整个序列,固定当前被遍历的对象,比较其与其位置左边所有的对象,若有对象比当前固定的对象大,则将该对象后移,直到被固定的对象左边的所有元素都比所固定的对象小为止。
// [插入排序]传入数组切片 切片是引用传递,原地排序 func InsertSort(nums []int, length int) { // 外层循环,用来控制从第二个元素到最后 for i := 1; i < length; i++ { // 记录当前需要插入的元素 temp := nums[i] j := i - 1 // 从前面已经排好序的数组找到合适的位置 for j >= 0 && nums[j] > temp { nums[j+1] = nums[j] j-- } // 将待插入的元素插入即可 nums[j+1] = temp } } -
快速排序:首先我们找一个基准数固定,将序列中所有比该基准数小的数都放到该基准数的左侧,将序列中所有比该基准数大的数都放在该基准数的右侧。然后不断左侧递归前述操作,不断右侧递归前述操作。
// [快速排序]传入切片 原地修改切片数据 func QuickSort(nums []int, left int, right int) { // 存储原来left和right的位置 l, r := left, right // 如果left和right指针指向同一位置或者交叉返回 if left >= right { return } // 假设待比较的值,在每个数组的第一个 temp := left // 左指针小于右指针的时候,继续比较和交换 for left < right { // 先从右往左找到第一个小于 nums[temp]假定值 的值 for left < right && nums[right] >= nums[temp] { right-- } // 再从左往右找到第一个大于 nums[temp]假定值 的值 for left < right && nums[left] <= nums[temp] { left++ } // 交换左右这两个值 nums[left], nums[right] = nums[right], nums[left] } // 此时left和right指向同一个数,将他们指向的数和最开始假定的值交换即可 nums[temp], nums[left] = nums[left], nums[temp] // 然后递归遍历排序左边的数据 QuickSort(nums, l, left-1) // 递归遍历排序右边的数据 QuickSort(nums, right+1, r) } -
golang二进制bit位的常用操作
1、golang二进制bit位的常用操作,biu是一个转换二进制显示的库: "github.com/imroc/biu" 2、1Byte = 8bit 3、1<<7 相当于 0000 0001 -> 1000 0000 相当于从右往左移动7位(最右边是第一位,最左边是8位,假设1是uint8) 4、假设a = (30)00011110 则b := a|(1<<7)b的结果为 (158)1001 1110,使用biu.ToBinaryString(b) 得到1001 1110 5、同理 可以左移 << 右移>> ,&与|或^非(a^b)异或,使用他们可以得到某一位想要的值或者得到新值,&1、|0 和他们操作会得到原来自己的数 -
向layui传入json数据时,需要满足以下格式,Data传入的数据必须是列表,否则显示错误
{
Code: 0,
Msg: “成功”,
Count: 10,
Data: [],
} -
列表中加入字典的方法,
// 定义一个空接口列表 var role_data []interface{} // 向接口列表加入数据时,加入字典形式的即可 role_data = append(role_data, map[string]interface{}{ "id": 1, "username": "超级管理员", "status": true, "sort": 5, "created": time.Now().Year(), "updated": time.Now().Month(), }) -
iris返回json数据的方法,[ctx iris.context] ctx.json(待返回的数据)函数帮你处理,自己不用额外处理
-
golang的defer是遵循后进先出原则,即后定义的先执行
-
如果defer传入的函数时含参函数,则defer(存在一个列表,定义函数时会传入defer的列表)在定义的时候的同时,会将形参传入,当时就已传入,不会受后进先出再影响,如果是闭包(没有参数传入,直接使用全局或局部变量)是遵循后进先出原则,是在函数结束后再继续执行
-
defer在return后执行,如果return中有地址的引用,且defer中有对该值的更改,则返回后的,用指针接收的值会变为更改后的值
-
defer 里的recover可以捕获异常,恢复协程,外部函数执行到panic后会立即跳到defer,不会继续执行panic,而是执行defer里的信息,defer 里 err:=recover(),如果err不为空,则err里的内容为捕获到的异常消息,并且同时不会再报panic错误,但是并不会继续往下执行
-
go test目的:功能、模糊、性能测试,测试方法:本地模式(指定),列表模式(多目录)
-
文件_test.go 功能测试:func TestParseSize(t *testing.T),模糊测试:func FuzzParseSize(t *testing.F), 性能测试:func BenchmarkParseSize(b *testing.B),主函数入口 func TestMain(m *testing.M)
-
加锁:加锁的范围越小越好,尽量不要再for循环里加入锁,sync函数,里的Mutex和RWMutex,解决协程安全和数据竞争,尽量避免使用锁(会堵塞,增加运行时间)sync.Map适合读多写少的情况下,通过内部的原子访问和锁机制实现结合的线程安全
-
开启协程:wg := sync.WaitGroup{} xxxxx wg.Add(1) go func(){} wg.Wait(); 加入互斥锁 local := sync.Mutex{} local.lock()xxxx local.Unlock();读写互斥锁,读写互斥,写写互斥,local := sync.RWMutex{} 读锁:RLock()RUnlock(),写锁Lock(),ULock();
-
sync.Pool 创建临时连接池,减少内存分配和降低GC压力,pool长度默认为cpu线程数,在pool中的对象随时可能在不被通知的情况下回收,sync.Once 初始化单例资源,适用于加载一次的场景
- Sort函数的使用(sort.Sort())
sort包中实现了3种基本的排序算法:插入排序.快排和堆排序.和其他语言中一样,这三种方式都是不公开的,他们只在sort包内部使用。所以用户在使用sort包进行排序时无需考虑使用那种排序方式,
sort.Interface定义的三个方法:
1. 获取数据集合长度的Len()方法
2. 比较两个元素大小的Less()方法
3. 交换两个元素位置的Swap()方法
实现上述三种方法就可以顺利对数据集合进行排序。sort包会根据实际数据自动选择高效的排序算法。
type Interface interface {
// 返回要排序的数据长度
Len() int
//比较下标为i和j对应的数据大小,可自己控制升序和降序
Less(i, j int) bool
// 交换下标为i,j对应的数据
Swap(i, j int)
}
任何实现了 sort.Interface 的类型(一般为集合),均可使用该包中的方法进行排序。这些方法要求集合内列出元素的索引为整数。
示例:
type Person struct {
Name string
Age int
}
type ByAge []Person
//实现了sort接口中的三个方法,则可以使用排序方法了
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func Example() {
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
fmt.Println(people)
sort.Sort(ByAge(people)) //此处调用了sort包中的Sort()方法,我们看一下这个方法
fmt.Println(people)
// Output:
// [Bob: 31 John: 42 Michael: 17 Jenny: 26]
// [Michael: 17 Jenny: 26 Bob: 31 John: 42]
}