Golang速成
golang里的main函数必须要导入package main
导包 1 2 3 4 import ( "fmt" "time" )
函数 函数的{ 一定是 和函数名在同一行的,否则编译错误
golang中的表达式,加”;”,和不加 都可以,建议是不加
1 2 3 4 5 func () { fmt.println ("hello Go!" ) time.sleep(1 *time.Second) }
变量的声明方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () {var a int fmt.Println("a=" ,a)fmt.Printf("type ofa=&T\n" ,a) var bb string ="abcd" fmt.Printf("bb =%s,type of bb = &T\n" , bb, bb) fmt.Println("c=" ,c)fmt.Printf("type of c = &T\n" , c) var cc="abcd" fmt.Printf("cc=%s,type of cc=%T\n" ,cc,cc) f :="abcd" fmt.Println("f=”,f) fmt.Printf(" type of f=&T\n",f) }
全局变量的声明 1 2 3 4 var gA int = 100 var gB=200
多变量 声明 1 2 3 4 5 6 7 8 9 10 var xx,yy int =100 ,200 fmt.Println"xx=" ,xx,",yy =" ,yy) var kk,ll= 100 ,"Aceld" fmt.Println("kk=" ,kk,",l=" ,ll) fmt.Println( "vv=" ,,",jj=" ,jj )
常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
1 const identifier [type ] = value
你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
常量还可以用作枚举:
1 2 3 4 5 const ( Unknown = 0 Female = 1 Male = 2 )
数字 0、1 和 2 分别代表未知性别、女性和男性。
常量可以用len(), cap(), unsafe.Sizeof()常量计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "unsafe" const ( a = "abc" b = len (a) c = unsafe.Sizeof(a) ) func main () { println (a, b, c) }
输出结果为:abc, 3, 16
unsafe.Sizeof(a)输出的结果是16 。
字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
函数 Go 函数可以返回多个值,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func swap (x, y string ) (string , string ) { return y, x } func main () { a, b := swap("Mahesh" , "Kumar" ) fmt.Println(a, b) }
init函数与import 首先我们看一个例子:init函数:
init 函数可在package main中,可在其他package中,可在同一个package中出现多次。
main函数
main 函数只能在package main中。
执行顺序
golang里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。
如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
Lib1.go
1 2 3 4 5 6 7 package InitLib1import "fmt" func init () { fmt.Println("lib1" ) }
Lib2.go
1 2 3 4 5 6 7 package InitLib2import "fmt" func init () { fmt.Println("lib2" ) }
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" _ "GolangTraining/InitLib1" _ "GolangTraining/InitLib2" ) func init () { fmt.Println("libmain init" ) } func main () { fmt.Println("libmian main" ) }
输出:
1 2 3 4 lib1 lib2 libmain init libmian main
匿名导包 import_”fmt“ 给fmt包起一个别名,匿名,无法使用当前包的方法,但是会执行当前的包内部的init()方法
import aa “fmt” 给fmt包起一个别名,aa,aa.Println()来直接调用。
import . “fmt” 将当前fmt包中的全部方法,导入到当前本包的作用中,fmt包中的全部的方法可以直接使用API来调用,不需要
函数参数 函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数:
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
以下定义了 swap() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 func swap (x, y int ) int { var temp int temp = x x = y y = temp return temp; }
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 package mainimport "fmt" func main () { var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值为 : %d\n" , a ) fmt.Printf("交换前 b 的值为 : %d\n" , b ) swap(a, b) fmt.Printf("交换后 a 的值 : %d\n" , a ) fmt.Printf("交换后 b 的值 : %d\n" , b ) } func swap (x, y int ) int { var temp int temp = x x = y y = temp return temp; }
以下代码执行结果为:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
引用传递(指针传递) 指针
Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。
接下来让我们来一步步学习 Go 语言指针。
我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
以下实例演示了变量在内存中地址:
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 package mainimport "fmt" func main () { var a int = 100 var b int = 200 fmt.Printf("交换前,a 的值 : %d\n" , a ) fmt.Printf("交换前,b 的值 : %d\n" , b ) swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n" , a ) fmt.Printf("交换后,b 的值 : %d\n" , b ) } func swap (x *int , y *int ) { var temp int temp = *x *x = *y *y = temp }
defer
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数。
defer作用:
结果
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。栈
1 2 3 4 5 6 7 8 9 func Demo () { defer fmt.Println("1" ) defer fmt.Println("2" ) defer fmt.Println("3" ) defer fmt.Println("4" ) } func main () { Demo() }
return 和 defer在同一个函数中 return先执行 defer后执行
因为defer 是在函数的生命周期结束后执行 类似于Python的“ __del__”函数
recover错误拦截
运行时panic异常一旦被引发就会导致程序崩溃。
Go语言提供了专用于“拦截”运行时panic的内建函数“recover”。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。
注意: recover只有在defer调用的函数中有效。
示例代码
结果
如果程序没有异常,不会打印错误信息。
1 func recover interface {}
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 package mainimport "fmt" func Demo (i int ) { var arr [10 ]int defer func () { err := recover () if err != nil { fmt.Println(err) } }() arr[i] = 10 } func main () { Demo(10 ) fmt.Println("程序继续执行..." ) }
切片 slice 数组 slice(动态数组) slice
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组")
,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片 你可以声明一个未指定大小的数组来定义切片:
切片不需要说明长度。
或使用make()函数来创建切片:
1 2 3 4 5 6 7 var slice1 []type = make ([]type , len )也可以简写为 slice1 := make ([]type , len )
也可以指定容量,其中capacity为可选参数。
1 make ([]T, length, capacity)
切片初始化
直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
初始化切片s,是数组arr的引用
1 s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
缺省endIndex时将表示一直到arr的最后一个元素
缺省startIndex时将表示从arr的第一个元素开始
1 s1 := s[startIndex:endIndex]
通过切片s初始化切片s1
通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
切片容量的增加 切片的长度和容量不同,长度表示左指针至右指针之间的距离,容量表示左指针至底层数组未尾的距离。
切片扩容机制, append的时候,如果长度增加后超过容量,则容量增加2倍
len() 和 cap() 函数 切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
以下为具体实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { var numbers = make ([]int ,3 ,5 ) printSlice(numbers) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
空(nil)切片 一个切片在未初始化之前默认为 nil,长度为 0,实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { var numbers []int printSlice(numbers) if (numbers == nil ){ fmt.Printf("切片是空的" ) } } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
切片截取 可以通过设置下限及上限来设置截取切片*[lower-bound:upper-bound]*,实例如下:
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 40 41 42 43 44 45 46 47 48 package mainimport "fmt" func main () { numbers := []int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 } printSlice(numbers) fmt.Println("numbers ==" , numbers) fmt.Println("numbers[1:4] ==" , numbers[1 :4 ]) fmt.Println("numbers[:3] ==" , numbers[:3 ]) fmt.Println("numbers[4:] ==" , numbers[4 :]) numbers1 := make ([]int ,0 ,5 ) printSlice(numbers1) number2 := numbers[:2 ] printSlice(number2) number3 := numbers[2 :5 ] printSlice(number3) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
append() 和 copy() 函数 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
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 package mainimport "fmt" func main () { var numbers []int printSlice(numbers) numbers = append (numbers, 0 ) printSlice(numbers) numbers = append (numbers, 1 ) printSlice(numbers) numbers = append (numbers, 2 ,3 ,4 ) printSlice(numbers) numbers1 := make ([]int , len (numbers), (cap (numbers))*2 ) copy (numbers1,numbers) printSlice(numbers1) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
map(dict) map和slice类似,只不过是数据结构不同,下面是map的一些声明方式。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package mainimport ( "fmt" ) func main () { var test1 map [string ]string test1 = make (map [string ]string , 10 ) test1["one" ] = "php" test1["two" ] = "golang" test1["three" ] = "java" fmt.Println(test1) test2 := make (map [string ]string ) test2["one" ] = "php" test2["two" ] = "golang" test2["three" ] = "java" fmt.Println(test2) test3 := map [string ]string { "one" : "php" , "two" : "golang" , "three" : "java" , } fmt.Println(test3) language := make (map [string ]map [string ]string ) language["php" ] = make (map [string ]string , 2 ) language["php" ]["id" ] = "1" language["php" ]["desc" ] = "php是世界上最美的语言" language["golang" ] = make (map [string ]string , 2 ) language["golang" ]["id" ] = "2" language["golang" ]["desc" ] = "golang抗并发非常good" fmt.Println(language) fmt.Println(language) }
遍历
1 2 3 for key,value := range cityMap{ fmt.println (key,value) }
面向对象特性 方法 假设有两个方法,一个方法的接收者是指针类型,一个方法的接收者是值类型,那么:
对于值类型的变量和指针类型的变量,这两个方法有什么区别?
如果这两个方法是为了实现一个接口,那么这两个方法都可以调用吗?
如果方法是嵌入到其他结构体中的,那么上面两种情况又是怎样的?
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 package mainimport "fmt" type T struct { name string } func (t T) method1() { t.name = "new name1" } func (t *T) method2() { t.name = "new name2" } func main () { t := T{"old name" } fmt.Println("method1 调用前 " , t.name) t.method1() fmt.Println("method1 调用后 " , t.name) fmt.Println("method2 调用前 " , t.name) t.method2() fmt.Println("method2 调用后 " , t.name) }
1 2 3 4 method1 调用前 old name method1 调用后 old name method2 调用前 old name method2 调用后 new name2
当调用t.method1()
时相当于method1(t)
,实参和行参都是类型 T,可以接受。此时在method1
()中的t只是参数t的值拷贝,所以method1
()的修改影响不到main中的t变量。
当调用t.method2()
=>method2(t)
,这是将 T 类型传给了 *T 类型,go可能会取 t 的地址传进去:method2(&t)
。所以 method1
() 的修改可以影响 t。
T 类型的变量这两个方法都是拥有的。
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 package mainimport "fmt" import "math" type Point struct { X, Y float64 }func (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y) } func main () { p := Point{1 , 2 } q := Point{4 , 6 } distanceFormP := p.Distance fmt.Println(distanceFormP(q)) fmt.Println(p.Distance(q)) distanceFormQ := q.Distance fmt.Println(distanceFormQ(p)) fmt.Println(q.Distance(p)) }
如果类名首字母大写,表示其他包也能够访问
如果说类的属性首字母大写,表示该属性是对外能够访问的,否则的话只能够类的内部访问