4.1 文件名、关键词与标识符

Go源文件是以.go为后缀,文件名以字母和下划线组成,不包含空格或特殊字符。
Go的源文件是没有限制大小的。
Go的标识符是区分大小写的。
有效的标识符是字母、数字和下划线组成,不能以数字开头。
下划线_是一个特殊的标识符,它有一个特殊的用途,叫做空白标识符,是一个只写变量,通常用来接收不处理的返回值,因为Go中的变量声明了就一定要使用的,如果用不到,就可以将返回值往下划线里写。
25个关键词或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
  1. 36个预定义标识符:
append bool byte cap close complex
complex64 complex128 uint16 copy false float32
float64 imag int int8 int16 unit32
int32 int64 iota len make new
nil panic unit64 print println real
recover string true uint uint8 uintptr

如果要把多行语句写在同一行,则需要使用分号;隔开,但不鼓励。

4.2 基本结构和要素

4.2.1 包的概念、导入与可见性

类似于其他语言的类库或命名空间,每个Go文件都属于一个包,比如main包,一个包可以包含很多个go源文件。
一个包不是一个文件夹,但是通常情况下,我们都会以子文件夹的命名作为包名,这样在导入的时候很方便。
通常,我们将main.go程序入口文件单独放项目根目录,其他的文件会放在子文件夹或者或其他包中。
在导入子包的时候,需要连父目录也导入,比如下面的例子:
image.png
main.go单独放在项目TEST目录下,其他的业务文件dd.go放在子文件夹testdir下,dd.go:

  1. package testdir
  2. import "fmt"
  3. func Test() {
  4. fmt.Println("这里是testdir包")
  5. }

main.go:

  1. package main
  2. import (
  3. "test/testdir" //这里需要连父目录test也导入,而不是testdir
  4. )
  5. func main() {
  6. testdir.Test()
  7. }

dd.go属于testdir包,也是目录名,但是这不是强制的,dd.go可以改包名为任意的,如test123包,那么在mian.go导入的时候需要这样:

  1. //dd.go
  2. package test123
  3. import "fmt"
  4. func Test() {
  5. fmt.Println("这里是testdir包")
  6. }
  7. //main.go
  8. package main
  9. import (
  10. test123 "test/testdir" //这里需要连父目录test也导入,而不是testdir
  11. )
  12. func main() {
  13. test123.Test()
  14. }

如果一个包进行了更改或重新编译,那么导入该包的程序都要重新编译。
一个包是编译的一个单元,所以通常在开发中,一个目录只包含一个包。
依赖包之间的编译顺序:比如a.go依赖b.go,b.go又依赖c.go,那么

  1. - 编译c.gob.go,然后是a.go
  2. - 为了编译a.go,编译器读取的是b.go,而不是c.go

这种机制可以显著提高编译速度,至于为什么?不知道。
包的查找规则:如果包名不是以 . 或 / 开头,如 “fmt” 或者 “container/list”,则 Go 会在全局文件进行查找;如果包名以 ./ 开头,则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
可见性规则,相当于其他语言的公有属性,私有属性的实现。
当标识符(变量名,函数名,类型等)是以大写字母开头时,此标识符的对象就可以被外部使用,前提是导入了这个包;如果是小写字母开头,那么就是不可见的,私有的。

4.2.2 注释

  • 单行注释//
  • 多行注释/ /

go doc工具会收集这些注释产生一个技术文档。

4.2.3 函数

一个函数是可以完成某个功能的代码块。Go中的函数可以有多少参数,也可以返回多个参数。

  1. package main
  2. import "fmt"
  3. func add(a int, b int) (int, int) { //加法函数,多参数,多返回值
  4. return 1, a + b
  5. }
  6. func main() {
  7. _, sum := add(5, 6) //舍弃返回值1
  8. fmt.Println(sum)
  9. }

