Featured image of post Go 萌新的初学之路

Go 萌新的初学之路

Go学习日记(一):基础类型和语句

本文属于 Golang 学习日记 系列:
  1. Go 萌新的初学之路 (本文)
  2. Go 萌新的初学之路 II
  3. Go 萌新的初学之路 III
  4. Go 萌新的初学之路 IV
  5. Go 萌新的初学之路 V

包、变量与函数

程序从 main 包开始运行。可以通过导入路径 "math/rand" 来使用这个包,包名与导入路径的最后一个元素一致。例如,"math/rand" 包中的源码均以 package rand 语句开始。

 1package main
 2
 3import (
 4    "fmt"
 5    "math/rand"
 6)
 7
 8func main() {
 9    fmt.Println("我最喜欢的数字是 ", rand.Intn(10))
10}

在 Go 中,只有首字母大写的标识符才会被导出,也就是说小写的标识符在其他包中是不可见的。可以自定义package常量,不过没有必要:

多此一举
 1package main
 2
 3import (
 4    "fmt"
 5    "math"
 6)
 7
 8func main() {
 9    const pi = math.Pi
10    fmt.Println(pi)
11}

输出: 3.141592653589793

萌新的疑问

可以 import "math/rand" ,那么可不可以 import "math/rand" 呢?答案是不可以的,因为 math/rand 是标准库中真实存在的独立子包,而 Pimath 包的导出常量,不属于包。以下是常用的子包:

  • math/rand:随机数相关
  • math/big:大数运算
  • math/cmplx:复数运算

函数可以返回任意数量的返回值。swap 函数返回了两个字符串。

 1package main
 2
 3import "fmt"
 4
 5func swap(x, y string) (string, string) {
 6    return y, x
 7}
 8
 9func main() {
10    a, b := swap("hello", "world")
11    fmt.Println(a, b)
12}

输出: world hello

Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。没有参数的 return 语句会直接返回已命名的返回值,也就是「裸」返回值。裸返回语句在长的函数中它们会影响代码的可读性。

 1package main
 2
 3import "fmt"
 4
 5func split(sum int) (x, y int) {
 6    x = sum * 4 / 9
 7    y = sum - x
 8    return
 9}
10
11func main() {
12    fmt.Println(split(17))
13}

输出:7 10

var 语句用于声明一系列变量,可以出现在包或函数的层级。

 1package main
 2
 3import "fmt"
 4
 5var c, python, java bool
 6
 7func main() {
 8    var i int
 9    fmt.Println(i, c, python, java)
10}

输出:0 false false false

如果声明变量时提供了初始值,则类型可以省略;变量会从初始值中推断出类型。在这里通过fmt.Printf%T打印变量的类型:

 1package main
 2
 3import "fmt"
 4
 5var i, j = 1, 2
 6
 7func main() {
 8    var c, python, java = true, false, "no!"
 9    fmt.Println(i, j, c, python, java)
10    fmt.Printf("%T, %T, %T, %T, %T", i, j, c, python, java)
11}

输出:

1 2 true false no!

int, int, bool, bool, string

短变量声明操作符:=是用来创建具有适当名称和初始值的变量。使用这个操作符的主要目的是在函数中声明和初始化 局部变量 ,并缩小变量的范围。

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    var i, j int = 1, 2
 7    k := 3
 8    c, python, java := true, false, "no!"
 9
10    fmt.Println(i, j, k, c, python, java)
11    fmt.Printf("%T, %T, %T, %T, %T, %T", i, j, k, c, python, java)
12}

输出:

1 2 3 true false no!

int, int, int, bool, bool, string

Go 的基本类型有:

  • bool

  • string

  • int int8 int16 int32 int64

  • uint uint8 uint16 uint32 uint64 uintptr

  • byte
    // uint8 的别名

  • rune
    // int32 的别名,表示一个 Unicode 码位

  • float32 float64

  • complex64 complex128
    // 复数的64位浮点数表示

 1package main
 2
 3import (
 4    "fmt"
 5    "math/cmplx"
 6)
 7
 8var (
 9    ToBe   bool       = false
10    MaxInt uint64     = 1<<64 - 1
11    z      complex128 = cmplx.Sqrt(-5 + 12i)
12)
13
14func main() {
15    fmt.Printf("类型:%T 值:%v\n", ToBe, ToBe)
16    fmt.Printf("类型:%T 值:%v\n", MaxInt, MaxInt)
17    fmt.Printf("类型:%T 值:%v\n", z, z)
18}

输出:

类型:bool 值:false

类型:uint64 值:18446744073709551615

类型:complex128 值:(2+3i)

rune 类型是 Go 语言的一种特殊数字类型。在 builtin/builtin.go 文件中,它的定义:type rune = int32 ;官方对它的解释是:rune 是类型 int32 的别名,在所有方面都等价于它,用来区分字符值跟整数值。使用单引号定义,返回采用 UTF-8 编码的 Unicode 码点。Go 语言通过 rune 处理中文,支持国际化多语言。

