golang小知识点

uint类型不能有负数,int类型可以为正也可以为负

goroutinue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
	go func() {
		func() {
			fmt.Println("这是子go")
			// 退出当前函数(此匿名函数)
			// return
			// 退出当前进程(主携程加子携程合在一起是一个进程,似崩溃)
			// os.Exit(-1)
			// 退出当前子携程
			runtime.Goexit()
		}()
		fmt.Println("子go over")
	}()
	time.Sleep(2 * time.Second)
	fmt.Println("主go over")

对于有缓冲的通道,如果写的次数和读的次数不一样就会造成严重的问题,会无限阻塞,在主程序出现就会崩溃死锁,在子携程中阻塞就会内存泄漏。所以最好使用for range来对管道进行读,写完所有数据后最好进行close来关闭这个管道,这样在读的for range中如果发现读的是一个已经关闭的管道(此时管道关闭,无法继续往里面写数据,会崩溃),那它读完这个管道中的所有数据后就会跳出循环。且close要写在写端,如果在读端,和写端速度不一致会导致可能有数据还在往里面写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
	channels := make(chan int, 10)
	go func() {
		for i := 0; i < 2; i++ {
			time.Sleep(time.Second)
			channels <- i
		}
		close(channels)
	}()

	// for range是不知道管道是否已经写完了的,因此当写的携程结束后这边依然在等待新的数据,就会死锁
	// 所以要在写完数据之后关闭管道,这样管道就变为了nil,for range遍历关闭的管道时会退出
	for v := range channels {
		fmt.Println("开始读取")
		fmt.Println(v)
		fmt.Println("结束读取")
	}

可以通过 v,ok:=<-channel 来判断管道是否已经关闭,ok为false则表示没从管道中取出数据,v为对应类型的零值

byte和rune类型

组成字符串的元素叫做字符,可以通过遍历(range)或者下标获取单个字符(s[0])。字符用单引号括起来,字符串用双引号

当需要处理中文日文等,需要用到rune类型,它实际上是一个int32

1
2
3
4
	s1 := "白萝卜"
	s2 := []rune(s1)
	fmt.Println(string(s2[1]))
如果不使用rune直接调用s1,最后出现的会是一个乱码

单词字母可以直接遍历,它属于byte类型

GO语言中只有显式强制类型转换(var a float32 =float32(n)),没有隐式转换。该语法只能在两个类型相互支持转换的时候使用(int到float,string到切片等)

image.png

image.png

1
2
3
	//数组可以通过...来自动推断后续数组初始化值的长度
	a1 := [...]int{1, 2, 3, 4, 5, 6, 7}
	fmt.Println(a1)

切片

当使用var定义了切片或map却没有使用make时,它是对应的零值nil,nil时是无法调用成员的,所以需要make,而结构体的零值不是nil,它里面有对应的成员零值

切片的容量cap()是指从第一个切片所对应的底层数组元素到底层数组最后一个元素的元素总数量

image.png

切片是引用类型,都指向了底层的一个数组,即修改切片会修改到底层数组(切片不保存值,它操作的是底层数组的值),修改数组会影响到切片

image.png

image.png

image.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	a1 := [...]int{1, 3, 5, 7, 8}
	s1 := a1[:]
	s1 = append(s1, 555)
	s1[3] = 777
	fmt.Println(s1)
	fmt.Println(a1)

输出结果:
[1 3 5 777 8 555]
[1 3 5 7 8]

当切片追加一个元素且超过原数组最大值后,它会重新创建一个属于自己的底层数组,再修改切片的值就不会让原底层数组发送改变

image.png

会报panic错误,因为定义了一个int类型的指针但是没有初始化,默认指针值为nil,对空指针所对应的内容进去赋值是不可能的,所以需要使用new函数来申请一个内存地址 var a = new(int)

map如果没有进行初始化(make),它就没有在内存中开辟空间,默认值为nil,是无法往map中写入数据的

image.png