Go中的左花括号{必须紧跟着函数的第一行。花括号的使用规则是通用的,比如if,struct。

4.2.4 程序执行

程序的执行顺序:

  1. - 按顺序导入所有被main包引用的其他包,然后在每个包中执行如下流程:
  2. - 如果该包又导入了其他的包,则从第一步开始递归执行,每个包只会被导入一次。
  3. - 以相反的顺序在每个包中初始化常量和变量,如果该包有init()函数的话,则调用该函数。
  4. - 在完成之后,main也执行相同的流程,最后调用main()开始执行程序。

4.2.5 类型

  • 基本类型:int,float,bool,string。
  • 复合类型:struct,array,slice,map,channel,interface。

函数也可以是一个类型,所以函数也可以作为返回值
当使用var关键词来声明类型时,会自动赋值各自类型的零值。
类型别名,使用type关键词来定义:
type myint int,即定义一个int类型的myint类型,但是这里并不是真正意义的别名,是不相等的。不知道实际用处。

4.2.6 类型转换

Go不存在隐式转换。在类型转换中,类型可以看作是一种函数。
在同种类型中(值类型,字符类型),类型转换只要加上类型名即可,如int转int64:int64(a);string转[]byte,[]byte(a)。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. var a string
  8. a = "1"
  9. fmt.Println(a)
  10. fmt.Println([]byte(a))
  11. fmt.Println(reflect.TypeOf([]byte(a))) //利用发射来查看变量的类型
  12. }

但是,如果是int和string之间的转换,需要用到strcov内置函数。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strconv"
  6. )
  7. func main() {
  8. var a int
  9. var b string
  10. a = 1
  11. b = "2"
  12. c := strconv.Itoa(a) //int转string
  13. d, _ := strconv.Atoi(b) //string转int
  14. fmt.Println(reflect.TypeOf(c))
  15. fmt.Println(reflect.TypeOf(d))
  16. }

4.3 常量

常量使用const关键词定义,定义一些不会改变的量。
常量只可以是数字型,布尔型,字符串型。

  1. - 显示定义:const a string = "1"
  2. - 隐式定义:const b = "2"

习惯,也推荐显示定义,隐式定义比那一起会自动推断它的类型。
常量的值必须是编译时能够确定的,所以函数返回值不能赋值给常量,因为函数要经过计算的,但是内置的函数可以,比如len()。
常量值如果太长,可以使用\来分行。
常量也允许并行定义,并行赋值:const a,b,c = 1,2,”d”。
iota是一个用来枚举的特殊字符。每经过一行就自动+1,遇到const就会置0:

  1. package main
  2. func main() {
  3. const (
  4. a = iota //a = 0
  5. b //b = 1
  6. c = iota //c = 2
  7. d = iota + 5 //d = 8
  8. )
  9. const e = iota //d = 0
  10. }

4.4 变量

4.4.1 简介

变量的声明一般使用var关键词:var a,b string,也可以使用简短声明:a := 123,简短声明只能在函数体内用。
变量声明之后,系统自动赋予该类型的零值,int为0,float为0.0,bool为false,string为空字符串,指针为nil。所有的内存空间在Go中都是经过初始化的。
变量的作用域

  1. - 全局变量:声明在函数体外。
  2. - 局部变量:声明在一对花括号中,可能是函数,可能是if for等结构。只能作用在这对花括号内。

下面的程序,会出错:

  1. package main
  2. import "fmt"
  3. func main() {
  4. a := 1
  5. if a == 1 {
  6. b := 2 //只作用在if语句中
  7. fmt.Println(b)
  8. }
  9. fmt.Println(b) //未声明
  10. }

因为全局变量可以作用在包内的任何地方,所有可以在函数内修改全局变量的值,不像其他语言需要类似于global关键词来引用。

4.4.2 值类型和引用类型

int,float,bool,string,array这些都属于值类型,值类型的变量存储的是内存中的值。
值类型的变量在赋值给另一个变量时,是拷贝操作。
可以通过&a来获取变量a的内存地址。
值类型的变量的值存储在中。

slice,map,channel,interface,指针,函数这些属于引用类型,引用类型的变量存储的是值所在的内存地址,或者内存地址中第一个字所有的位置。每个字都指示了下一个字所在的内存地址。
引用类型的变量在赋值给另一个变量时,是传递内存地址,不是值,所以都有有关的变量的更改都会收到同样的影响。
引用类型的变量存储在中,以便进行垃圾回收,且堆比栈内存空间更大。

4.4.3 打印

主要是fmt包中的打印函数:

  1. - fmt.Printf:格式化打印:fmt.Printf("a 的值为 %d", a)
  2. - fmt.Sprintf:也是格式化打印,但把字符串返回:b := fmt.Sprintf("b 的值为 %d", a)
  3. - fmt.Print:标准格式化打印,自动使用%v
  4. - fmt.Println:比fmt.Print多了一个换行。

