GO语言基础|青训营笔记

87 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天

GO语言圣经笔记

1. 入门

1.1基本结构

Go语言是一门静态编译型语言,其提供的工具都通过一个单独的命令 go 调用,go 命令有一系列子命令。最简单的一个子命令就是 run

 package main
 ​
 import "fmt"
 ​
 func main() {
     fmt.Println("Hello, World! ^_^ ")
 }

VS code中新建01_helloworld.go文件,输入上述代码,通过command + J调出终端,输入以下代码运行go程序。

go run 01_helloworld.go

image-20230116224752063

在MAC端下,输入go build 01_helloworld.go,用来编译go程序,编译后的go程序可以直接运行。

image-20230116225332470

(package)

每个go程序都是由构成,每个源文件都以一条 package 声明语句开始,表示该文件属于哪个包。

程序从main包开始运行,在package声明后面导入(import)一系列需要用到的包。

按照约定,包名与导入路径的最后一个元素一致。

package main

按照惯例,我们在每个包的包声明前添加注释;对于 main package,注释包含一句或几句话,从整体角度对程序做个描述。

main 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 main 里的 main 函数是整个程序执行时的入口。

import "fmt"

Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如 fmt 包,就含有格式化输出、接收输入的函数。

 package main
 ​
 import (
   "fmt"
   "math/rand"
 )
 ​
 func main() {
   rand.Seed(86)   //初始化种子
   fmt.Println("My favorite number is", rand.Intn(100))
   fmt.Println("My favorite number is", rand.Intn(100))
   fmt.Println("My favorite number is", rand.Intn(100))
 }

"math/rand" 包中的源码均以 package rand 语句开始,在rand包官方文档里可以查到rand包中的各个函数的用法,本例用rand.Intn(n)输出伪随机数。通常使用非固定种子time.Now().UnixNano()

 package main
 ​
 import (
   "fmt"
   "math/rand"
   "time"
 )
 ​
 func main() {
   rand.Seed(time.Now().UnixNano())
   fmt.Println("My favorite number is", rand.Intn(100))
   fmt.Println("My favorite number is", rand.Intn(100))
   fmt.Println("My favorite number is", rand.Intn(100))
 }
 ​

导出名

在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。

 package main
 ​
 import (
   "fmt"
   "math"
 )
 ​
 func main() {
   // fmt.Println(math.pi)   报错
   fmt.Println(math.Pi)      //输出:3.141592653589793
 }

函数

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。

 func name(parameter-list) (result-list) {
     body
 }

形式参数列表描述了函数的参数名以及参数类型

返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。

下面,是4种方法声明拥有2个int型参数和1个int型返回值的函数

 func add(x int, y int) int   {return x + y}
 func sub(x, y int) (z int)   { z = x - y; return}
 func first(x int, _ int) int { return x }
 func zero(int, int) int      { return 0 }
 ​
 fmt.Printf("%T\n", add)   // "func(int, int) int"
 fmt.Printf("%T\n", sub)   // "func(int, int) int"
 fmt.Printf("%T\n", first) // "func(int, int) int"
 fmt.Printf("%T\n", zero)  // "func(int, int) int"

每一次函数调用都必须按照声明顺序为所有参数提供实参(参数值)。在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。

注意类型在变量名之后。当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。

(参考 这篇关于 Go 语法声明的文章了解这种类型声明形式出现的原因。)

多值返回

函数可以返回任意数量的返回值。

swap 函数返回了两个字符串。

 package main
 ​
 import "fmt"
 ​
 func add(x int, y int) int {
         return x + y
 }
 ​
 func swap(x, y int) (int, int) {
         return y, x
 }
 ​
 func main() {
         a, b := 42, 13
         fmt.Println(add(42, 13)) //输出:55
         fmt.Println(swap(a, b))  //输出:13 42
         fmt.Printf("%T\n", add)  //输出:func(int, int) int
 ​
 }

变量及初始化

 package main
 ​
 import "fmt"
 ​
 var i, j int = 1, 2
 ​
 func main() {
   var c, python, java = true, false, "no!"
   //可替换为i,  j, c, python, java := 1, 2, true, false, "yes!"
   fmt.Println(i, j, c, python, java)    //输出:1 2 true false no!
 }

短变量声明

在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。

函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。

零值

没有明确初始值的变量声明会被赋予它们的零值。

零值是:

  • 数值类型为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)

常量

常量的声明与变量类似,只不过是使用 const 关键字。

常量可以是字符、字符串、布尔值或数值。

常量不能用 := 语法声明。

类型转换

与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换

表达式 T(v) 将值 v 转换为类型 T

一些关于数值的转换:

 var i int = 42
 var f float64 = float64(i)
 var u uint = uint(f)

