欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Go语言圣经笔记

程序员文章站 2024-02-17 15:34:40
...

Go圣经

  • i++是语句,而不是表达式,所以类似j=i++非法,并且++只能在后边
  • 常量目前只能是数值、字符串或者一个固定的布尔值

变量

基本类型:数值、字符串、布尔值

引用类型:指针、接口、切片、map、函数、chan

聚合类型:数组、结构体

  • Go中声明变量不初始化的情况下都会默认零值,不会出现undefined的情况

引用类型的零值为nil

基本类型为其对应的零值

聚合类型的元素或字段为其对应的零值

  • 短变量声明对已经声明过的变量只有赋值作用,但是多次变量声明必须要有一个是新的变量
res,err:=test()
res1,err:=test()//ok
res:=test()
res:=test()//error
  • 不是每个值都有内存地址,但是每个变量一定有内存地址,换句话说变量是可寻址的
  • p:=new(T)声明一个T类型的零值变量,返回其地址,每次返回都是新的变量地址,但是可能会相同,如果两个类型为空时,即类型的大小为0,如struct{}[0]int,返回地址可能是同一个,并且当大小为0时,垃圾回收器有不同行为,应该避免这种用法。

整形

  • Go中取模结果符号与被取模运算符的符号是一致的(同Javascript),除法结果依赖于操作数是否为整数,只要有一个为浮点数那么就是正常除法
  • 打印数据时%[1]为继续取第一个操作符,如fmt.Println("%d %[1]c %[1]q\n",ascii)

字符串

字符串的第ige索引值代表的是第i个字节,第i个字节不一定是第i个字符,因为UTF-8是一种变长的编码方式,对于非ASCII编码可能会使用两个或者更多字节进行编码,可以使用[]rune(string)将字符串转化为unicode码点后进行索引,一个unicode码点为4个字节

func main() {
	s := "国hello, world"
	fmt.Println(string(s[0]), string([]rune(s)[0])) // å 国
}

常量

golang也可以进行类似枚举的操作(只能是常量),使用iota,从0开始递增

var (
	A int = 1 << iota
	B
	C
	D
)

func main() {
	// var a Vex
	fmt.Println(A, B, C)
}

golang许多常量可能并没有一个明确的基础类型,在赋值时会进行尝试转换

import "fmt"

func main() {
	var a float64 = 3 + 0i
	fmt.Printf("类型:%T,值:%[1]v\n", a) // 类型:float64,值:3
	a = 20
	fmt.Printf("类型:%T,值:%[1]v\n", a) // 类型:float64,值:20
	a = 'a'
	fmt.Printf("类型:%T,值:%[1]v\n", a) // 类型:float64,值:97
}

复合数据类型

数组

  • 数组可以使用索引的形式进行声明:[...]int{99:1}声明长度为100,最后一个元素值为1的数组。
  • 数组区别与JavaScript不是引用类型,数组的比较相等判断对应元素是否相等
  • append像切片添加内容时,如果底层数组容量足够,那么直接是在底层数组上添加,然后返回切片值,如果容量我够则扩容生成一个新的底层数组,所以在使用append向切片添加内容时我们不确定返回的是之前的底层数组的切片值还是一个新的底层数组
func test(arr []int) {
	arr = append(arr, 3)
	arr[0] = 3
	fmt.Println(len(arr), cap(arr)) // 3 4
}

func main() {
	arr := make([]int, 2, 4)
	test(arr)
	fmt.Println(arr[:3])            //[3 0 3] 共享同一底层数组,扩容后可以看见添加的值
	fmt.Println(len(arr), cap(arr)) // 2 4
}

所以平常可以这么使用,避免一些不必要的麻烦(覆盖原始数据之类的)

func test(arr []int) []int {
	arr = append(arr, 3)
	arr[0] = 3
	return arr
}

func main() {
	arr := make([]int, 2, 4)
	arr = test(arr)
	fmt.Println(arr) // 2 4
}

map

  • map的值是不可取地址的,原因在于map随着元素增加会分配更大的内存空间,之前分配的空间可能失效

函数

可变参数

golang支持可变参数,类型必须匹配

func test(v ...int) {
	for _, value := range v {
		fmt.Println(value)
	}
}

func main() {
	test(12, 3, 3)
	arr := []int{5, 6, 7}
	test(arr...)
}

匿名函数

golang中也要注意循环闭包带来的引用同一变量的问题,类似JavaScript

Recover

golang异常可以使用recover()恢复,但是不要滥用,在该恢复的地方进行恢复

func main() {
	defer func() {
		if p := recover(); p != nil {
			fmt.Println("调用recover可以恢复异常,注意应该只恢复应该恢复的panic")
		}
	}()
	log.Panic("异常")
	log.Println("啊啊啊啊啊")
}

接口

  • 空接口类型不能对值进行操作,因为他没有实现任意方法
  • 只有当两个或两个以上具体类型需要使用同样的方法处理时才应该使用接口

包和工具

包的重命名

import (
	"crypto/rand"
	"fmt"
	mrand "math/rand"
)
  • golang中出现循环依赖会报错

测试

创建test目录,为file包创建file_test.go文件,test文件测试函数必须以Test开始。调用go test -v进行测试。demo:

func TestSum(t *testing.T) {
	if Sum(2, 3) != 5 {
		t.Error("错误:2+3!=5")
	}
	if Sum(4, 6) == 8 {
		t.Error("错误:4+6=8")
	}
}

测试覆盖率查看

  1. go test -v -coverprofile=cover.out
  2. go tool cover -html=c.out

第二部是转化为html查看

反射

反射可以帮助我们在运行时更新变量或者检查他们的值,调用方法和内在操作等,interface{}需要在运行时确定类型,但是如果不是确定其动态类型并且调用断言的话,是不能访问其内在的值和方法的,但是反射提供了这种机制。

package main

import (
	"fmt"
	"reflect"
)

func test(data interface{}) {
	t := reflect.TypeOf(data) // 返回reflect.Type
	fmt.Printf("%q\n", t)     // 打印时会调用String方法

	v := reflect.ValueOf(data) // 返回reflect.Value类型
	fmt.Printf("%q\n", v)
	fmt.Println("---------", v.Kind() == reflect.String) // 判断类型,不能与string比较,因为其是无类型string

	t = v.Type() // 调用Type 方法返回对应具体类型的reflect.Type
	fmt.Printf("%q\n", t)

	//  reflect.ValueOf(data)的逆操作,返回interface{}类型的具体值
	// 	与reflect.Value不同的是空接口隐藏了值的内部表现和方法(什么都
	// 	做不了),只有知道具体的动态类型时才能使用断言访问内部值。相反
	// 	reflect。Value有很多方法检查其内容
	i := v.Interface()
	fmt.Printf("%q\n", i.(string))
}

func main() {
	test("字符串")
}

通过反射修改值

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 3
	a := reflect.ValueOf(&x).Elem() // 获取可取地址的Value,reflect.ValueOf()返回的是值的拷贝
	fmt.Println(a.CanAddr())        // 可以使用该方法判断是否可取地址
	/*
		Addr() 返回reflect.Value 保存指向变量的指针
		随后取Interface + 断言即可通过普通指针更新变量
	*/
	// a.Addr().Interface().(*int)

	// 或者调用可取地址的reflect.Value的Set方法更新值
	a.Set(reflect.ValueOf(4))
	// 简便写法
	a.SetInt(6)

	y := struct{ field int }{10}
	// 可以获取未导出字段,但是不能更改
	field := reflect.ValueOf(&y).Elem().FieldByName("field")
	// 判断是否可更改
	fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"

}

相关标签: golang 笔记 go