go的一些注意事项2023-12-29
概述
go是一门很不同于传统语言的一门语言,有些怪异的行为我们需要注意
对于range和index取值,不同于其他语言
package main
import "fmt"
func main() {
var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a // b 是指向数组的指针
fmt.Println(a[0], a[1]) // 打印数组的前2个元素
fmt.Println(b[0], b[1]) // 通过数组指针访问数组元素的方式和数组类似
for i, v := range b { // 通过数组指针迭代数组的元素
fmt.Println(i, v)
}
for i, v := range a { // 通过数组指针迭代数组的元素
fmt.Println(i, v)
}
}
//怪异的设计
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap
string
type StringHeader struct {
Data uintptr
Len int
}
slice
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
windows路径分隔符
可以采用统一模式/,例如
D:/tools/win32_x64_1.1.7
可nil类型
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
异或不同于其他语言
A = 0011 1100
B = 0000 1101
A^B = 0011 0001
2的n方,2<<n
除了全局变量,其他变量必须被使用
strconv.ParseInt
第三个参数确实是起限制作用的,如果待转换值超出了我们设置的限制,就会将报错信息存于err中
package main
import (
"fmt"
"strconv"
)
func main() {
str1 := "128"
n, err := strconv.ParseInt(str1, 10, 8) //这里的 8 指的是 转换结果最大值不超过 int8 即 127
if err != nil {
fmt.Printf("err is %v\n", err)
}
fmt.Printf("类型: %T ,值: %d", n, n)
}
func main() {
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Printf("字符串 '%s' 转换为整数为:%d\n", str, num)
}
str2 := strconv.Itoa(num)
fmt.Printf("整数 %d 转换为字符串为:'%s'\n", num, str2)
strconv.ParseInt(str, 10, 64)
strconv.ParseFloat("3.33", 64)
str = strconv.FormatFloat(3.33, 'f', 2, 64)
}
糟糕的API带来的别扭
既然IsNil不是所有类型都可以调用的,那么你让放到Kind里面就是一个糟粕
func IsNil(i interface{}) bool {
vi := reflect.ValueOf(i)
k := vi.Kind()
switch k {
case reflect.Pointer, reflect.Slice, reflect.Map, reflect.Func, reflect.Interface, reflect.Chan:
{
return vi.IsNil()
}
default:
{
return false
}
}
}
不定参数
func test(capSlice ...int) {
fmt.Println(capSlice)
}
调用方式
test([]int{1, 2, 3}...)
test(1, 3, 4)
关于nil
func testNil() {
var data *byte
var in interface{}
fmt.Println(data, data == nil) //true(和interfacer不一样),打印的是字面量,所以首先nil
fmt.Println(in, in == nil) //true,打印的是字面量,所以首先nil
in = data //有了类型,所以不是nil了
fmt.Println(in, in == nil, reflect.ValueOf(in).Kind(), reflect.ValueOf(in).IsNil())
//<nil> false ptr true
}
go的执行时间取决于GPM的情况,和代码顺序没有必然关系
//乱序执行,不要以为就全9
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println(i)
wg.Done()
}() // 闭包延迟取值,go延迟执行,但不一定都是9,因为go在执行的时候就会去取,你怎么知道不是取到之前的呢?
}
wg.Wait()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
fmt.Println(n)
wg.Done()
}(i) //go的启动时间和速度是不一定的,所以乱序十个数
}
wg.Wait()
}
结构体的字段不能用:=
type info struct {
result int
}
func work() (int, error) {
return 3, nil
}
func main() {
var data info
data.result, err := work() // :=简短申明不能用于结构体的字段,(无论有没有新的值)但可以通过重新定义的方式来解决
fmt.Printf("info: %+v\n", data)
}
// 正确示例
func main() {
var data info
var err error // err 需要预声明
data.result, err = work()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("info: %+v\n", data)
}
简短申明,不是=,不是赋值运算,新的作用域中会有一个新的变量被申明
func main() {
x := 1
println(x) // 1
{
println(x) // 1
x := 2
println(x) // 2 // 新的 x 变量的作用域只在代码块内部
}
println(x) // 1
}
不可使用cap方法来度量map的容量
func main() {
m := make(map[string]int, 99)
println(cap(m)) // error: invalid argument m1 (type map[string]int) for cap
}
go里面的string不能用nil进行初始化,想想C++
// 错误示例
func main() {
var s string = nil // cannot use nil as type string in assignment
if s == nil { // invalid operation: s == nil (mismatched types string and nil)
s = "default"
}
}
// 正确示例
func main() {
var s string // 字符串类型的零值是空串 ""
if s == "" {
s = "default"
}
}
闭包会调用函数,参数会提前取值
新的代码域如果用:容易产生新的变量
package main
import "fmt"
var p = 33
func main() {
p, err := add()
_ = err
fmt.Println(p)
printValue() //33
}
func printValue() {
fmt.Println(p)
}
func add() (int, error) {
return 1, nil
}
go func(){}()的执行时间和代码顺序没有必然关系
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
fmt.Println(n)
wg.Done()
}(i)
}
wg.Wait()
}
//最后乱序执行
slice索引的顺序可放后面,如同map
var x = []int{2: 2, 3, 0: 1}
断言必须有明确类型
package main
func main() {
x := interface{}(nil)
y := (*int)(nil)
a := y == x //虽然都是nil,但是再次他们各自的类型信息不一样
b := y == nil //true
_, c := x.(interface{}) //断言失败,因为x对应的实际类型不存在
println(a, b, c) // false true false
}
关于defer
https://blog.csdn.net/qq_49723651/article/details/121509818
https://zhuanlan.zhihu.com/p/545898510
package main
import "fmt"
func main() {
defer func() {
fmt.Print(recover()) //能
}()
defer func() {
defer fmt.Print(recover()) //这里能捕捉,因为首先在defer func里面取值后作为参数
//panic之前的defer会执行,所以先输出2,然后会被上面的defer func recover捕捉
panic(1)
}()
defer recover() //无效
panic(2)
}
interface及接收器的关系
type data struct {
name string
}
func (p *data) print() {
fmt.Println("name:", p.name)
}
type printer interface {
print()
}
func main() {
d1 := data{"one"}
d1.print()
var in printer = data{"two"}
in.print() //报错
}
//值类型仅仅拥有值接收器的方法,虽然能够调用,那是因为语法糖.
//指针类型实现方法,既可以用值接收器,也可以用指针接收器.
关于range
for k,v:=range ts ,range相当于一个函数,在go里面所有的传参又是值传递,所以要注意
range可以是数组,以及数组的指针(引用),
直接数组就是一个新拷贝,如果指针就是同一个数组
range 的 遍历对象,是一个拷贝值,对他的操作不会影响原有值
range slice 先会做切片,所以无论是否增加新数据,都不影响原来的。range不能是slice的指针
range array 是新的拷贝,所以要原来的请用指针
func main() {
var nums=[...]int{
1,
2,
4,
}
for i, num := range &nums { //如果直接数组名,就会是一个新的拷贝
nums[2]=8
fmt.Println(i,num)
}
}
0 1
1 2
2 8
关键字
func A(string string) string {
return string + string
}
func B(len int) int {
return len + len
}
func C(val, default string) string {
if val == "" {
return default
}
return val
}
default 属于关键字。string 和 len 是预定义标识符,可以在局部使用。
nil 也可以当做变量使用
map右值怪异的行为
func main() {
m := make(map[string]int)
m["foo"]++ //基础数据int这样的没有问题,要是结构体就会出问题
fmt.Println(m["foo"])
}
八进制十六进制
Go 语言里面,八进制数以 0 开头,十六进制数以 0x 开头,所以 Decade 表示十进制的 8。
const (
Century = 100
Decade = 010 //8
Year = 001
)
切片的本质是,内部的指针指向被切数据底层数据的起切位置,cap为底层数组的的长度
func main() {
a := [3]int{0, 1, 2}
s := a[1:2] //切片的本质是,内部的指针指向被切数据底层数据的起切位置,cap为底层数组的的长度
s[0] = 11
s = append(s, 12)
s = append(s, 13)
s[0] = 21
fmt.Println(a)
fmt.Println(s)
}
//[0 11 12]
//[21 12 13]
embed将文件内嵌到程序中
import (
_ "embed"
)
//go:embed pathtofile
var str String
//go:embed pathtofile
var bs []byte
//go:embed pathtofileordir
//go:embed resources/*
var f embed.FS
// Open 打开要读取的文件,并返回文件的fs.File结构.
func (f FS) Open(name string) (fs.File, error)
// ReadDir 读取并返回整个命名目录
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
// ReadFile 读取并返回name文件的内容.
func (f FS) ReadFile(name string) ([]byte, error)
const 没有类型的话就只做展开,有点类似于宏
func testconst() {
const a = 3
c1(a)
cint64(a)
}
func c1(b int8) {
fmt.Println(b)
}
func cint64(b int64) {
fmt.Println(b)
}
go中const类型不能取地址,无论有没有指定类型
type自定义新类型,不会永远原有类型的方法
type myMutex sync.Mutex
func main() {
var mtx myMutex
mtx.Lock() //X
mtx.UnLock()//X
}
多值返回时,要么全部没有名称,要么都给名称
//第一个返回值有sum名称,第二个未命名,所以错误
func funcMui(x,y int)(sum int,error){
return x+y,nil
}
switch判断只能用于interface
func main() {
i := GetValue()
switch i.(type) { //只有interface才可以这么用
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
func GetValue() int {
return 1
}
闭包含有相同值
结构体初始化方式
type Cat struct {
Name string
Age int
}
func initStruct() {
var c1 Cat
c1.Name = "c1"
c1.Age = 1
var c2 *Cat = new(Cat)
c2.Name = "name1"
c2.Age = 1
//json方式
var c3 = Cat{Name: "c2", Age: 3}
_ = c3
//不给字段名
var c4 = Cat{"abcv", 3}
_ = c4
}
slice的扩容机制
如果要的容量>原来的两倍,就按新容量.
否则len的长度,如果>1024就不断1.25倍扩容,直到容量满足
如果<1024,依旧两倍扩容
// =========== 第一种
a := make([]string, 5)
fmt.Println(len(a), cap(a)) // 输出5 5
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 输出6 10
// 总结: 由于make([]string, 5) 则默认会初始化5个 空的"", 因此后面 append 时,则需要2倍了
// =========== 第二种
a:=[]string{}
fmt.Println(len(a), cap(a)) // 输出0 0
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 输出1 1
// 总结:由于[]string{}, 没有其他元素, 所以append 按 需要扩容的 cap 来
// =========== 第三种
a := make([]string, 0, 5)
fmt.Println(len(a), cap(a)) // 输出0 5
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 输出1 5
// 总结:注意和第一种的区别,这里不会默认初始化5个,所以后面的append容量是够的,不用扩容
// =========== 第四种
b := make([]int, 1, 3)
a := []int{1, 2, 3}
copy(b, a)
fmt.Println(len(b)) // 输出1
// 总结:copy 取决于较短 slice 的 len, 一旦最小的len结束了,也就不再复制了
for range的总结
for k,v:=range ts
for k,v:=range &ts //v不是T的指针类型,是T类型
range相当于一个函数(不一样的地方,在于Slice会做一次切片),在go里面所有的传参是值传递
range可以是数组,以及数组的指针(引用),但是i,v都是新的副本.
range数组就是一个新的ts拷贝,如果指针就是在读取同一个数组.
1.下面的代码输出什么?
```go
<p>type T struct {
n int
}</p>
<p>func main() {
ts := [2]T{}</p>
<pre><code>for i, t := range ts { //这里产生了新的数组拷贝
switch i {
case 0:
t.n = 3 //虚招,这里枚举器是一个新副本,所以这么操作影响不了ts
ts[1].n = 9 //改的ts
case 1: //新的ts副本不会因为ts的改变而变化
fmt.Print(t.n, " ")
}
}
fmt.Print(ts)</code></pre>
<p>}</p>
<pre><code>参考答案及解析:0 [{0} {9}]
**2.下面的代码输出什么?**
```go
type T struct {
n int
}
func main() {
ts := [2]T{}
for i, t := range &ts {
switch i {
case 0:
t.n = 3 // //虚招,这里枚举器是一个新副本,所以这么操作影响不了ts
ts[1].n = 9 //改变的是ts
case 1: //range的是数组的指针,所以连带受到影响
fmt.Print(t.n, " ")
}
}
fmt.Print(ts)
}
参考答案及解析:9 [{0} {9}]。
语法糖无效,以及右值问题
type X struct{}
func (x *X) test() {
println(x)
}
func (x X) test2() {
println(x)
}
func main() {
var a *X
a.test() //nil
(&X{}).test() //语法糖无效,所以需要手动用&
(&getT()).Set(1) //直接采用是不行的,即便用&,需要借助中间值
getT().n = 1//直接采用是不行的,需要借助中间值
}
type T struct {
n int
}
func (t *T) Set(n int) {
t.n = n
}
func getT() T {
return T{}
}