UTF-8 编码规则

  • ASCII 字符(0-127):1 字节
  • 带变音符号的拉丁/希腊/西里尔字母:2 字节
  • 常用汉字(CJK 统一表意文字):3 字节
  • 生僻字/历史文字/表情符号:4 字节
 1package main
 2
 3import (
 4    "fmt"
 5)
 6
 7func main() {
 8    s := "Go语言编程"
 9    
10    fmt.Println(len("Go语言编程"))  // 输出:14
11    fmt.Println(len([]rune("Go语言编程")))  // 输出:6
12    fmt.Println(s[0:8])  // 输出:Go语言
13    fmt.Println(s[0:7]) // 输出:Go语��
14    fmt.Println(string([]rune(s)[:4])) // 输出:Go语言
15    
16    // byte
17    fmt.Println([]byte(s)) // 输出:[71 111 232 175 173 232 168 128 231 188 150 231 168 139]
18    // rune
19    fmt.Println([]rune(s)) // 输出:[71 111 35821 35328 32534 31243]
20}

字符串由字符组成,字符的底层由字节组成,而一个字符串在底层的表示是一个字节序列。在 Go 语言中,字符可以被分成两种类型处理:对占 1 个字节的英文类字符,可以使用 byte (或者 unit8 );对占 1 ~ 4 个字节的其他字符,可以使用 rune (或者 int32 ),如中文、特殊符号等。

Go 语言把字符分 byterune 两种类型处理。byte 是类型 unit8 的别名,用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节。rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值。

stringbyterune三者间的关系

字符串在底层的表示是由单个字节组成的一个不可修改的字节序列,字节使用 UTF-8[1] 编码标识 Unicode[2] 文本。Unicode 文本意味着 .go 文件内可以包含世界上的任意语言或字符,该文件在任意系统上打开都不会乱码。UTF-8 是 Unicode 的一种实现方式,是一种针对 Unicode 可变长度的字符编码,它定义了字符串具体以何种方式存储在内存中。UFT-8 使用 1 ~ 4 为每个字符编码。

Go 语言把字符分 byterune 两种类型处理。byte 是类型 unit8 的别名,用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节。rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值。如下图所示:


常量的声明与变量类似,只不过使用 const 关键字。常量可以是字符、字符串、布尔值或数值,但不能用 := 语法声明。

 1package main
 2
 3import "fmt"
 4
 5const Pi = 3.14
 6
 7func main() {
 8    const world = "世界"
 9    fmt.Println("Hello", world)
10    fmt.Println("Happy", Pi, "Day")
11
12    const Truth = true
13    fmt.Println("Go rules?", Truth)
14    
15    fmt.Printf("%T, %T, %T", world, Truth, Pi)
16}

输出:

Hello 世界

Happy 3.14 Day

Go rules? true

string, bool, float64

数值常量是高精度的int 类型可以存储最大 64 位的整数,根据平台不同有时会更小。

 1package main
 2
 3import "fmt"
 4
 5const Pi = 3.14
 6
 7func main() {
 8    const world = "世界"
 9    fmt.Println("Hello", world)
10    fmt.Println("Happy", Pi, "Day")
11
12    const Truth = true
13    fmt.Println("Go rules?", Truth)
14    
15    fmt.Printf("%T, %T, %T", world, Truth, Pi)
16}

输出:

21

0.2

1.2676506002282295e+29

在Go中,单引号括起来的是rune类型,也就是int32的别名,用来表示Unicode码点。而双引号括起来的才是字符串,比如"d"就是string类型。python转过来的真的有好多细节不适应 : (

1package main
2
3import "fmt"
4
5func main() {
6    v := 'd'
7    fmt.Printf("v is of type %T\n", v)
8    // 输出: v is of type int32
9}

流程控制语句:for、if、else、switch 和 defer

Go 只有一种循环结构:for 循环。基本的 for 循环由三部分组成,它们用分号隔开:

  • 初始化语句:在第一次迭代前执行
  • 条件表达式:在每次迭代前求值
  • 后置语句:在每次迭代的结尾执行

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。一旦条件表达式求值为 false,循环迭代就会终止。

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    sum := 0
 7    for i := 0; i < 10; i++ {
 8        sum += i
 9    }
10    fmt.Println(sum)
11}

输出:45 (1+2+3+4+5+6+7+8+9)

初始化语句和后置语句是可选的
 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    sum := 1
 7    for ; sum < 1000; {
 8        sum += sum
 9        fmt.Println(sum)
10    }
11    fmt.Println(sum)
12}
输出结果

2

4

8

16

32

64

128

256

512

1024

1024

怎么写 Go 中的「while」语句呢?其实就是 for 循环去掉了分号:

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    sum := 1
 7    for sum < 1000 {
 8        sum += sum
 9        fmt.Println(sum)
10    }
11    fmt.Println(sum)
12}
输出结果

