Go的语句是可以省略;
结束符的,且每行尽可能只写一句代码,这是Go语言的编程范式,因此应遵循规范,不要加分号。分号只在一种情况下是必须的,当一行写了多个代码语句,则每个语句必须使用分号分隔。
Go语言有两种注释方式,基本来自于我们熟悉的C或Java中的注释方式
// 单行注释
/*
* 多行注释
*/
Go与以往的C系列编程语言(Java、C#、C++等)最大的一个不同,就是其声明变量时,类型定义在变量名之后,对于C系列的程序员而言,开始的时候会非常别扭,与我们以往的编程习惯是反的,不过熟悉之后也就习惯成自然了。
就我个人感觉,我认为Go语言的语法是一个大杂烩,主要在C语言的基础上,吸收融合了Python、Java的某些语法特点,并做了一些创新,当然,也能看到一点JavaScript的影子。因此,拥有C、Java、Python技术背景的人,总能从Go中找到熟悉的感觉。以上三种编程语言中,掌握任意一种或两种,那么学习Go语言都会轻而易举,甚至可以说易如反掌。
使用关键字var,并将类型放在变量名之后
var v1 int //整型变量
var v2 string //字符串变量
var v3 [10]int // 数组
var v4 []int // 切片
var v6 *int // 指针
var v7 map[string]int // map,key为string类型,value为int类型
// 声明结构体变量v5
var v5 struct {
f int
}
// 声明函数签名
var v8 func(a int) int
我们知道在C语言中,变量声明后必须初始化再使用,否则变量会带有随机的值。Go语言的编译器则会在声明变量时对变量做零值初始化,这一点正解决了C语言的缺陷
package main
import "fmt"
func main() {
var v1 int
var v2 string
var v3 [10]int
var v4 []int
fmt.Printf("%d,%s,%d,%p\n",v1,v2,v3,v4)
}
打印结果:
0,,[0 0 0 0 0 0 0 0 0 0],0x0
字符串的零值是一个空字符串""
,而数组则是已经开辟了空间,且每个元素的值都是零值,指针的零值是则0
,这里v4
变量的类型是切片,实际上就是一个指针。
同时声明多个变量
// 不同类型
var (
v1 int
v2 string
)
// 相同类型
var i, j, k int
使用
:=
操作符,可以做短变量声明。
var v1 int = 10 // 声明变量的同时初始化
var v2 = 10 // 让编译器自动推导类型
v3 := 10 // 省略关键字var和类型,让其自动推导类型
Go语言的语法吸收了很多动态语言的特点,例如我们在Python中声明变量,不需要使用任何关键字和类型定义,而在Go中也提供了类似的机制,这就是使用:=
操作符做短变量声明。如上例v3 := 10
,编译器会根据所赋的值自动推导出v3
变量的类型,这是Go中变量声明最简洁的表达语法。
// 先声明,再赋值
var i, j, k int
i, j, k = 2, 3, 4
// 声明的同时赋值,注意使用":="操作符
l, m, n := 7, 8, 9
使用const
关键字声明常量,声明常量时也可以省略类型定义,如果想要明确具体类型,提高代码可读性,也可以显式指定类型
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 浮点常量
const (
size int64 = 1024
eof = -1 // 整型常量
)
// u = 0.0, v = 3.0,常量的多重赋值
const u, v float32 = 0, 3
// a = 3, b = 4, c = "foo", 无类型整型和字符串常量
const a, b, c = 3, 4, "foo"
// 常量定义的右值可以是一个编译期运算的常量表达式
const mask = 1 << 3
枚举指一系列相关的常量,比如下面关于一个星期中每天的定义。同Go语言的其他符号一样,以大写字母开头的常量在包外可见
// Go语言中实际上是用整型常量替代枚举
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
我们可以观察一下结果
func main() {
fmt.Printf("%d,%d,%d,%d\n",Sunday,Monday,Tuesday,Wednesday)
}
打印结果:
0,1,2,3
其值是从0开始的自然数列
Go语言中有两种字符类型,分别是byte
和rune
。byte
类型就相当于C语言中的char
,表示的是单字节字符,如ASCII码,如果是中文这种,单字节是无法表示的,就需要多个字节一起来表示。而rune
类型则是专门用来表示Unicode字符的,它相当于C语言中的所谓宽字符,即wchar
,单个rune
就能表示一个中文。具体区别在以下字符串一小结说明。
Go语言的作者也是UTF-8
编码的发明者,毫无疑问,Go语言的源代码默认的编码方式就是UTF-8
,如同Python3与Python2的区别一样,我们可以在源代码中直接书写中文字面量。
// 声明并初始化字符串变量
var str1 string = "这是中文"
var str2 string = "are you ok?"
// 字符串拼接
str3 := str1 + str2
Go语言吸收了Python中字符串切片的语法,使得对字符串的截取操作变得异常赏心悦目
var str1 string = "believe in whatever my love has said to me"
// 要特别注意,字符串切片返回的依然是string类型
fmt.Println(str1[0:5])
切片的具体语法请参看后面切片小节,需要注意的是,不要对中文字符串直接切片操作,会返回乱码。这是因为Go语言默认编码是UTF-8
,这是一种多字节编码,通常它使用3个byte
来表示一个中文,而字符串切片切的就是byte
,中文直接切片破坏了每三个byte
一组的完整性。如需对中文字符串切片,必须先强制转换为[]rune
类型
func main() {
var str1 string = "你看这碗又大又圆,你看这面又长又宽"
runeStr := []rune(str1)
// 转换为[]rune类型后才能切片操作,s
// 且操作后还需再次转为string类型才能打印
fmt.Println(string(runeStr[0:5]))
}
Go语言中还支持一种反引号括起来的字符串,该字符串可以包含多行和特殊字符,双引号括起来的字符串遇特殊字符需转义,用反引号则不需要
func main() {
str1 := `golang
java`
var str2 string = `string\n\t\r`
fmt.Println(str1)
fmt.Println(str2)
}
实际上就等同于Python中的三引号括起来的字符串,这是一种原始字符串,它会包含字面量中的格式,即换行和特殊字符。
数组是一个具有
相同数据类型
的元素组成的固定长度
的有序集合
。数组中包含的每个数据称为数组元素,包含的元素个数称为数组长度。
在Go语言中,数组是值类型,长度是类型的组成部分,也就是说[10]int
和[20]int
是完全不同的两种数组类型。
package main
import "fmt"
func main() {
// 数组声明
var a [5]int
fmt.Println("emp:", a)
// 使用数组索引(下标)操作元素
a[4] = 100
fmt.Println("set:", a)
fmt.Println("get:", a[4])
// 使用内置的len函数获取数组的长度
fmt.Println("len:", len(a))
// 声明并初始化一个数组的写法
b := [5]int{1, 2, 3, 4, 5}
fmt.Println("dcl:", b)
// 数组的元素是一个数组,声明二维数组
var twoArr [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoArr[i][j] = i + j
}
}
fmt.Println("2d: ", twoArr)
}
输出结果
emp: [0 0 0 0 0]
set: [0 0 0 0 100]
get: 100
len: 5
dcl: [1 2 3 4 5]
2d: [[0 1 2] [1 2 3]]
需要注意,数组作为参数传递时,是值传递,这一点与C语言有很多不同,所谓值传递即传递的是原始数组的一份复制品,操作复制品,不会改变原始数组,除非是传入数组的指针。
package main
import "fmt"
func modify(array [5]int) {
array[0] = 10 // 修改数组的第一个元素
fmt.Println("In modify(), array values:", array)
}
func main() {
// 定义并初始化一个数组
array := [5]int{1,2,3,4,5}
// 将数组作为函数参数传递
modify(array)
fmt.Println("In main(), array values:", array)
}
打印结果:
In modify(), array values: [10 2 3 4 5]
In main(), array values: [1 2 3 4 5]
数组的长度在定义之后无法再次修改,且数组是值类型,每次传递都将产生一份副本,这些是数组的缺陷,而切片正好弥补了数组的这些不足之处。
实际上,Go所谓的切片,就是一个动态数组,类似与Java、Python中的List
,可以自动增长。
package main
import "fmt"
func main() {
// 定义一个数组
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 基于数组创建一个数组切片
var mySlice []int = myArray[:5]
fmt.Println("Elements of myArray: ")
for _, v := range myArray {
fmt.Print(v, " ")
}
fmt.Println("\nElements of mySlice: ")
for _, v := range mySlice {
fmt.Print(v, " ")
}
fmt.Println()
}
输出结果:
Elements of myArray:
1 2 3 4 5 6 7 8 9 10
Elements of mySlice:
1 2 3 4 5
切片与数组的区分
大家是不是像我一样,数组、切片,傻傻的分不清楚?其实我们仔细观察一下,数组和切片有一个明显的不同
var myArray [10]int
var mySlice []int
看到了吧,数组的中括号中有数字指明数组长度,而切片是没有指定数字的。这一点又和C语言不同,Go语言中,声明数组是不能省略数组中括号中的内容的,如果省略,则声明的是切片,不是数组。大家别被C语言的语法给弄迷糊了,该分清还是得分清。
Go语言可以使用myArray[first:last]
这样的方式来基于数组生成一个数组切片,当[]
中的first或者last为0时,可以省略。其中索引是从0开始的,且包含first索引对应的元素,不包含last索引对应的元素。更灵活的用法如下
// 基于myArray的所有元素创建数组切片
mySlice = myArray[:]
// 基于myArray的前5个元素创建数组切片
mySlice = myArray[:5]
// 基于从第5个元素开始的所有元素创建数组切片
mySlice = myArray[5:]
// 基于从第2个元素到第5个元素之间的所有元素创建数组切片
mySlice = myArray[1:5]
有过Python经验的小伙伴是不是非常熟悉,这就和Python的切片语法如出一辙。
使用内置函数make()可以更灵活地创建数组切片。
// 创建一个初始元素个数为5的数组切片,元素初始值为0
mySlice1 := make([]int, 5)
// 创建一个初始元素个数为5的数组切片,元素初始值为0,
// 并预留10个元素的存储空间
mySlice2 := make([]int, 5, 10)
// 直接创建并初始化包含5个元素的数组切片
mySlice3 := []int{1, 2, 3, 4, 5}
数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是指针。
数组切片的数据结构抽象为以下3个变量
实际上看过我的 C语言专栏 的朋友,应该非常熟悉了,这不就是 数据结构 那一篇的基于数组的线性表实现吗,看过的小伙伴应该已经自己实现过了,现在再来看Go语言切片,是不是So easy 呢?
操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用len()数获取元素个数,并支持使用range关键字来快速遍历所有元素。
遍历
for i := 0; i <len(mySlice); i++ {
fmt.Println("mySlice[", i, "] =", mySlice[i])
}
//range表达式有两个返回值,第一个是索引,第二个是元素值
for i, v := range mySlice {
fmt.Println("mySlice[", i, "] =", v)
}
动态增减元素
可动态增减元素是数组切片比数组更为强大的地方。与数组相比,数组切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值,这就好比篮子里苹果的个数和篮子的大小一样。合理地设置存储能力的值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序性能。
分别使用len()
函数和cap()
函数获得切片中元素的个数和切片存储能力的大小
package main
import "fmt"
func main() {
mySlice := make([]int, 5, 10)
fmt.Println("len(mySlice):", len(mySlice))
fmt.Println("cap(mySlice):", cap(mySlice))
}
输出结果:
len(mySlice): 5
cap(mySlice): 10
使用append()
函数给切片添加元素。需要注意append()
的第二个参数是一个不定参,可以按需求添加若干个元素。当追加的内容长度超过当前已分配的存储空间(即cap()的值),数组切片会自动扩容。
// 添加一个元素
mySlice = append(mySlice, 4)
// 一次添加三个元素
mySlice = append(mySlice, 1, 2, 3)
mySlice2 := []int{8, 9, 10}
// 给mySlice切片的后面追加另一个数组切片中的所有元素,
// 因为添加的是一个切片,所以三个点不可省略
mySlice = append(mySlice, mySlice2...)
所谓映射,也就是键值对数据结构。它是Python中的字典,Java中的HashMap
,其他语言中也称为关联数组。Go的映射底层是通过Hash表实现的,因此查询性能高效。
func main() {
// 声明一个映射变量
var mapVar1 map[string] int
// 使用make函数创建映射
mapVar1 = make(map[string] int)
//初始化映射中的键值
mapVar1["age"] = 18
mapVar1["id"] = 11
// 使用自动推导声明并创建映射
mapVar2 := make(map[string] int)
// 声明、创建并初始化映射
mapVar3 := map[string] int{
"age":20,
"num":1001
}
// 映射的增删改查操作
// 1. 增加元素
mapVar2["resId"] = 126
// 2. 删除元素,需要指定键
delete(mapVar1, "age")
// 3. 修改元素
mapVar3["num"] = 12
// 4. 查询元素 会返回两个参数,第二个是布尔类型,表示查询的键是否存在
value, ok := mapVar1["id"]
if ok {
// 查询的键存在,成功查询到对应的值
}
fmt.Printf("%d,%d",mapVar2,mapVar3)
}
在Go语言中强制规定,声明的变量必须使用,否则无法通过编译。因此我们在上述代码最后加入fmt.Printf("%d,%d",mapVar2,mapVar3)
,使用一下两个变量,否则无法编译。
在 Go 中条件语句不需要圆括号,但是需要加上花括号。另外Go语言里没有三目运算符,只能使用完整的if条件语句,这一点比较坑爹!
package main
import "fmt"
func main() {
// 基本分支
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
// 只有if条件
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
// if条件可以包含一个初始化表达式,
// 这个表达式中的变量是该条件判断结构中的局部变量
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
Go语言中的switch
比C和Java都要强大得多,甚至可以完全替代if
逻辑分支
package main
import "fmt"
import "time"
func main() {
// 基本使用,类似C与Java,但不需要break去结束,也可以省略default
i := 2
fmt.Print("write ", i, " as ")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
// 使用逗号在case中分开多个条件。最后可以添加default语句,
// 当上面的case都没有满足的时候执行default逻辑块
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("it's the weekend")
default:
fmt.Println("it's a weekday")
}
// 当switch后面没有跟表达式的时候,功能和if/else相同
// 且case后面的表达式不一定是常量,可以是逻辑表达式
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("it's before noon")
default:
fmt.Println("it's after noon")
}
// 可以用来比较类型而非值,用来发现一个接口值的类型
switch t := i.(type) {
case bool:
fmt.Println("I'm a bool")
case int:
fmt.Println("I'm an int")
default:
fmt.Printf("Don't know type %T\n", t)
}
}
Go语言继承了C中的goto语法,在C语言中,常常被告诫不要使用goto
语法,甚至被一些教材视为洪水猛兽。实际上,如果goto
语法被合理运用,可以大大简化语法表达,不过仍然建议要慎用goto
语法,千万不可滥用
goto
关键字需要与一个标签结合起来使用,表示跳转到指定的标签处
func main() {
var i int = -1
fmt.Println("------ 1 -----")
if i < 0{
goto end
}else{
goto lab1
}
fmt.Println("------ 2 -----")
lab1:
fmt.Println("------ 3 -----")
end:
fmt.Println("------ end -----")
}
如上例,条件满足时,直接跳转到end
标签处,打印结果
------ 1 -----
------ end -----
goto
语法主要用于多层循环嵌套时,直接跳出所有的循环,或者做返回值的异常处理,根据不同的返回值,直接跳转到对应的异常处理代码,跳过中间的大段代码。
for
是 Go 中唯一的循环结构。for
循环有三个基本使用方式
package main
import "fmt"
func main() {
//单个循环条件,类似于C、Java中的while循环
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
//经典for循环
for j := 7; j <= 9; j++ {
fmt.Println(j)
}
//死循环
for {
fmt.Println("loop")
break
}
for n := 0; n <= 5; n++ {
if n%2 == 0 {
// 循环控制,可以使用continue或break
continue
}
fmt.Println(n)
}
}
for
循环配合range
函数,可实现对map
的遍历
maps := make(map(string)string)
for k,v := range maps{
fmt.Printf("key=%s,value=%s\n",k,v)
}
函数的定义格式为:
关键字func + 函数名 + 参数列表 + 返回值类型 + 函数体 + 返回语句
当函数没有返回值时,则省略返回值声明
package main
import "fmt"
// 返回值类型需写在参数列表与函数体花括号之间
func plus(a int, b int) int {
return a + b
}
// 相同类型参数,可以简化,只需声明一次类型
func plusPlus(a, b, c int) int {
return a + b + c
}
func main() {
res := plus(1, 2)
fmt.Println("1+2 =", res)
res = plusPlus(1, 2, 3)
fmt.Println("1+2+3 =", res)
}
与C语言相比,Go语言函数的特点是可以返回多个值,这一点与Python函数的多值返回是一样的。
package main
import "fmt"
func vals() (int, int) {
return 3, 7
}
func main() {
a, b := vals()
fmt.Println(a)
fmt.Println(b)
// 使用"_"操作符,可以丢弃多返回值中不需要的返回值
_, c := vals()
fmt.Println(c)
}
Go语言吸收了Python的语法特点,在多返回值中,可以使用_
操作符丢弃不需要的。
所谓不定参,是指函数传入的参数个数是不确定的。最典型的不定参函数是Printf
,我们可以传入任意数量的参数。
// 定义一个接收不定参的函数
func fun1(args ...int) {
// 使用for循环获取不定参数
for _, arg := range args {
fmt.Println(arg)
}
}
func main() {
fun1(1,2,3,4,5)
fun1(18)
}
形如...int
格式的类型是作为函数的不定参数类型存在的,它表明不定参数的类型都是int
型,当函数中还有其他参数时,不定参必须放到参数列表的最后。
不定参的传递
如果我们外层的函数只是一个包装,不定参需要传递给内部的调用的函数,则可如下传递
func fun1(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
func fun2(args ...int){
// do something
// 传递不定参
fun1(args...)
}
所谓匿名函数,是指没有函数名的函数。在Go语言中,函数也是一种类型,函数也可以作为一种参数进行传递,从一个函数传入另一个函数,如同C语言中的函数指针或其他语言的函数式编程。与C语言不同的是,Go的匿名函数可以随时在另一个函数中定义
func main() {
// 定义一个匿名函数,并赋值给变量f1
f1 := func(a,b int)int{
return a + b
}
// 通过变量调用匿名函数
fmt.Println(f1(1,5))
}
与普通函数相比,匿名函数只是去除了名字,其他没有区别。除了匿名函数,实际上普通函数也可以赋值给一个变量用于传递
func fun1(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
func main() {
f1 := fun1;
f1(1,2,3)
}
通常人们直接将匿名函数理解为闭包,这样理解也不算错,但其实是不准确的,真正的闭包是指一个匿名函数引用了外部变量。匿名函数我们已经知道了,那什么叫对外部变量的引用呢?
// 定义一个计数器函数,它的返回值也是一个函数
func counter() (func()int) {
var i int = 0
// 匿名函数引用了一个外部变量i
return func()int{
i++
return i
}
}
func main() {
// 创建一个计数器
count := counter()
// 调用计数器,每次调用都会加1
fmt.Println(count())
fmt.Println(count())
fmt.Println(count())
}
打印结果:
1
2
3
以上counter
函数中的匿名函数才是一个真正的闭包,因为它引用了相对于自己的外部变量i
。闭包概念的关键就是对外部变量的引用,只要这个闭包还在使用,那么闭包所引用的外部变量就不会被释放。因此闭包的使用应当小心,滥用闭包易造成内存泄露。
以上闭包的例子实际上已经演示了函数作为返回值传递,函数作为参数也大致相同,需要注意的是函数类型该如何声明。
// 定义一个函数,它的第一个形参也是一个函数
func f3(add func(int,int)int,a,b int)int{
return add(a,b)
}
func main() {
// 定义一个匿名函数,只要函数签名匹配,就能传递给形参
f := func(x,y int)int{
return x + y
}
result := f3(f,10,8)
fmt.Println(result)
}
一个函数的函数签名就是它的类型,以上代码看起来不是很清晰,而且函数签名写起来又很长不简洁,还容易错,这时候我们就需要借用type
关键字,将函数签名定义为一种新的类型,这等同于C语言中的typedef
// 将函数签名定义成一种新类型 Add
type Add func(int,int)int
// 声明形参时,直接使用这种新的类型,而不用写很长的函数签名
func f3(add Add,a,b int)int{
return add(a,b)
}
为了适应现代的大型程序开发,现代高级编程语言都有包的概念,Go中的包与Python、Java中的包概念相似。简单说,包就是一个分组,或者说是一个文件夹,但它也是一个命名空间。之前在C语言专栏的文章中提过,C语言有一个重大缺陷,就是没有命名空间的概念,函数同名,会造成命名冲突。Go语言的包机制则不存在该问题。
在当前目录下创建一个test
目录,在该目录中创建t1.go
源文件
// 使用package声明一个包名叫test
package test
// 函数名首字母必须大写,否则包外无法访问
func Add(a ,b int)int{
return a+b
}
func Sub(a ,b int)int{
return a-b
}
在hello.go
中调用
package main
import "fmt"
import "hello/test"
func main() {
// 需使用包名调用包中函数
test.Add(1,2)
fmt.Printf("hello,world!\n")
}
文件结构
hello
│─go.mod
│─hello.go
│
└─test
t1.go
package
用于命名包,import
用于导入包。导入包时,注意路径,这里以当前工程的相对路径开始。当前工程文件夹是hello,"hello/test"
则表示当前工程下的test
文件夹。在命名包名时,应与包的文件夹名一致。
// 一个一个导入
import "fmt"
import "hello/test"
// 在一个括号内导入,推荐
import (
"fmt"
"hello/test"
)
import (
"fmt"
// 导入包的同时给包起别名
tt "hello/test"
)
func main() {
// 使用别名调用,当包名过长时很有用
tt.Add(1,2)
}
有时候使用包名或包的别名去调用函数,都显得有点麻烦,实际上Go还提供了另一种导入方式,无需使用别名调用
import (
"fmt"
// 导入包时在前面加一个. 符号
. "hello/test"
)
func main() {
// 无需包名直接调用函数
Add(1,2)
fmt.Printf("hello,world!\n")
}
以上这种使用.
导包的方式不推荐,极易造成导包冲突,当我导入很多包时,很可能会存在多个包中有同名函数的情况。起别名是比较推荐的导包方式,特别是导入同名包时,可以通过起别名来区别。
最后还有一种匿名导包方式
import (
"fmt"
// 导入包名的前面加下划线 _
_ "hello/test"
)
匿名导包表示只导入,不使用。有人肯定会奇怪了,不使用干嘛要导入呢?Go语言强制规定,导入的包必须使用,否则无法通过编译。有时候我们只需要加载一个包,但是不调用包中的函数,这时候不匿名导包的话,就不能编译了。
在Go语言中,每个包都有一个初始化函数init
,该函数不需要我们手动调用,它只在包加载的时候调用。上面说的匿名导包也就是为了调用该函数做一些初始化工作。
修改上面的t1.go
,实现一下init
函数
package test
import "fmt"
func init(){
fmt.Println("test init run")
}
只在hello.go
中匿名导包
package main
import (
// 导入包的同时给包起别名
_ "hello/test"
)
func main() {
}
打印结果:
test init run
Go语言也是以一个main
函数作为入口函数,但是Go语言强制规定,main
函数必须在main
包中,因此我们的入口源文件必须声明为main
包。
那么这里就有一个问题,在main
包的初始化函数init
和main
函数谁先执行呢?
package main
import "fmt"
func init(){
fmt.Println("main init run")
}
func main() {
fmt.Println("main main run")
}
其实我们可以推测一下,init
函数作为初始化函数,理论上应该比别的函数都早,哪怕是main
函数。
看一下打印结果,事实证明了我们的猜想。
main init run
main main run