或者,更加简单的形式:

 i := 42
 f := float64(i)
 u := uint(f)

类型推导

在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。

当右值声明了类型时,新变量的类型与其相同:

 var i int
 j := i // j 也是一个 int

不过当右边包含未指明类型的数值常量时,新变量的类型就可能是 int, float64complex128 了,这取决于常量的精度:

 i := 42           // int
 f := 3.142        // float64
 g := 0.867 + 0.5i // complex128

尝试修改示例代码中 v 的初始值,并观察它是如何影响类型的。

 package main
 ​
 import "fmt"
 ​
 const Pi = 3.14
 ​
 func main() {
   v := 42// 修改这里!
   fmt.Printf("v is of type %T\n", v)
   
   const World = "世界"
   fmt.Println("Hello", World)
   fmt.Println("Happy", Pi, "Day")
 ​
   const Truth = true
     fmt.Println("Go rules?", Truth)
   
 }
 ​

结尾分号

Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析。

例:x+y,在+后换行可以,在x后换行则会编译错误

格式化

gofmt 文件名可以格式化代码

输入/输出

print:输出操作数的默认格式,空格隔开。

println:输出操作数的默认格式,空格隔开,换行。

printf:根据格式说明符打印。

官方文档


1.2流程控制语句

for

Go 只有一种循环结构:for 循环。

基本的 for 循环由三部分组成,它们用分号隔开:

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

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。

一旦条件表达式的布尔值为 false,循环迭代就会终止。

注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。

 package main
 ​
 import "fmt"
 ​
 func main() {
   sum := 0
   for i := 0; i < 10; i++ {
     sum += i
   }
   fmt.Println(sum)    //输出:45
 }

与C语言相同,初始化语句和后置语句是可选的。

 package main
 ​
 import "fmt"
 ​
 func main() {
   sum := 1
   for ; sum < 1000; {
     sum += sum
   }
   fmt.Println(sum)    //输出:1024
 }

此时去掉分号相当于while

 for sum < 1000 {
     sum += sum
   }

无限循环

如果省略循环条件,该循环就不会结束。即for { }

if

Go的if语句与for循环类似,表达式外无需小括号( ),而大括号{ }则是必须的。

 package main
 ​
 import (
         "fmt"
         "math"
 )
 ​
 func sqrt(x float64) string {
         if x < 0 {
                 return sqrt(-x) + "i"
         }
         return fmt.Sprint(math.Sqrt(x))
 }
 ​
 func main() {
         fmt.Println(sqrt(2), sqrt(-4)) //输出:1.4142135623730951 2i
 }

for一样,if语句可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在if之内。

if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用。

 package main
 ​
 import (
         "fmt"
         "math"
 )
 ​
 func pow(x, n, lim float64) float64 {
         if v := math.Pow(x, n); v < lim {
                 return v
         } else {
                 fmt.Printf("%g >= %g\n", v, lim)
         }
         // 这里开始就不能使用 v 了
         return lim
 }
 ​
 func main() {
         fmt.Println(
                 pow(3, 2, 10),
                 pow(3, 3, 20),
         )
 }

switch

switch是编写一连串if - else语句的简便方法,类似于C。

区别:

  • 在于GO提供了每个case的break语句,除非以fallthrough语句结束,否则分支会自动终止。
  • case 无需为常量,且取值不必为整数。
 package main
 ​
 import (
         "fmt"
         "time"
 )
 ​
 func main() {
         fmt.Println("When's Saturday?")
         today := time.Now().Weekday()
         fmt.Println(today)         //today输出的星期string
         fmt.Println(today + 3)     //若执行加法则输出对应天数后的星期string
         fmt.Println(time.Saturday) //输出.后的星期string
         today += 3
         switch time.Saturday {
         case today + 0:
                 fmt.Println("Today.")
         case today + 1:
                 fmt.Println("Tomorrow.")
         case today + 2:
                 fmt.Println("In two days.")
         default:
                 fmt.Println("Too far away.")
         }
 }

没有条件的 switch

switch true一样,这种形式能将一长串if-then-else写得更加清晰。

 package main
 ​
 import (
         "fmt"
         "time"
 )
 ​
 func main() {
         t := time.Now()
         switch {
         case t.Hour() < 12:
                 fmt.Println("Good morning!")
         case t.Hour() < 17:
                 fmt.Println("Good afternoon.")
         default:
                 fmt.Println("Good evening.")
         }
 }

defer

defer语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

 package main
 ​
 import "fmt"
 ​
 func main() {
         fmt.Println("counting")
 ​
         for i := 0; i < 10; i++ {
                 defer fmt.Println(i)
         }
 ​
         fmt.Println("done")
 }

参考资料

  1. 参考书籍:《Go语言圣经》中文版 地址
  2. 格式说明符中文博客参考阅读