2

4

8

16

32

64

128

256

512

1024

1024

if 语句与 for 循环类似:

 1package main
 2
 3import (
 4    "fmt"
 5    "math"
 6)
 7
 8func sqrt(x float64) string {
 9    if x < 0 {
10        return sqrt(-x) + "i"
11    }
12    return fmt.Sprint(math.Sqrt(x))
13}
14
15func main() {
16    fmt.Println(sqrt(2), sqrt(-4))
17}

输出:1.4142135623730951 2i

for 一样,if 语句可以在条件表达式前执行一个简短语句。该语句声明的变量作用域仅在 if 之内。(在pow最后的 return 语句处使用 v ,会报错 undefined: v

 1package main
 2
 3import (
 4    "fmt"
 5    "math"
 6)
 7
 8func pow(x, n, lim float64) float64 {
 9    if v := math.Pow(x, n); v < lim {
10        return v
11    }
12    return lim
13}
14
15func main() {
16    fmt.Println(
17        pow(3, 2, 10),
18        pow(3, 3, 20),
19    )
20}

输出:9 20

if 的简短语句中声明的变量同样可以在对应的任何 else 块中使用。(在 mainfmt.Println 调用开始前,两次对 pow 的调用均已执行并返回其各自的结果。)

 1package main
 2
 3import (
 4    "fmt"
 5    "math"
 6)
 7
 8func pow(x, n, lim float64) float64 {
 9    if v := math.Pow(x, n); v < lim {
10        return v
11    } else {
12        fmt.Printf("%g >= %g\n", v, lim)
13    }
14    // can't use v here, though
15    return lim
16}
17
18func main() {
19    fmt.Println(
20        pow(3, 2, 10),
21        pow(3, 3, 20),
22    )
23}

输出:

27 >= 20

9 20

switch 语句是编写一连串 if - else 语句的简便方法。它运行第一个 case 值 值等于条件表达式的子句。

Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只会运行选定的 case,而非之后所有的 case。 在效果上,Go 的做法相当于这些语言中为每个 case 后面自动添加了所需的 break 语句。在 Go 中,除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switchcase 无需为常量,且取值不限于整数。

 1package main
 2
 3import (
 4    "fmt"
 5    "runtime"
 6)
 7
 8func main() {
 9    fmt.Print("Go 运行的系统环境:")
10    switch os := runtime.GOOS; os {
11    case "darwin":
12        fmt.Println("macOS.")
13    case "linux":
14        fmt.Println("Linux.")
15    default:
16        // freebsd, openbsd,
17        // plan9, windows...
18        fmt.Printf("%s.\n", os)
19    }
20}

输出:Go 运行的系统环境:Linux.

switchcase 语句从上到下顺次执行,直到匹配成功时停止。如:

1switch i {    
2    case 0:
3    case f():
4}

注意: 在 i==0 时,f 不会被调用。

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    fmt.Println("周六是哪天?")
10    today := time.Now().Weekday()
11    switch time.Saturday {
12    case today + 0:
13        fmt.Println("今天。")
14    case today + 1:
15        fmt.Println("明天。")
16    case today + 2:
17        fmt.Println("后天。")
18    default:
19        fmt.Println("很多天后。")
20    }
21}

输出:

周六是哪天?

很多天后。

无条件的 switchswitch true 一样。这种形式能将一长串 if-then-else 写得更加清晰。

 1package main
 2
 3import (
 4    "fmt"
 5    "time"
 6)
 7
 8func main() {
 9    t := time.Now()
10    switch {
11    case t.Hour() < 12:
12        fmt.Println("早上好!")
13    case t.Hour() < 17:
14        fmt.Println("下午好!")
15    default:
16        fmt.Println("晚上好!")
17    }
18}

输出:晚上好!

defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

1package main
2
3import "fmt"
4
5func main() {
6    defer fmt.Println("world")
7
8    fmt.Println("hello")
9}

输出:

hello

world

推迟调用的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的调用会按照后进先出的顺序调用。

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6    fmt.Println("counting")
 7
 8    for i := 0; i < 10; i++ {
 9        defer fmt.Println(i)
10    }
11
12    fmt.Println("done")
13}

输出结果

counting

done

9

8

7

6

5

4

3

2

1

0

小结

作为一个 python 爱好者,转到 go 后在许多小角落都会有些迷茫,比如大小写敏感、单双引号不通用等等。但是还是记录一下自己的学习进程,敦促自己尽快打好 go 的基础,之后打算找几个项目练练手。

Licensed under CC BY-NC-SA 4.0
最后更新于 2025-09-16 11:05 +0800
本文属于 Golang 学习日记 系列:
  1. Go 萌新的初学之路 (本文)
  2. Go 萌新的初学之路 II
  3. Go 萌新的初学之路 III
  4. Go 萌新的初学之路 IV
  5. Go 萌新的初学之路 V
给博主施舍一个赞吧(;へ:) ❤️