go lang checklist

注意:

  1. “_”是特殊标识符,用来忽略结果。
  2. iotago语言的常量计数器,只能在常量的表达式中使用。 iotaconst关键字出现时将被重置为0const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)
  3. slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,也就是slice是一个引用
    1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
    2. 切片的长度可以改变,因此,切片是一个可变的数组。
    3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。 
    4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
    5. 切片的定义:var 变量名 []类型,比如 var str []string  var arr []int6. 如果 slice == nil,那么 lencap 结果都等于 0

 

s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
fmt.Println(s1, len(s1), cap(s1))

//结果是:
[0 1 2 3 0 0 0 0 100] 9 9

 

new与make的区别

    1.二者都是用来做内存分配的。
    2.make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
    3.new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用

1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。

例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等

结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

    `key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。

func myfunc(args ...int) {    //0个或多个参数
  }

注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.

任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。

用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。

在Go语言中,...是一种特殊语法,用于将切片或数组的元素展开作为函数参数传递给可变参数函数。

 nil 值。nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量

直接使用值为 nil 的 slice、map

允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map添加元素则会造成运行时 panic

复制代码
// map 错误示例
func main() {
    var m map[string]int
    m["one"] = 1        // error: panic: assignment to entry in nil map
    // m := make(map[string]int)// map 的正确声明,分配了实际的内存
}    


// slice 正确示例
func main() {
    var s []int
    s = append(s, 1)
}
复制代码

.string 类型的变量值不能为 nil

对那些喜欢用 nil 初始化字符串的人来说,这就是坑:

golang 中字符串是不能赋值 nil 的,也不能跟 nil 比较。

复制代码
// 错误示例
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"
    }
}
复制代码

 

访问 map 中不存在的 key

Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。

检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:

复制代码
// 错误的 key 检测方式
func main() {
    x := map[string]string{"one": "2", "two": "", "three": "3"}
    if v := x["two"]; v == "" {
        fmt.Println("key two is no entry")    // 键 two 存不存在都会返回的空字符串
    }
}

// 正确示例
func main() {
    x := map[string]string{"one": "2", "two": "", "three": "3"}
    if _, ok := x["two"]; !ok {
        fmt.Println("key two is no entry")
    }
}
复制代码

 

使用了值为 nil 的 channel

在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:

复制代码
func main() {
    var ch chan int // 未初始化,值为 nil
    for i := 0; i < 3; i++ {
        go func(i int) {
            ch <- i
        }(i)
    }

    fmt.Println("Result: ", <-ch)
    time.Sleep(2 * time.Second)
}
复制代码

 

runtime 死锁错误:

fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive (nil chan)]

利用这个死锁的特性,可以用在 select 中动态的打开和关闭 case 语句块:

复制代码
func main() {
    inCh := make(chan int)
    outCh := make(chan int)

    go func() {
        var in <-chan int = inCh
        var out chan<- int
        var val int

        for {
            select {
            case out <- val:
                println("--------")
                out = nil
                in = inCh
            case val = <-in:
                println("++++++++++")
                out = outCh
                in = nil
            }
        }
    }()

    go func() {
        for r := range outCh {
            fmt.Println("Result: ", r)
        }
    }()

    time.Sleep(0)
    inCh <- 1
    inCh <- 2
    time.Sleep(3 * time.Second)
}
复制代码
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    Addr string `json:"addr,omitempty"`
}

结构体里的 Addr 字段有 omitempty 属性,因此 encoding/json 在将对象转化 json 字符串时,只要发现对象里的 Addr 为 false, 0, 空指针,空接口,空数组,空切片,空映射,空字符串中的一种,就会被忽略。

golang 里的 iota

  1. 不同 const 定义块互不干扰;
  2. 所有注释行和空行全部忽略;
  3. 没有表达式的常量定义复用上一行的表达式;
  4. 从第一行开始,iota 从 0 逐行加一;
  5. 替换所有 iota。

 

协程什么时候会切换

  1. I/O,select

  2. channel

  3. 等待锁

  4. 函数调用

  5. runtime.Gosched()

 永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

 

复制代码
package main
import (
    “fmt”
)
type nexter interface {
    next() byte
}
func nextFew1(n nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next()
    }
    return b
}
func nextFew2(n *nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next() // 编译错误:n.next未定义(*nexter类型没有next成员或next方法)
    }
    return b
}
func main() {
    fmt.Println("Hello World!")
}
复制代码

  

 

复制代码
type User struct {
  Name  string
  Email string
}

func (u User) Notify() error

// User 类型的值可以调用接受者是值的方法
damon := User{"AriesDevil", "ariesdevil@xxoo.com"}
damon.Notify()

// User 类型的指针同样可以调用接受者是值的方法
alimon := &User{"A-limon", "alimon@ooxx.com"}
alimon.Notify()
复制代码

 

  • 一个结构体的方法的接收者可能是类型值或指针
  • 如果接收者是值,无论调用者是类型值还是类型指针,修改都是值的副本
  • 如果接收者是指针,则调用者修改的是指针指向的值本身

 

复制代码
package main

import "fmt"

type coder interface {
    code()
    debug()
}

type Gopher struct {
    language string
}

func (p Gopher) code() {
    fmt.Printf("I am coding %s language1\n", p.language)
}

func (p *Gopher) debug() {
    fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
    var c coder = Gopher{"Go"}
    c.code()
    c.debug()
}
# command-line-arguments
./code.go:23: cannot use Gopher literal (type Gopher) as type coder in assignment:
    Gopher does not implement coder (debug method has pointer receiver)
复制代码

 

  • 类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
  • 类型 T 的可调用方法集包含接受者为 T 的所有方法
  • 类型 T 的可调用方法集包含接受者为 *T 的方法

也就是说:

  • 接收者是指针 *T 时,接口的实例必须是指针
  • 接收者是值 T 时,接口的实例可以是指针也可以是
Methods Receivers Values
(t T) T and *T
(t *T) *T

上面的表格可以解读为:如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有实例类型的指针能够实现对应的接口。

 

在Go语言中,可以使用unsafe.Sizeof函数来获取uintptr类型的大小。例如:unsafe.Sizeof(uintptr(0))

Uber Go 语言编码规范

nterface 合理性验证

在编译时验证接口的符合性。这包括:

  • 将实现特定接口的导出类型作为接口API 的一部分进行检查
  • 实现同一接口的(导出和非导出)类型属于实现类型的集合
  • 任何违反接口合理性检查的场景,都会终止编译,并通知给用户

补充:上面3条是编译器对接口的检查机制, 大体意思是错误使用接口会在编译期报错. 所以可以利用这个机制让部分问题在编译期暴露.

Bad Good
// 如果Handler没有实现http.Handler,会在运行时报错
type Handler struct {
  // ...
}
func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  ...
}
 
type Handler struct {
  // ...
}
// 用于触发编译期的接口的合理性检查机制
// 如果Handler没有实现http.Handler,会在编译期报错
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}
 

如果 *Handler 与 http.Handler 的接口不匹配, 那么语句 var _ http.Handler = (*Handler)(nil) 将无法编译通过.

赋值的右边应该是断言类型的零值。 对于指针类型(如 *Handler)、切片和映射,这是 nil; 对于结构类型,这是空结构

接收器 (receiver) 与接口

Effective Go 中有一段关于 pointers vs. values 的精彩讲解。

补充:

  • 一个类型可以有值接收器方法集和指针接收器方法集
    • 值接收器方法集是指针接收器方法集的子集,反之不是
  • 规则
    • 值对象只可以使用值接收器方法集
    • 指针对象可以使用 值接收器方法集 + 指针接收器方法集
  • 接口的匹配(或者叫实现)
    • 类型实现了接口的所有方法,叫匹配
    • 具体的讲,要么是类型的值方法集匹配接口,要么是指针方法集匹配接口

具体的匹配分两种:

  • 值方法集和接口匹配
    • 给接口变量赋值的不管是值还是指针对象,都ok,因为都包含值方法集
  • 指针方法集和接口匹配
    • 只能将指针对象赋值给接口变量,因为只有指针方法集和接口匹配
    • 如果将值对象赋值给接口变量,会在编译期报错(会触发接口合理性检查机制
posted @   codestacklinuxer  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
历史上的今天:
2022-05-20 TLS 线上问题
2021-05-20 log&& buffevent&&内存池 2
2021-05-20 gdb 打印每个线程的bt ps 状态等命令
点击右上角即可分享
微信分享提示