在格式化输出中:

  1. - %v:按值的本来值输出。
  2. - %+v:在 %v 基础上,对结构体字段名和值进行展开。
  3. - %#v:输出 Go 语言语法格式的值。
  4. - %T:输出 Go 语言语法格式的类型和值。
  5. - %%:输出 % 本体。
  6. - %b:整型以二进制方式显示。
  7. - %o:整型以八进制方式显示。
  8. - %d:整型以十进制方式显示。
  9. - %x:整型以十六进制方式显示。
  10. - %X:整型以十六进制、字母大写方式显示。
  11. - %UUnicode 字符。
  12. - %f:浮点数。
  13. - %p:指针,十六进制方式显示。

4.4.4 init函数

变量除了可以在全局声明中初始化,也可以在init函数内初始化。
init函数不能被人为调用。
init函数执行优先级比main函数高。
一个源文件可以有多个init函数。从上到下顺序执行。
如果一个包中多个源文件都有init函数,则执行顺序不明。

4.5 基本类型和运算符

4.5.1 布尔型

Go是强类型,两个值之间的比较必须保证都是相同的类型。
Go中的&&和||具有快捷性质的运算符,当运算符左边的表达式的值已经可以满足整个表达式的值的时候,右边的表达式就不执行了,所以在编码中,如果有多个判断条件,应当把能快速得出结论的,较为简单计算的放左边。
在格式化输出中,可以使用%t输出布尔型的变量。

4.5.2 数字型

整型int和浮点型float,复数型。
数字型的位运算,采用补码的形式。
int,uint,uintptr这些是基于架构的类型,也就是在不同的操作系统,使用的字节不同。

  • ·int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64位(8 个字节)。
  • uintptr 的长度被设定为足够存放一个指针即可。

Go没有float类型,只有float32和float64。float32精确到小数点后7位,float64是精确到后15位。在比较的时候,需要特别注意精度丢失,应尽量使用float64。
各类型的范围:

  • 整数:
    • int8(-128 -> 127)
    • int16(-32768 -> 32767)
    • int32(-2,147,483,648 -> 2,147,483,647)
    • int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
  • 无符号整数:
    • uint8(0 -> 255)
    • uint16(0 -> 65,535)
    • uint32(0 -> 4,294,967,295)
    • uint64(0 -> 18,446,744,073,709,551,615)
  • 浮点型(IEEE-754 标准):
    • float32(+- 1e-45 -> +- 3.4 * 1e38)
    • float64(+- 5 1e-324 -> 107 1e308)

添加前缀0表示8进制,如077,添加前缀0x表示16进制,如0x77。
Go有两个复数类型:complex64(32位实数和虚数),complex128(64位实数和虚数)。
var c1 complex = 1+2i
使用real(c1)可以获取实数部分,使用imag(c1)可以获取虚数部分,都是浮点型。
可以使用%v打印复数。

4.5.3 字符型

严格来说,这并不是Go的一个类型,字符只是整数的特殊用例。
byte是uint8的别名。
rune是int32的别名。
当我们定义一个单引号的字符时,实际上它时int32类型的:a := ‘1’ 。

  1. var ch int = '\u0041'
  2. var ch2 int = '\u03B2'
  3. var ch3 int = '\U00101234'
  4. fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
  5. fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
  6. fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
  7. fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point

字符串是值类型,是不可变的,可以看作是一种定长数组,数组也是不可变的。
字符串是可以索引的,迭代的。
字符串分为解释字符串和非解释字符串。

  1. - 解释字符串:斜杠\是转义字符串,能产生一些特殊的组合。
  2. - \n:换行符
  3. - \r:回车符
  4. - \ttab
  5. - \u \UUnicode 字符
  6. - \\:反斜杠自身
  7. - 非解释字符串:反斜杠不起作用,所有字符都原样输出,同时支持多行字符。

字符串之间可以使用+号拼接,也可以使用strings.join()拼接,或者bytes.buffer。

4.5.4 指针型

程序在内存中存储值,每个内存块都有一个地址,成为内存地址,用十六进制表示。
变量直接指向值,而指针直接指向值的内存地址。
取值符用&,获取一个值的内存地址。
声明指针:var ptr *int,是一个int类型的指针。

  1. package main
  2. import "fmt"
  3. func main() {
  4. i := 123
  5. var p *int //声明一个指针
  6. p = &i //取得i的地址
  7. fmt.Println("i的值", i)
  8. fmt.Println("p的值", p)
  9. fmt.Println("获取i的值", *p) //*p = *(&i) = i
  10. }