切片,map动态扩容会影响到性能,最好是在创建时就直接估算好

map可以用value ,ok :=m1[“key”]来判断key对应的值是否存在,返回的布尔值存在ok中

image.png

copy复制切片要注意:目标切片如果make出的容量不足以复制源切片的全部元素时,会复制从0开始的部分元素,如果目标切片make给的长度是0,copy是不会自动扩容的,则没有元素复制过去

1
if unicode.Is(unicode.Han,char) 	判断一个字符是不是汉字,unicode.Han

函数类型也可以作为另一个函数的参数和返回值,传参和返回时千万不要加括号,加上括号表示调用此函数方法,不加就是作为参数或者返回值使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func f1(x int){
	fmt.Println("我是f1")
}

func f2(x,y int) int{
	return x+y
}
func f3(x func(int)) func(int,int)int{
	return f2
}

func main(){
	f4:=f3(f1)
	fmt.Println(f4(5,10))
}

defer延迟加载在注册时就会将变量的状态代入进去会先进入代入延迟加载中的变量的值,即a会先代入1这个值,后面a改变也不会影响到defer的值,如果defer函数中嵌套了函数calc(20,add()),它也会先将里面的函数(add)计算出值然后再搁置在最后结束时运行defer,defer只会保存最外层的函数至最后执行

1
2
3
	a:=1
	defer fmt.Println(a)
	a=2

构造函数(结构体)

约定成俗用new开头作为构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type person struct {
	name string
	age int
}

func newPerson(name string,age int) *person{
	return &person{
		name:name,
		age:age,
	}
}

func main() {
	p1:=newPerson("张三",12)
	fmt.Println(p1)
}

结构体可以使用声明的方式产生新的变量,也可以通过函数来产生,通过函数产生时最好返回值为结构体的指针,减少程序的内存开销,因为结构体属于值传递,如果不使用指针就是将函数返回值拷贝一份给变量,当结构体的元素更多或者使用函数的次数更多,会给内存造成压力,而且复制指针比复制结构体内容要更快,内存压力更小

方法

只能给自己定义的类型添加方法,是无法给go标准库中的(int,string等)类型和别的包里面的类型添加方法的,所以才有type myInt int这种情况,给其他包添加方法也可以通过type来实现

方法的接受者形参约定成俗用类型的首字母小写来定义,接受者分为指针传递和值传递,如果是值传递,那么方法内部对变量的改变不会影响到外部,且建议当同一类型的某个方法使用了指针接受者,其他方法也应该用指针接受者

一般都使用指针接受者,因为如果结构体成员过多,值传递拷贝会影响内存的占用,而指针永远都是一个uint64类型的值

time包

time.Now()记录了当前时间,time.Now().Year()可以获取当前年,还有Month,Day等

time.Now().Unix()表示从1970年1月1日8点整到现在的时间戳,UnixNano()精确到纳秒

time.Unix()可以将时间戳转换为时间格式

time有个add方法可以进行时间增加,如当前时间的5小时后为 time.Now().Add(5*time.Hour)

格式化时间将时间对象转换成字符串类型的时间,使用 time.Now().Format(2006-01-02 15:04:05),它使用的是GO的诞生时间2006年1月2日15点4分5秒(可以记作2006 1 2 3 4 5),如果想用12小时制的在后面加个PM即可

按照对应字符串格式将字符串时间解析成时间对象类型

使用Parse()方法来进行解析

1
2
3
4
5
6
	t,err:=time.Parse("2006-01-02","1998-01-19")
	if err!=nil{
		fmt.Println("输入的时间格式不对")
		return
	}
	fmt.Println(t)

对于求两个时间间隔的结果不准确的解决方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	//Now()会按照本地时间获取,而Parse则会按照UTC时间获取,因此两者相减的时间(如果本地不是UTC时间)会比正常情况多几个小时
	now := time.Now()
	tim, _ := time.Parse("2006-01-02 15:04:05", "2020-10-29 22:00:00")
	d := tim.Sub(now)
	fmt.Println(d)

	//按照指定时区解析时间
	loc, _ := time.LoadLocation("Asia/Shanghai")
	tim1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-29 22:00:00", loc)
	d1 := tim1.Sub(now)
	fmt.Println(d1)

