概述

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类型不能取地址,无论有没有指定类型

file

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
}

闭包含有相同值

file

结构体初始化方式


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{}
}

file

发表回复