【学习笔记】《Go 指南》

学习笔记 —— Go 指南

前言:先贴上网址,因为是先用 Typora 写,然后直接导入的,所以格式多多少少有点问题= =

一. 包、变量和函数

  1. 导包可以用分组形式(括号)

    import (
    	"fmt"
    	"math"
    )
    
  2. 在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。

    math.pi // 未导出的变量无法访问
    math.Pi 
    
  3. 函数参数类型在;连续同类型时可以省略,只在最后一个参数写上类型

    func add(x, y int) int {
    	return x + y
    }
    
  4. 多值返回:函数可以返回任意数量的返回值。

    func swap(x, y string) string {
      return y, x
    }
    
  5. 返回值可被命名,位于函数顶部。

    无参的 return 语句会直接返回已命名的返回值。(长函数这样使用会影响可读性

    // 已命名返回值 x, y
    func split(sum int) (x, y int) {
      x = sum % 10;
      y = sum / 10;
      // 会返回 x, y
      return
    } 
    
  6. var 语句:用于声明一个变量列表,跟函数的参数列表一样,类型在最后

    var 语句可以出现在包或函数级别

    package main
    
    import "fmt"
    
    var java, python, golang bool
    func main(){
      var i int
      fmt.Println(i, java, python, golang)  
    }
    
  7. 声明可以包含初始化值

    有初始化值时,可以省略类型,会从初始化值中自动获取

    var i, j int = 1, 3
    // 自动获取;可以类型不同!
    var golang, java = true, "java~"
    
  8. 短变量声明:在函数中,可以用 “:=” 来代替 var 声明语句。(函数外不行,因为函数外要求语句以关键字开始)

    func main(){
      k := 3
      i, golang, java := 3, true, "java~"
    }
    
  9. 基本类型: 居然没有字符= =

    bool
    
    string
    
    // int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 
    // 整形应使用 int 类型,除非特需使用固定大小 or 无符号的整数类型。
    int  int8  int16  int32  int64
    uint uint8 uint16 uint32 uint64 uintptr
    
    // uint8 的别名
    byte 
    
    // int32 的别名, 表示一个 Unicode 码点
    rune 
    
    // 两种大小的 float 型
    float32 float64
    
    // 两种大小的 复数
    complex64 complex128
    
    // var 也可以像 import 一样分组~
    var (
      myBool bool = true
      myString string = "fwy"
    )
    
  10. 默认值

    // 数值默认 0
    var i int
    var j float32 
    
    // 布尔默认 false 
    var is bool
    
    // 字符串默认 ""(空串)
    var name string
    
  11. 类型转换:只能显式转换

    // 注意:go 不支持隐式转换,设计者认为这个功能的弊端比好处多。
    var i, j = 1, 2
    var k float32 = float32(i + j)
    
    // 短声明
    i, j := 1, 2
    k := float32(i + j)
    
  12. 类型推导:变量类型由右值推导

    var i int = 3
    // 推导 j is type of int
    var j = i 
    
    // 当右边包含未指明类型的数值常量时,新变量的类型取决于常量的精度:
    i := 3 // int
    j := 3.14 // float64
    k := 3 + 3.14 // float64
    
  13. 常量: const 关键字。不能用 := 声明

    // 常量可以是字符串、布尔值或数值
    const world = "世界"
    
    // 可以使用分组、可以指定类型
    const (
      name int = 20
      school = "SZU"
    )
    
  14. 数值常量:数值常量是高精度的 。一个未指定类型的常量上下文来决定其类型。

    const (
      Big = 1 << 100
      Small = Big >> 99
    )
    
    func needInt(x int) int {
      return x
    }
    
    func needFloat(x float32) float32 {
      return x
    }
    
    func main(){
      // 两个都可以跑,因为常量类型未指定
      fmt.Println(needInt(Small))
      fmt.Println(needFloat(Small))
      
      fmt.Println(needFloat(Big))
      fmt.Println(needInt(Big)) // error: overflows int
    }
    

二. 流程控制语句 for、switch、if 和 defer

  1. 循环语句:

    sum := 0
    /**
    	* 1. go 只有一种循环:for 循环
    	* 2. 三个构成部分外没有小括号,用 ';' 隔开
    	* 3. 大括号{ }是必须的
    */
    for i := 0; i < 10; i++ {
      sum += i
    }
    
    // 4. 初始化 & 后置语句可省略
    for ; sum < 100; {
      sum += sum
    }
    
    // 5. for 是 go 中的 "while"。去掉分号,有:
    for sum < 1000 {
      sum += sum
    }
    
    // 6. 条件也可省略。下例为“紧凑的无限循环”
    for {
    }
    
    
  2. 判断语句:

    // 1. 和 for 一样:省略小括号,必须大括号
    you := true
    if you {
      fmt.Println("you~you~you")
    }
    
    // 2. 和 for 一样:在条件表达式之前,可以执行一个简单语句
    if me := 3 * 4; me == 12 {
      fmt.Prinln("简单语句声明的变量,作用域仅在该if语句中")
    }
    // 当然,对应的 else 块中也可以使用该变量
    else {
      me += me
    }
    
  3. switch 语句:

    /**
    	* 1. Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。
    	* 2. 除非以 fallthrough 语句结束,否则分支会自动终止 (接1)
    	* 3. case 无需为常量,且取值不必为整数
    	* 4. 同 if ,可以在先执行一个简单语句
    */
    func main(){
      switch os := "macOS"; os {
        case "Linux":
        	fmt.Println("LinuxOS")
        case "mac":
        	fmt.Println("macOS")
        default:
        	fmt.Println(os)
      }
      
      // 5. switch == switch true (无条件 switch 相当于 switch true)
    }
    
  4. defer 语句:将函数推迟到外层函数返回之后执行

    参数立即求值,但是函数要返回之后才调用。

    func main(){
      defer fmt.Println("hello")
      fmt.Print("world")
      // 输出:world hello
    }
    
  5. defer 栈:推迟的函数调用会被压入一个中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

    func main(){
      for i := 1; i < 4; i++ {
        defer fmt.Print(i)
      }
      fmt.Print("end")
      // 输出:end 3 2 1
    }
    

三. 更多类型 struct、slice 和映射

  1. 指针:

    
    // 和 C++ 相同的两个运算符:*、&
    var(
      a int = 3
      // 注意定义时 * 在类型前
      p *int = &a
    )
    fmt.Println(*p)
    // 零值为 nil
    p = nil
    
  2. 结构体:一个结构体(struct)就是一组字段(field)

    // 声明格式
    type Vertex struct {
      x int
      y int
    }
    func main(){
      // 1. 大括号构造
      temp := Vertex{1, 2}
      // 2. 用'.'来访问成员
      fmt.Println(temp.x)
      // 3. 结构体指针
      p := &temp
      // 4. 允许隐式间接引用
      fmt.Println((*p).x) // 这样写太啰嗦了
      fmt.Println(p.x)
      // 5. 使用 Name: 语法可以仅列出部分字段。(顺序无关)
      temp := Vertex{x: 1} // 1, 0
      temp := Vertex{} // 0, 0
    }
    
  3. 数组:

    // var 格式
    var a [2]int
    a[0] = 3
    
    // := 格式
    stinrgs := [2]string{"fwy", "szu"}
    
  4. 切片1:动态、灵活,在实践中比数组更常用

    arr1 := [3]int{1, 2, 3}
    // 1. [low, height) 闭开区间
    //    不存储数据,只是描述数组的一部分(共享同一个数组)
    sec1 := arr1[1 : 2]
    
    // 2. 创建一个数组,然后构建一个引用了它的切片(此处[]内没有大小声明)
    sec2 := []int{1, 2, 3}
    
    // 3. 以默认忽略上下界(下界为0,上界为切片长度)
    arr2 := [3]int{1, 2, 3}
    //    这四个切片实际上相同
    sec3 := arr[: 3]
    sec4 := arr[0 :]
    sec5 := arr[:]
    sec6 := arr[0 : 3]
    
  5. 切片2:

    // capacity:从切片第一个元素,到其底层数组元素末尾的个数
    // length:包含的元素个数
    s := []int{1, 2, 3, 4, 5} // 5, 5
    fmt.Println(cap(s), len(s)) // 直接通过cap()、len() 获取
    
    // 零值 nil:len 和 cap 都是0,而且【没有底层数组】
    var ss []int
    
  6. 切片3:make 函数:分配一个元素为零值的数组,并返回一个引用了它的切片

    ​ 这也是分配动态数组的方式

    a := make([]int, 5)
    // 指定容量,则加入第三个参数
    b := make([]int, 5, 7)
    
    // 切片可包含任何类型,包括其它的切片。
    board := [][]string{
      []string{"-", "-", "-"},
      []string{"-", "-", "-"},
    }
    
  7. 切片4:append 追加元素

    // 1. 追加到末尾
    // 2. 容量不够时,会创造新的切片,然后再返回新切片
    var s []int
    append(s, 0)
    // 3. 可一次添加多个元素
    append(s, 1, 2, 3)
    
  8. for 循环的 range 形式

    // 1. 遍历切片时,每次会返回两个值:index、element的副本
    var arr = []int{1, 2, 3}
    for i, v := range pow {
      fmt.Prinln(i, v)
    }
    
    // 2. 可以用 _ 来忽略返回值
    for i, _ := range pow
    for _, v := range pow
    // 3. 如果只需要索引,也可以直接忽略第二个参数
    for i := range pow
    
  9. 映射1:

    // 1. 零值为 nil:既没有键,也不能添加键
    // 2. make 函数会返回给定类型的映射,并将其初始化备用
    m := make(map[string]int)
    m["fwy's age"] = 20
    // 3. 必须有键名
    var mm = map[string]int {
      "fwy" : 20
      "JOJO" : 200
    }
    // 4. 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
    var mmm = map[string]Vertex{
    	"Bell Labs": Vertex{
    		40.68433, -74.39967,
    	},
      // 省略
    	"Google": {
    		37.42202, -122.08408,
    	},
    }
    
  10. 映射2:

    // 增 or 改
    m["fwy"] = 21
    // 查
    age = m["fwy"]
    // 删
    delete(m, key)
    // contains:通过双赋值实现
    element, ok = m[key]
    // 存在则 ok == true,否则 ok == false
    // 不存在则 element 为类型 nil 值
    // 如果 element, ok 未声明,可以用短变量声明
    element, ok := m[key]
    
  11. 函数

    // 函数也是值。它们可以像其它值一样传递。(有点离谱。。但是也是这么个理)
    func compute(fn func(float64, float64) float64) float64{
      return fn(3, 4)
    }
    // 翻译:compute函数的参数为“一个返回值为float型的,需要两个float参数的函数”,返回值为float类型
    
    func main(){
      // 这个也挺离谱的,整得有点像 Java 的匿名类了= =
      mySqrt := func(x, y float64) {
        return math.Sqrt(x * x, y * y)
      }
      compute(mySqrt())
    }
    
  12. 函数的闭包

    // 闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值
    // adder() 的返回值类型 func(int) int
    func adder() func(int) int {
      sum := 0
      return func(x int) int {
        sum += x
        return sum
      }
    }
    
    func main() {
      pos, neg := adder(), adder()
      fmt.Println(pos(1)) // 1
      fmt.Println(neg(-1)) // -1
    }
    
    // 斐波那契闭包
    func fibonacci() func() int {
      // 初始化,这行代码只跑了一次
      a, b := 0, 1
      // func() 一直引用了函数体外的a、b,并且进行了修改
      return func() int {
        temp := a
        a, b = b, a + b
        return temp
      }
      
      func main() {
        fibo := fibonacci()
        for i := 0; i < 10; i++ {
          fmt.Println(fibo())
        }
      }
    }
    

四. 方法和接口

  1. 方法:

    // 1. go 没有类,但是可以给结构体定义“方法”
    // 2. 方法: 带特殊的“接收者参数”的函数(方法即函数)
    type Vertex struct {
      x int
      y int
    }
    
    // 3. 接收者参数,位于函数名前
    func (v Vertex) getX() int {
      return v.x
    }
    
    // 4. 只能为包内定义的类型的接收者声明方法(包外,像int 之类的内建类型也不行)
    // 可以通过这种方法曲线救国
    type myInt int
    func (v myInt) getInt() int {
      return (int)v
    }
    
    // 5. 为指针接收者声明方法
    func (v *Vertex) scale(num int) {
      v.x *= num
      v.y *= num
    }
    
    // 如果不是指针,而是值,那么将会在副本上修改,而不是本体(对于函数的其他参数也是如此)
    func (v Vertex) scale(num int) {
      v.x *= num
      v.y *= num
    }
    
    // 6. 带指针参数的函数必须接受一个指针,而以指针为接收者的函数则“值和指针都可以”
    // 相反,值也是一样
    p.Abs() // 会被解释为 (*p).Abs()
    
    // 7. 使用指针为接收者好处:
    // 1)可以修改值
    // 2)不用复制值,造成资源浪费
    
  2. 接口 def

    // 接口类型 是由一组方法签名定义的集合。
    // 接口类型的变量可以保存任何实现了这些方法的值。
    type Abser interface {
      Abs() float64
    }
    
    type MyFloat float64
    func (mf MyFloat) Abs() float64 {
      return float64(-mf);
    }
    
    type Vertex struct {
      x, y float64
    }
    
    func (v *Vertex) Abs() float64 {
      return x + y
    }
    
    func main() {
      var (
        abser Abser
        mf = math.Sqrt(30)
        v = Vertex{3, 4}
      )
      abser = MyFloat(mf)
      abser.Abs()
      abser = &v
      abser.Abs()
      // error: abser = v
    }
    
    // 1. 没有 implements 关键字,无需显示声明
    
    // 2. 接口也是值,可以传递。
    // 可以看做包含值和具体类型的元组:(value, type),会保存一个具体底层类型的具体值
    
    // 3. 即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。“底层值为 nil 的接口值”
    // ps: 保存了 nil 具体值的接口其自身并不为 nil。
    
    // 4. nil 接口值:和 3 不同,会产生运行时错误。“nil 接口值” 
    // 也就是找不到具体方法的值,而 3 则是找到具体方法,找不到具体对象
    type MyInterface interface {
      M()
    }
    func main() {
      var i MyInterface
      i.M()
    }
    
  3. 空接口:定义了 0 个方法的接口,可保存任何类型的值

    // 被用来处理未知类型的值
    func main() {
      var i interface{}
      fmt.Printf("(%v, %T)\n", i, i)// (<nil>, <nil>)
      i = 42
      fmt.Printf("(%v, %T)\n", i, i)// (42, int)
      i = "hello"
      fmt.Printf("(%v, %T)\n", i, i)// (hello, string)
    }
    

    **类型断言:**提供了访问接口值底层具体值的方式。

    // t = i.(T)
    func main() {
      var i interface{} = "hello"
      
      s := i.(string) // hello
      s, ok := i.(string) // hello yes
      
      // 将会赋予对应零值
      f, ok := i.(float64) // 0, false
      // 失败,且没有接收判断值的情况会报错
      f := i.(float64)
    }
    
  4. 类型选择

    func do(i interface{}) {
      switch v := i.(type) {
      	case int : 
        	fmt.Println("int")
        case string:
        	fmt.Println("string")	
        default:
        	fmt.Println("I don't know how %T !", v)
      }
    }
    
    func main() {
      do(32)
      do("hello")
      do(true)
    }
    
  5. Stringer:属于 fmt 包

    type Stringer interface {
      // 用于描述自己,类似 Java 的 toString()
      String() string
    }
    
  6. 错误:类似5,是一个内建接口

    type error interface {
      Error() string
    }
    
    // 通常函数会返回一个 error 值, error 值为 nil 时成功,否则失败
    type MyError struct {
      when time.Time
      what string
    }
    
    func (myError *MyError) Error() string {
      return fmt.Sprintf("%v, %s", myError.when, myError.what)
    }
    
    func run() error {
      return &MyError{
        time.Now(),
        "it didn't work" 
      }
    }
    
    func main() {
      if err := run(); err != nil {
        fmt.Println(err)
      }
    }
    
  7. Reader: io 包

    func main() {
    	// 新建一个 Reader
    	r := strings.NewReader("Hello Golang~");
    
    	// 新建一个容量为8的切片
    	mySlice := make([]byte, 8);
    
    	for {
    		// func (T) Read(b []byte) (n int, err error)
    		n, err := r.Read(mySlice)
    		fmt.Printf("n = %v  err = %s  mySlice = %v \n", n, err, mySlice)
    		// 遇到数据流结尾时,会返回一个 EOF 错误
    		if err == io.EOF {
    			break;
    		}
    	}
    }
    
  8. 待补充:图像

五. 并发

  1. Go 程(goroutine): Go 运行时管理的轻量级线程。

    // f, x, y 和 z 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中。
    // go f(x, y, z)
    
    func sum(s []int, c chan int) {
    	sum := 0;
    	for _, v := range s {
    		sum += v
    	}
    	// 把 sum 的值送入流信道中
    	c <- sum
    }
    
    func main() {
    	arr := []int{1, 2, 3, -1, -2 ,7}
    	// 创建管道
    	c := make(chan int)
    	// 各负责一半
    	go sum(arr[: len(arr)/2], c)
    	go sum(arr[len(arr)/2 :], c)
    	// 都结束后,再从信道中获取
    	x, y := <- c, <- c
    	fmt.Println(x + y)
    }
    
    
  2. 带缓冲的信道

    // 1. 通过在 make() 第二个参数设置缓冲
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    ch <- 3 // 填满后会阻塞
    
  3. range & close

    // 1. 可以通过分配第二个参数来判断信道是否关闭
    ch = make(chan int)
    // 如果已经关闭了, ok = false
    v, ok := <-c
    
    // 2. 如果使用 range,会不断从信道接收值,直到他被关闭
    for i := range c {
    }
    
  4. select 语句

    // 1. 使一个 Go 程可以等待多个通信操作。
    // 2. 会阻塞到任一分支可行,如多个可行则随机一个
    func fibonacci(c, quit chan int) {
    	x, y := 0, 1
    	for {
    		select {
    		case c <- x:
    			x, y = y, x + y
    		// quit 可流出时
    		case <- quit:
    			fmt.Println("quit")
    			return
        default:
        	fmt.Println("其他分支都没有准备好~")
    		}
    	}
    }
    
    func main() {
    	c := make(chan int)
    	quit := make(chan int)
    
    	// 有 Java 匿名类内味了
    	go func() {
    		for i := 0; i < 10; i++ {
    			fmt.Println(<- c)
    		}
    		quit <- 1
    	}()
    	fibonacci(c, quit)
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值