对于可变参数的理解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func f1(a ...interface{}){
	fmt.Println(a)
	fmt.Println(a...)
}
func main(){
	f1(1,2,3)
}
输出结果:
[1 2 3]		//这个为一个变量的输出
1 2 3		//这是三个变量

在使用函数的可变参数时(只有空接口类型才能使用三个…进行拆解),在参数后面加三个点表示拆开可变参数生成的切片,分成多个变量,如果不加三个点就表示将a作为一个空接口切片类型传入

在golang中,栈(stack)和堆(heap)存的东西不一样,栈存的是对应类型的名字,堆存的是对应类型的具体数据,由栈指向堆

使用map时,value如果是一个结构体,必须使用指针类型,map里结构体无法直接寻址,必须取址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var i map[string]*Age=make(map[string]*Age)

	i["张文"]=&Age{Id: 23}
	i["利尔"]=&Age{Id: 38}
	//此处map的value如果不是地址而是结构体本身的话v的地址一直都是一个地址,遍历只是将新的值覆盖到旧地址上。并且它是无法寻址的,即无法找到对应结构体的成员进行改变
	for _,v:=range i{
		v.Id=v.Id+3
	}

	//将结构体赋值给map则需要这样
	for i:=0;i<len(ages);i++{
		x[ages[i].Name]=&ages[i]
	}

	for _,v:=range i{
		fmt.Println(v.Id)
	}

重写sort方法实现结构体的排序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// SortByReadBook 按照阅读量排序,定义一个结构体是原结构体的切片
type SortByReadBook []ReadLeaderboardResp

func (s SortByReadBook) Len() int      { return len(s) }   //重写len方法
func (s SortByReadBook) Swap(i, j int) { s[i], s[j] = s[j], s[i] }   //重写swap方法
func (s SortByReadBook) Less(i, j int) bool {		//重写less方法
	//uint用大于小于Decimal使用自身的lessThan方法
	return s[i].ReadBookCount > (s[j].ReadBookCount)
}

调用时则使用sort方法,强转原结构体  sort.Sort(SortByReadBook(readLeader))

vscode 调试配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
      {
          "name": "Launch Package",
          "type": "go",
          "request": "launch",
          "mode": "auto",
          "program": "${workspaceFolder}//cmd//operation//main.go"
      }
  ]
}

结构体的String方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Test struct {
	name string
}

func (t Test) String() string {
	return t.name + "======111"
}

func main() {
	t := Test{"张三"}
	fmt.Println(t)
}

如果给结构体绑定一个String的方法,那么在打印时只打印此结构体会隐式的调用此结构体的String方法

通过reflect.Value修改值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type test struct {
	Name string
	Age  int
}

func main() {
	t := reflect.ValueOf(&test{"张三", 18}) // a reflect.Type
	switch t.Kind() {
	case reflect.Ptr:
		if t.IsNil() {
			log.Println("this is nil")
		} else {
			va := t.Elem()
			for i := 0; i < va.NumField(); i++ {
				// 1. 针对结构体成员是小写不公开的情况需要这种
				// temp := va.Addr().Interface().(*test)
				// temp.name = "李四"
				// 2. 公开的可以直接通过set方法来设置
				if va.Type().Field(i).Name == "Name" {
					fmt.Println("true")
					va.Field(i).SetString("历史")
				}
			}
		}
	}
	fmt.Println(t)
}

golang是没有野指针的,因为只要有一个指针指向函数返回的变量,它就不会被回收。Go语言的内存回收机制规定,只要有一个指针指向一个变量,那么这个变量就不会被释放(内存逃逸),因此在 Go 语言中返回函数参数或临时变量是安全的。

Licensed under CC BY-NC-SA 4.0