1. 入门
-
1.1 Hello, world
package main
import (mb
"fmt"
)
func main() {
fmt.Println("Hello, go")
}
-
1.2 fmt.Print 和 fmt.Println
- 可以传若干个参数,之间用逗号分开。
- 参数可以是字符串、数字、数学表达式等等。
- Println 比 Print 多输出一个回车。
-
1.3 fmt.Printf 格式化打印
- 类似 C 语言里的 printf,第一个参数必须是字符串
- 包含了像
%v
这样的格式化动词,值由第二个参数代替
-
1.4 fmt.Printf 对齐文本
- 在格式化动词里指定宽度,就可以对齐文本。例如,%4v,就是向左填充到足够4个宽度
- 正数,向左填充空格
- 负数,向右填充空格
package main
import (
"fmt"
)
func main() {
fmt.Printf("%-15v $%4v\n", "SpaceX", 94)
fmt.Printf("%-15v $%4v\n", "Virgin Galactic", 100)
}
2. 常量和变量
- const,用来声明常量,常量的值不可改变
- var,用来声明变量,想要使用变量必须先声明
-
2.1 同时声明多个变量
func main() {
//第一种直接声明
var distance = 56000000
var speed = 100800
//第二种,用括号
var (
distance = 56000000
speed = 100800
)
//第三种,用逗号
var distance, speed = 56000000, 100800
//常量也一样
const hoursPerDay, minutesPerHour = 24, 60
}
-
2.2 运算符
- 有加减乘除,+=/-=/*= 等,与其他语言一致
- 有++,但是只有 count++,没有++count
-
2.3 随机数
- 使用 rand 包,可以生成伪随机数
- 例如,Intn 可以返回一个指定范围的随机整数 - [0, n)
- import 的路径是“math/rand”
- rand.Seed(time.Now().UTC().UnixNano())
3. 循环与分支
-
3.1 Boolean 类型
- 某些语言(例如 JS)会把
""
字符串当作 false,其他字符串当作 true; - 但是 Go 语言不行,只有 true 是真,false 是假。
- 某些语言(例如 JS)会把
-
3.2 strings.Contains 函数
- 用于判断是否包含子字符串。
-
3.3 比较运算符
- ==、<=、<、!=、>=、>
-
3.4 逻辑运算符
- ||、&&、!
- Go 语言里同样有短路逻辑
-
3.5 switch case 命令
- 不用写 break
- 多个 case 可以用逗号隔开
- 还有 fallthrough 关键字,可以穿过执行下一个 case 的 body 部分。
func main() {
var command = "go inside"
switch command {
case "go east":
fmt.Println("You head further up the mountain.")
case "enter cave", "go inside":
fmt.Println("You find yourself in a dimly lit cavern.")
case "read sign":
fmt.Println("The sign reads 'No Minors'.")
default:
fmt.Println("Didn't quite get that.")
}
}
-
for 循环
- 带一个条件 = while 循环
- 带初始化,判断,赋值 = for 循环
4. 变量与作用域
-
4.1 作用域
- 变量被声明后即进入了作用域
- Go 里的作用域就是
{}
之间的部分 - 作用域的好处就是,可以在不同作用域声明同名变量,而不至于冲突
-
4.2 短声明
- count := 10
- 短声明可以在不能用
var
的地方,声明变量
-
4.3 package 作用域
- main 函数以外定义的变量
- 对本 package 内所有函数可见
- 不能用短声明定义 package 作用域的变量,得用
var
5 实数
-
5.1 浮点型变量
下面三个语句结果都是一样的:
days := 365.2425
var days = 365.2425
var days float64 = 365.2425
只要数字含有小数部分,那么它的类型就是float64
双精度float64
,其他语言叫做double
单精度float32
,其他语言叫做float
使用 Print
或 Println
打印浮点数时,默认尽可能多打印几位小数
如果你不想这样,那么你应该使用 Printf
函数
third := 1.0 / 3
fmt.Println(third)
fmt.Printf("%v\n", third)
fmt.Printf("%f\n", third)
fmt.Printf("%.3f\n", third)
fmt.Printf("%4.2f\n", third)
-
5.2 整数
- 8种整数类型
- int8、uint8、int16、uint16、int32、uint32、int64、uint64
- 类型与CPU架构无关
- 而 int 和 uint 是针对目标设备优化的类型
- 在树莓派 2、比较老的移动设备上,int 和 uint 都是32位的
- 在比较新的计算机上,int 和 uint 都是 64 位的
- int 和 uint 不是8种类型的别名,是另外的2种类型。
- 整数类型的最大、最小值常量:
-
math.MaxInt16
、math.MinInt64
- 而 int 和 uint,可能是 32 或者 64 位的,所以没有系统定义的最大/小值。
-
- 8种整数类型
-
5.3 非常大的数
- 如果没有为指数形式的数值指定类型的话,那么 Go 将会将它视作 float64 类型
- 对于较大的整数(超过10^18):
big
包的 big.Int - 对于任意精度的浮点类型,big.Float
- 对于分数,big.Rat
import (
"fmt"
"math/big"
)
func main() {
lightSpeed := big.NewInt(299792)
secondsPerDay := big.NewInt(86400)
fmt.Println(lightSpeed, secondsPerDay)
distance := new(big.Int)
distance.SetString("2400000000000000000000000000000000000", 10)
fmt.Println(distance)
seconds := new(big.Int)
seconds.Div(distance, lightSpeed)
days := new(big.Int)
days.Div(seconds, secondsPerDay)
fmt.Println("That is", days, "days of travel at light speed.")
}
6 字符串
-
6.1 字符串字面值
- 字符串字面值可以包括转义字符,例如
\n
- 如果你确实想得到\n而不是换行,可以使用``
- 字符串字面值可以包括转义字符,例如
-
6.2 字符,code points,runes,bytes
- Unicode 联盟为超过 100 万个字符分配了相应的数值,这个数叫做 code point
- 例如:65 代表 A,128515 代表 😃
- 为了表示这样的 unicode code point,Go 语言提供了 rune 这个类型,它是 int32 的一个类型别名
- 而 byte 是 uint8 类型的别名,目的是用于二进制数据。
- Unicode 联盟为超过 100 万个字符分配了相应的数值,这个数叫做 code point
-
6.3 打印
- 如果想打印 字符 而不是 数值,那么可以使用
%c
- 任何整数类型都可以使用
%c
打印,但是 rune 意味着该数值表示了一个字符 - 字符字面值使用单引号 '' 括起来
- 如果没指定字符类型的话,那么 Go 会推断它的类型为 rune
- 字符字面值也可以用 byte 类型
- 如果想打印 字符 而不是 数值,那么可以使用
-
6.4 Go 内置函数
- len 是 Go 语言的一个内置函数,统计的是 byte 数
- 对于多字节的文字如:西班牙语、汉语等,可能长度不对
message := "你好"
fmt.Println(len(message)) //6
- 如何支持西班牙语、俄语、汉语等?
- 把字符解码成 rune 类型,然后再进行操作。
- 使用
unicode/utf8
包,它提供可以按 rune 计算字符串长度的方法:DecodeRuneInString
fmt.Println(len(message), "bytes") //6
fmt.Println(utf8.RuneCountInString(message), "runes") //2
c, size := utf8.DecodeRuneInString(message)
fmt.Printf("First rune: %c %v bytes", c, size)
7 类型转换
-
7.1 类型不能混着用
- 拼接字符串和
Int
型是会报错的 - 整型和浮点类型也不能混着用,加减乘除是会报错的
- 类型转换如下:
- 拼接字符串和
age := 41
marsAge := float64(age)
earthDays := 365.2425
fmt.Println(int(earthDays)) //会被截断
c := string(65)
fmt.Println(c) //A
- 无符号和有符号类型间也需要转换
- 不同大小的整数类型之间也需要转换
- 想把数值转化为 string,它的值必须能转化为 code point
- strconv 包的 Itoa 函数就可以做这件事
- Itoa 是 Integer to ASCII 的意思
- 另外一种把数值转化为 string 的方式是使用 fmt.Sprintf 函数,
- 和 Printf 略类似,但是会返回一个 string
8 函数
-
8.1 声明与调用
func Intn(n int) int //func关键字 函数名(变量名 变量类型) 函数返回值
num := rand.Intn(10) // 包名.函数名(传参)
-
8.2 函数声明
- 在 Go 里,大写字母开头的函数、变量或其他标识符都会被导出,对其他包可用
- 小写字母开头的就不行。
-
8.3 多个返回值
- 多个返回值需要用括号括起来
func Atoi(s string) (i int, err error)
countdown, err := strconv.Atoi("10")
-
8.4 可变参数
func Println(a ...interface{}) (n int, err error)
-
8.5 声明新类型
- 关键字 type 可以用来声明新类型
type celsius float64
var temperature celsius = 20
- 因为仅仅是起别名,所以
celsius
还是可以和float64
一起运算 - 为什么要声明新类型?因为可以极大提高代码可读性和可靠性
-
8.6 通过方法添加行为
- 在 C#、Java 里,方法属于类
- 在 Go 里,它提供了方法,但是没提供类和对象
- Go 比其他语言的方法要灵活
- 可以将方法与同包中声明的任何类型相关联,但不可以是 int、float64 等预声明的类型进行关联
type celsius float64
type kelvin float64
func (k kelvin) celsius() celsius {
return celcius(k - 273.15)
}
- 变量调用 变量名.方法()
import "fmt"
func main() {
var k kelvin = 294.0
var c celsius
c = kelvinToCelsius(k)
c = k.celsius()
fmt.Println(c)
}
type kelvin float64
type celsius float64
func kelvinToCelsius(k kelvin) celsius {
return celsius(k - 273.15)
}
func (k kelvin) celsius() celsius {
return celsius(k - 273.15)
}
-
8.7 一等函数
- 在 Go 里,函数是一等公民。
- 可以将函数赋给变量
- 将函数作为参数传递给函数
-
8.8 声明函数类型
- 为函数声明类型有助于精简和明确调用者的代码。
type sensor func() kelvin
func measureTemperature(samples int, s func() kelvin) //可以简写为
func measureTemperature(samples int, s sensor)
-
8.9 闭包和匿名函数
- 匿名函数就是没有名字的函数,在 Go 里也称作 函数字面值(function literal)
//匿名函数
var f = func() {
fmt.Println("Dress up for the masquerade.")
}
//立即执行
func main() {
func() {
fmt.Println("Functions anonymous")
}()
}
//闭包
type kelvin float64
type sensor func() kelvin
func realSensor() kelvin {
return 0
}
func calibrate(s sensor, offset kelvin) sensor {
return func() kelvin {
return s() + offset
}
}
func main() {
sensor := calibrate(realSensor, 5)
fmt.Println(sensor())
}
9 数组
-
9.1 访问数组元素
var planets [8]string
planets[0] = "Mercury"
planets[1] = "Venus"
planets[2] = "Earth"
earth := planets[2]
fmt.Println(earth)
fmt.Println(len(planets))
-
9.2 数组越界
- Go 编译器在检测到对越界元素的访问时会报错
- 如果 Go 编译器在编译时未能发现越界错误,那么程序在运行时会出现 panic
-
9.3 使用复合字面值初始化数组
- 复合字面值(composite literal)是一种给复合类型初始化的紧凑语法
//字面值初始化
dwarfs := [5]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
//自动计算长度
dwarfs := [...]string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
//range 遍历数组
for i, dwarf := range dwarfs {
fmt.Println(i, dwarf)
}
-
9.4 数组的复制
- 无论数组赋值给新的变量还是将它传递给函数,都会产生一个完整的数组副本
- 数组也是一种值,函数通过值传递接受参数。所以数组作为函数的参数,非常低效
- 数组的长度也是数组类型的一部分
- 尝试将长度不符的数组作为参数传递,将会报错
- 所以:
函数一般使用 slice 而不是数组作为参数
-
9.5 数组的数组
var board [8][8]string
board[0][0] = "r"
board[0][7] = "r"
for column := range board[1] {
board[1][column] = "p"
}
fmt.Print(board)
10 Slice 切片
-
10.1 Slice 指向数组的窗口
- 切分数组不会导致数组被修改,它只是创建了指向数组的一个窗口或视图
这种视图就是 slice 类型
- 切分数组不会导致数组被修改,它只是创建了指向数组的一个窗口或视图
planets[0:4] //前4个元素
planets[4:6] //4-5 两个元素
-
10.2 Slice 的默认索引
- 忽略掉 slice 的起始索引,表示从数组的起始位置进行切分
- 忽略掉 slice 的结束索引,相当于使用数组的长度作为结束索引
- slice 索引不能是负数
- 如果同时省略掉起始和结束索引,那就是包含数组所有元素的一个 slice
- 切分数组的 slice 也可以用于切分字符串(索引代表字节数而不是
rune
)
-
10.3 Slice 的复合字面值
- Go 里面很多函数都倾向于使用 slice 而不是数组作为参数
- 想要获得与底层数组相同元素的 slice,那么可以使用 [:] 进行切分
- 切分数组并不是创建 slice 唯一的方法,可以直接声明 slice:例如
[]string
-
10.4 带方法的切片
- 在 Go 里,可以将 slice 或数组作为底层类型,然后绑定其它方法
type StringSlice []string
func (p StringSlice) Sort() {
//function body
}
11 更大的 Slice
-
11.1 append 函数
- append 函数也是内置函数,可以将元素添加到 slice 里面
dwarfs := []string{"Ceres", "Pluto", "Haumea", "Makemake", "Eris"}
dwarfs = append(dwarfs, "Orcus")
dwarfs = append(dwarfs, "Salacia", "Quaoar", "Sedna")
fmt.Println(dwarfs)