指针是指针地址,所以是引用类型,在传递给函数参数时,是引用传递。
Go的指针不能运算,即指针不能移动,p++,p+2等都是不允许的。

4.5.4 位运算

%b表示位的格式化标识符。
二元运算符:

  1. - 按位与&:均相同为1,否则为0
  2. - 按位或|:均为0,才为0,否则为1
  3. - 按位异或^:不同为1,相同为0
  4. - 按位清除:将指定位置上的值设置为0

一元运算符:

  1. - 按位补足^:和异或公用一个操作符。对于无符号值使用 “全部位设置为 1 的规则,对于有符号 值,将该值与-1异或。^2 = 2^-1 = -3
  2. - 位左移<<n:相当于乘2n次方。
  3. - 位右移>>n:相当于除2n次方。
  1. package main
  2. import "fmt"
  3. func main() {
  4. var a int = 1 //0000 0001
  5. var b int = 3 //0000 0011
  6. var c int = a & b //按位与
  7. var d int = a | b //按位或
  8. var e int = a ^ b //按位异或
  9. var f int = b &^ a //按位清除,把b在a的位置的数置0
  10. print(c) //0000 0001 = 1
  11. print(d) //0000 0011 = 3
  12. print(e) //0000 0010 = 2
  13. print(f) //0000 0010 = 2
  14. fmt.Printf("%08b", ^2) //-0000 0011
  15. print(1 << 10) //左移10位 = 1024
  16. print(1 >> 10) //右移10位 = 0
  17. }

格式化说明符 %c 用于表示字符;当和字符配合使用时,%v 或 %d 会输出用于表示该字符的整数;%U 输出格式为 U+hhhh 的字符串
包 unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  1. - 判断是否为字母:unicode.IsLetter(ch)
  2. - 判断是否为数字:unicode.IsDigit(ch)
  3. - 判断是否为空白符号:unicode.IsSpace(ch)

4.5.5 随机数

内置rand包实现了伪随机数的生成。

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. )
  6. func main() {
  7. a := rand.Int() //随机生成一个数,一般很大
  8. b := rand.Intn(100) //[0,100)内生成一个随机数
  9. fmt.Println(a, b)
  10. }

4.7 strings包和strconv包

4.7.1 strings包

一些常用的查找函数:

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func main() {
  7. s := "I love China, i am wuyanzu"
  8. if strings.HasPrefix(s, "China") { //判断开头字符
  9. fmt.Println("字符串有China")
  10. }
  11. if strings.HasSuffix(s, "wuyanzu") { //判断结尾字符
  12. fmt.Println("字符串以wuyanzu结尾")
  13. }
  14. if strings.Contains(s, "am") { //判断包含字符
  15. fmt.Println("字符串包含了am")
  16. }
  17. k := strings.Index(s, "i") //返回字符所在第一个位置索引,LastIndex判断最后的位置索引
  18. if k != -1 {
  19. fmt.Println("i在字符串的位置:", k)
  20. }
  21. }

一些常用的替换函数:

  1. package main
  2. import (
  3. "strings"
  4. )
  5. func main() {
  6. s := "I love China, i am wuyanzu "
  7. news := strings.Replace(s, "China", "Chinese", -1) //-1代表替换所有
  8. cts := strings.Count(s, "i") //统计i在字符串中出现的次数
  9. rs := strings.Repeat("d", 5) //重复5次d字符,并返回
  10. ups := strings.ToUpper(s) //变大写
  11. lows := strings.ToLower(s) //变小写
  12. cuts := strings.TrimSpace(s) //剔除开头结尾的空格
  13. cutss := strings.Trim(s, "wuyanzu") //剔除开头结尾的wuyanzu
  14. slis := strings.Fields(s) //分割字符串,以空格分隔
  15. spls := strings.Split(s, ",") //以逗号来分割
  16. sli_str := strings.Join(slis, s) //切片slice和字符串拼接
  17. }

4.7.2 strconv包

整个包是用来将字符串和其他类型转换的。

  1. package main
  2. import (
  3. "strconv"
  4. )
  5. func main() {
  6. i := 123
  7. s := "123"
  8. var f float64 = 123.123
  9. toint, _ := strconv.Atoi(s) //字符串转int
  10. tos := strconv.Itoa(i) //int转字符串
  11. ftos := strconv.FormatFloat(f, 'f', 15, 64) //float64转字符串,精度15
  12. stof, _ := strconv.ParseFloat(s, 32) //字符串转float64
  13. }