1. 包
1. main 函数和 main 包
所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。
2. 导入包和导出名字
- 按照约定,包名与导入路径的最后一个元素一致。比如使用 “math/rand” 包时,用 rand 来调用
- 导入可以直接导入或者使用圆括号分组导入,圆括号内每个包占一行
- 任何以大写字母开头的变量或者函数,都是被导出的名字,其它包只能访问被导出的函数和变量,任何未导出的名字在该包外均无法访问
- 导入了包,却不在代码中使用它,在 Go 中是非法的,此时编译器是会报错
1
2
3
4
5
6
7
8
9
10
11
12package main
import "math/rand" // 直接导入
import ( // 分组导入
"fmt"
"math"
)
func main() {
fmt.Println(math.pi) // 报错 :./prog.go:9:14: cannot refer to unexported name math.pi
// ./prog.go:9:14: undefined: math.pi
} // 正确的是 math.Pi
3. 自定义包
属于某一个包的源文件都应该放置于一个单独命名的文件夹里。按照 Go 的惯例,应该用包名命名该文件夹。
4. 初始化函数
所有包都可以包含一个 init 函数。
- 该函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它
- 该函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性
包的初始化顺序:
- 首先初始化被导入的包,按照顺序全部导入
- 然后初始化包级别(Package Level)的变量
- 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。
- 调用 main 函数
5. 特殊操作
- 别名:可以重新命名使用包时候的名字
- 空白操作:由于导入不使用是非法的,这种情况就可以使用空白标识符
_
屏蔽报错。 - 点操作:表示导入包之后可以省略包前缀使用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14package main
import (
sql "database/sql" // 可以使用 sql 来代替包前缀
_ "geometry/rectangle" // 需要初始化,但不使用
. "fmt" // 可以直接调用
)
var _ = rectangle.Area // 错误屏蔽器
func main() {
sql.open()
Println("hello world")
}
2. 变量
1. 声明
- 通用语法:
var 变量名 变量类型
,语句用于声明一个变量列表,类型在最后,可出现在包和函数级别。 - 变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型。
- 在函数中,简短声明语句
:=
可在类型明确的地方代替var
声明,不能在函数外使用。:=
操作符左边的所有变量必须都有初始值:=
操作符的左边至少有一个变量是尚未声明
const
一般用来声明常量,常量不能用:=
语法声明。- 常量不允许重新赋值
iota
是常量计数器,只能在常量的表达式中使用,在const
关键字出现时将被重置为0(const
内部的第一行之前),const
中每新增一行常量声明将使iota
计数一次1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39// 声明单个变量
var aa int // 直接声明,不赋初始值则其为零值
var bb = 100 // 类型推断
// 声明多个变量
var i, j int = 1, 2 // 声明多个变量并初始化
var x, y = 100, "hello"
var (
vv int = 10
jj string = "world"
)
// 声明常量
const name = "gp" // 单个常量
const ( // 可以用来定义枚举
a = iota // 0
b // 1
c // 2
)
const (
Apple, Banana = iota + 1, iota + 2 // 0+1=1, 0+2=2
Cherimoya, Durian // 1+1=2, 1+2=3
Elderberry, Fig // 2+1=3, 2+2=4
Blackberry, Blueberry // 4+1=5, 4+2=6
Cherry, Lemon = iota*1,iota*10 // 5*1=5, 5*10=50
Pear, Peach // 6*1=6, 6*10=60
)
func main() {
var c, python = true, false
fmt.Println(a, b, i, j, x, y, vv, jj, c, python)
// 简短声明
name, age := "naveen" // 报错 age 没有被赋值。要求 := 操作符左边的所有变量必须都有初始值
a1, b1 := 20, 30 // 声明变量a和b
b1, c1 := 40, 50 // b已经声明,但c尚未声明。要求 := 操作符的左边至少有一个变量是尚未声明
a1, b1 := 60, 70 // 报错,没有尚未声明的变量
}
2. 基本类型
bool
string
- 数字类型
- 有符号整型:
int8, int16, int32, int64, int
int8
:8 位有符号整型,-128~127int16
:16 位有符号整型,-32768~32767int32
:32 位有符号整型,-2147483648~2147483647int64
:64 位有符号整型,-9223372036854775808~9223372036854775807int
:无特殊需求,通常使用它,根据不同的底层平台,表示 32 或 64 位有符号整型
- 无符号整型:
uint8, uint16, uint32, uint64, uint
uint8
:8 位无符号整型,0~255uint16
:16 位无符号整型,0~65535uint32
:32 位无符号整型,0~4294967295uint64
:64 位无符号整型,0~18446744073709551615uint
:无特殊需求,通常使用它,根据不同的底层平台,表示 32 或 64 位无符号整型
- 浮点型:
float32, float64
,32 位浮点数和 64 位浮点数 - 复数类型:
complex64, complex128
,实部和虚部都是 float32 和 float64 类型的的复数 byte
:uint8 的别名。rune
:int32 的别名,表示一个 Unicode 码点
- 有符号整型:
3. 注
3.1. 零值(Zero Value)
没有明确初始值的变量声明会被赋予它们的零值。数值类型为 0,布尔类型为 false,字符串为 “”(空字符串)。
3.2. 同类型转换
golang 没有自动类型提升或类型转换表达式,可以用 T(v)
将值 v 转换为类型 T,主要在同一基础类型不同精度范围转换时使用。
3. 函数
1. 语法
- 通用语法:
func 函数名(参数 类型) 返回值类型 { }
- 无参无返回值可省略参数列表和返回值
func 函数名() { }
- 函数有多个返回值,那么返回值必须用
()
括起来 _
在 Go 中被用作空白符,可以用作表示任何类型的任何值- 可变参数函数:如果函数最后一个参数被记作
...T
,这时函数可以接受任意个T
类型参数作为最后一个参数。
2. 函数参数
- 类型在参数之后
x int, y string
- 多个同类参数,可只留最后一个。当连续两个或多个函数的已命名形参类型相同时,可只在最后写一个类型,即
x int, y int
等于x, y int
。
3. 函数特点
3.1. 返回值可以是任意数量
任意数量返回值,可分为匿名得和有形参名的,当返回值被命名,它们会被视作定义在函数顶部的局部变量,一般仅用在短函数中,否则会影响阅读性
1 | // 无形参名 |
3.2. defer
含有 defer
语句的函数,会在该函数将要返回之前,调用该语句后定义的函数。类似于 java 的 finally 内的语句。其又称为延迟函数。
- 并非在调用延迟函数的时候才确定实参,而是当执行 defer 语句的时候,就会对延迟函数的实参进行求值
- 当一个函数内多次调用 defer 时,Go 会把 defer 调用放入到一个栈中,随后按照后进先出(Last In First Out, LIFO)的顺序执行
1
2
3
4
5
6
7
8
9
10func printA(a int) {
fmt.Println("value of a in deferred function", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("value of a before deferred function call", a) // value of a before deferred function call 10
// value of a in deferred function 5
}
4. 头等函数
头等函数机制,指该语言可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。
4.1. 匿名函数
两种调用方式:
- 把函数赋值给变量
- 在定义之后使用
()
立即调用,因此只能使用一次1
2
3
4
5
6
7
8
9
10func main() {
a := func() { // 把函数赋值给变量
fmt.Println("hello world first class function")
}
a()
func(n string) { // 定义之后立即调用
fmt.Println("Welcome", n)
}("Gophers")
}
4.2. 自定义的函数类型
类似自定义结构体,可以定义自己的函数类型
1 | type add func(a int, b int) int // 自定义函数类型 add |
4.3. 高阶函数
高阶函数可以定义为,满足下列条件之一的函数:
- 接收一个或多个函数作为参数
- 返回值是一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 函数作为参数
func simple(a func(a, b int) int) {
fmt.Println(a(60, 7))
}
func main() {
f := func(a, b int) int {
return a + b
}
simple(f) // 将定义的匿名函数 f 作为参数传入
}
// 函数作为返回值
func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}
func main() {
s := simple() // 调用 simple 函数,并把返回函数赋值给了 s
fmt.Println(s(60, 7))
}
4.4. 闭包
闭包(Closure)是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。
1 | func appendStr() func(string) string { |