【Go学习笔记】(一)通过go-by-example学Go

85 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

前言

从今天开始,打算正式入坑Go,并计划以一周为周期,初步熟悉Go语言特性、基础语法等。吸取了前面学习的教训,这次打算通过阅读分析现成代码、主动思考和探索来学习。 本次参考的入门代码GitHub地址为: go-by-example

参考书籍:《Go语言圣经》 Go语言圣经

官方权威文档: Go Package

hello world!

package main

import (
   "fmt"
)

func main() {
   fmt.Println("hello world")
}
  • Q:明明看到它所属的包名叫01-hello,为什么package后跟的是main?而且换了别的名称后编译不通过? A:在Go中,main不是一个库,而是一个独立可执行的程序。main中的main函数是程序的入口。

  • import的"fmt"是什么?

  1. fmt是Go提供的100多个包里面的其中一个,是处理输入输出的,类似于C的"stdio.h"
  2. 必须告诉编译器需要哪些包,而且要做到不重不漏,因为Go是编译型语言,通过静态编译将源代码及其依赖转换成计算机的机器指令,像C/C++一样比较接近底层、讲求链接。 3.顺序不能错,package在前,import在后
  • 函数相关
  1. 包本身小写,包中的函数首字母开头要大写。比如"Println"
  2. 截至目前,只见过写main()函数,格式是 func main(),先记着这个func的表达

var

package main

import (
   "fmt"
   "math"
)

func main() {

   var a = "initial"

   var b, c int = 1, 2

   var d = true

   var e float64

   f := float32(e)

   g := a + "foo"
   fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
   fmt.Println(g)                // initialapple

   const s string = "constant"
   const h = 500000000
   const i = 3e20 / h
   fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

这次导入了"math"包,可作数学处理

  • ":="是什么写法?

短变量声明的一部分,定义一个或多个变量并根据它们的初始值为这些变量赋予适当类型的语句 (确实,看着这个变量前面并没有标注int之类)


(之后几个程序感觉没啥疑问,就先跳过了)

slice(切片)

package main

import "fmt"

func main() {

   s := make([]string, 3)
   s[0] = "a"
   s[1] = "b"
   s[2] = "c"
   fmt.Println("get:", s[2])   // c
   fmt.Println("len:", len(s)) // 3

   s = append(s, "d")
   s = append(s, "e", "f")
   fmt.Println(s) // [a b c d e f]

   c := make([]string, len(s))
   copy(c, s)
   fmt.Println(c) // [a b c d e f]

   fmt.Println(s[2:5]) // [c d e]
   fmt.Println(s[:5])  // [a b c d e]
   fmt.Println(s[2:])  // [c d e f]

   good := []string{"g", "o", "o", "d"}
   fmt.Println(good) // [g o o d]
}
  • make是什么?和new有什么区别?
  1. make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
  2. new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  3. new 分配的空间被清零。make 分配空间后,会进行初始化; 参考资料: Go语言make和new关键字的区别及实现原理
  • slice操作
  1. slice支持直接append(感觉这方面像Java的动态数组),而且还可以一次添加多个;
numbers = append(numbers, 2,3,4)  
printSlice(numbers)
  1. 支持索引取块s[2,5](这一点像python的切片)
  2. 支持直接拷贝
copy(numbers1,numbers)  
printSlice(numbers1)
  • 稍微深挖一下slice slice的特性体现和它的底层有关。

slice像个更灵活、更轻便的数组,而实际上,它的底层指向的就是数组,并且不同的slice引用的可能是同一块内存区域。

image.png 从上图也可以理解:slice的第一个元素不一定对应底层数组起始的第一个元素。

slice由三部分组成:指针、长度、容量。

这个概念很好理解,类似于初学数据结构时用c自定义一个栈的结构体。

image.png

所以Go中的len()和cap()各自返回的是什么也就可以类比了。

  • 【存疑,后续跟进】对于《go语言圣经》(中文译本)中的以下一段话,我感觉有点疑惑

另外,字符串的切片操作和[]byte字节类型切片的切片操作是类似的。都写作x[m:n],并且都是返回一个原始字节序列的子序列,底层都是共享之前的底层数组,因此这种操作都是常量时间复杂度。x[m:n]切片操作对于字符串则生成一个新字符串,如果x是[]byte的话则生成一个新的[]byte。

对此,我特意去查了一下英文原文:

As an aside, not e the similarity of the substring operation on strings to the slice operator on []byte slices. Both are written x[m:n], and both return a subsequence of the original bytes, sharing the underlying represent ation so that both operations take constant time. The expression x[m:n] yields a string if x is a string , or a []byte if x is a []byte.

原文说的是yield,和return(返回)一词差不多意思,它也没有说是生成一个新的吧?

  • slice操作可以直接修改底层数组。
  • slice之间无法直接比较,不能使用“==”判断两个slice是否相等。 对于byte数组,官方提供了高度优化的bytes.Equal()函数,但是其他的就得自己写逐个比较的了。

不过以下语句是合法的:

if summer == nil { /* ... */ }

nil表示一个长度为0、容量为0的空切片(或者说没有切片,看首字母n->null),因为是空的,所以不指向任何底层数组。

不过判断是否为空切片,不应该使用上述==语句,应该使用len(s) == 0