https://www.runoob.com/go/go-tutorial.html
GO的语言特性
并发
Go语言在并发编程方面比绝大多数语言要简洁不少
Go语言的并发执行单元是一种称为 goroutine 的协程
内存回收(GC)
内存自动回收,再也不需要开发人员管理内存
开发人员专注业务实现,降低了心智负担
只需要new分配內存,不需要释放
内存分配
先分配一块大内存区域
大内存被切分成各个大小等级的块,放入不同的空闲list中
对象分配空间时从空闲list中取出大小合适的内存块
内存回收时,会把不用的内存重放回空闲list
空闲内存会按照一定策略合并,以减少碎片。
编译
目前 Galang具有两种编译器
一种是建立在GC基础上的Gccgo
另外—种是分别针对64位×64和32位86计算机的一套编译器(6g和8g)
网络编程
socket用net.Dial(基于tcp/udp,封裝了传统的 connect、listen、accept等接口)
http用http Get/post()
rpc用client.Call(‘class_name.method_name’ , args, &reply)
函数多返回值
允许函数返回多个值,在某些场景下,可以有效的简化编程。
Go语言推荐的编程风格,是函数返回的最后一个参数为error类型(只要逻辑体中可能出现异常),这样,在语言级别支持多返回值,就很有必要了
语言交互性
语言交互性指的是本语言是否能和其他语言交互,比如可以调用其他语言编译的库。
Go可以和c程序交互
HelloWorld GO自动找main包的main函数作为程序入口,然后进行编译
1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { fmt.Println("helloGo" ) }
代码解析
package
在同一个包下面的文件属于同一个工程文件,不用import
包,可以直接使用
在同一个包下面的所有文件的package名,都是一样的
在同一个包下面的文件package
名都建议设为是该目录名,但也可以不是
import
import "fmt"
: 告诉 Go 编译器这个程序需要使用 fmt 包的函数,fmt 包实现了格式化 IO(输入/输出)的函数可以是相对路径也可以是绝对路径,推荐使用绝对路径(起始于工程根目录)
点操作
: 这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名(类似于python的from import
)
也就是前面你调用的fmt.Println("hello world")
可以省略的写成Println("hello world")
别名操作
: 把包命名成另一个我们用起来容易记忆的名字
别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")
_操作
: 引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数
1 2 3 4 import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" )
main : 是程序运行的入口。
执行(go run)
python执行文件是python helloworld.py
go执行文件是go run helloworld.go
编译(go build , go install) 编译 :
1 2 3 4 5 6 7 8 # 在任意文件路径下,运行: go build hello # 进入项目(应用包)的路径,然后运行: go build # -o参数来指定编译后得到的可执行文件的名字 go build -o heiheihei.exe
在哪里执行build就会在哪里生成exe
文件
install :
install= build + 移动exe文件 : 就是build之后 , 将exe文件移动到GOPATH
目录下的bin目录
1 2 3 4 5 # 在任意文件路径下,运行: go install hello # 进入项目(应用包)的路径,然后运行: go install
在编译生成go程序的时,go实际上会去两个地方找程序包 , 在程序包里,自动找main包的main函数作为程序入口,然后进行编译:
GOROOT下的src文件夹下,
GOPATH下的src文件夹下。
然后就能在GOPATH
目录下的bin目录中找到hello.exe
交叉编译 想在windows下编译一个linux下可执行文件
只需要指定目标操作系统的平台和处理器架构即可:
1 2 3 SET CGO_ENABLED=0 // 禁用CGO SET GOOS=linux // 目标平台是linux SET GOARCH=amd64 // 目标处理器架构是amd64
然后再执行go build
命令,得到的就是能够在Linux平台运行的可执行文件了。
变量
静态强类型
: 变量需要声明类型 , 且变量的类型不经常改变
局部变量在声明后必须使用 (全局变量可以不用 , 但是函数里面的变量声明后必须使用)
推荐使用小驼峰变量
(首字母不大写 , 后续单词大写)
类型推导
: 省略变量类型声明 , 交给编译器去猜
短变量声明
: 在函数内部,可以使用更简略的 :=
方式声明并初始化变量。
匿名变量
: _
和python的_
变量一样 , 匿名变量不占用命名空间,不会分配内存
标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
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 var name string = "yui" var age = 21 var ( school string = "gdgy" isMarry bool ) func foo () (a int , b string ) { return a, "Q1mi" } func main () { job := "student" isMarry = false fmt.Println(name, age, school, isMarry, job) x, _ := foo() fmt.Println("x=" , x) }
常量
const同时声明多个常量时,如果省略了值则表示和上面一行的表达式
相同。 (注意是表达式相同 , 值不一定相同)
iota
常量计数器,只能在常量的表达式中使用。**iota
在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota
计数一次**(iota可理解为const语句块中的行索引)。
定义数量级 : <<
表示左移操作
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。不过函数必须是内置函数,否则编译不过
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 58 59 60 61 62 63 64 65 66 import "unsafe" const pi = 3.1415 const ( forbiden = 403 notFound = 404 OK = 200 ) const ( a = "abc" b = len (a) c = unsafe.Sizeof(a) ) const ( score1 = 98 score2 score3 ) const ( n1 = iota n2 n3 ) const ( n100 = 1 n200 = iota n300 = 288 n400 n500 = iota _ n600 ) const n700 = iota const ( d1, d2 = iota + 1 , iota + 2 d3, d4 = iota + 100 , iota + 200 ) const ( _ = iota KB = 1 << (10 * iota ) MB = 1 << (10 * iota ) GB = 1 << (10 * iota ) TB = 1 << (10 * iota ) PB = 1 << (10 * iota ) ) const ( i=1 <<iota j=3 <<iota k l ) func main () { fmt.Println(pi,forbiden,notFound,OK) fmt.Println(score1,score2,score3) fmt.Println(n1,n2,n3) }
数据类型
整型、
浮点型、
布尔型、
字符串
数组、
切片、
结构体、
函数、
map、
通道(channel)
整型
带符号整型 : int8、int16、int32、int64
无符号整型 : uint8、uint16、uint32、uint64
特殊整型 : uint、int
uint8
就是我们熟知的byte
型,int16
对应C语言中的short
型,int64
对应C语言中的long
型。
类型
描述
uint8
无符号 8位整型 (0 到 255)
uint16
无符号 16位整型 (0 到 65535)
uint32
无符号 32位整型 (0 到 4294967295)
uint64
无符号 64位整型 (0 到 18446744073709551615)
int8
有符号 8位整型 (-128 到 127)
int16
有符号 16位整型 (-32768 到 32767)
int32
有符号 32位整型 (-2147483648 到 2147483647)
int64
有符号 64位整型 (-9223372036854775808 到 9223372036854775807)
uint
32位操作系统上就是uint32
,64位操作系统上就是uint64
int
32位操作系统上就是int32
,64位操作系统上就是int64
uintptr
无符号整型,用于存放一个指针
和Java
一样 , 支持定义N进制
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { decimal := 10 oct := 077 ox := 0x1324567 ob := 0 b1111 interger8 := int8 (123 ) fmt.Println(decimal, oct, ox, ob, interger8) }
浮点型
float32
和float64
默认为float64
1 2 3 4 5 6 7 8 9 10 func main () { num := 1.2354 num2 := float32 (1.789 ) fmt.Println(math.MaxFloat32) fmt.Println(num,num2) num = float64 (num2) fmt.Println(num,num2) }
布尔型
布尔类型变量的默认值为false
。
不允许将整型强制转换为布尔型.
布尔型无法参与数值运算 ,也无法与其他类型进行转换。
字符串
字符串的值为双引号(")
中的内容
多行字符串为反引号
注意 : 反引号内的内容皆row格式
方法
介绍
len(str)
求长度
+或fmt.Sprintf
拼接字符串
strings.Split
分割
strings.contains
判断是否包含
strings.HasPrefix,strings.HasSuffix
前缀/后缀判断
strings.Index(),strings.LastIndex()
子串出现的位置
strings.Join(a[]string, sep string)
join操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { name := "heyingliang" path := `C:\software\Anaconda3` fmt.Println(name) fmt.Println(path) var1 := name + path var2 := fmt.Sprintf("%s%s" ,name,path) fmt.Println(var1==var2) res := strings.Split(path,"\\" ) fmt.Println(res) fmt.Println(strings.Join(res,"+" )) fmt.Println(strings.Contains(path,"\\" )) fmt.Println(strings.HasPrefix(path,"C:" )) fmt.Println(strings.Index(path,"Ana" )) }
byte和rune类型
字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。
字符串底层是一个byte数组
Go语言中为了处理非ASCI码类型的字符 , 定义了新的rune类型
byte
类型,代表了ASCII码
的一个字符 。 实际是一个uint8
rune
类型,代表一个 UTF-8字符
。实际是一个int32
实际上源码:
1 2 type byte = uint8 type rune = int32
byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = 'A'
,字符使用单引号括起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func traversalString () { s := "hello沙河" for i := 0 ; i < len (s); i++ { fmt.Printf("%v(%c) " , s[i], s[i]) } fmt.Println() for _, r := range s { fmt.Printf("%v(%c) " , r, r) } fmt.Println() } func main () { traversalString() }
因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个byte数组 ,所以可以和[]byte
类型相互转换。字符串是不能修改的,字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
修改字符串 要修改字符串,需要先将其转换成[]rune
或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
1 2 3 4 5 6 7 8 9 10 11 12 func changeString () { s1 := "big" byteS1 := []byte (s1) byteS1[0 ] = 'p' fmt.Println(string (byteS1)) s2 := "白萝卜" runeS2 := []rune (s2) runeS2[0 ] = '红' fmt.Println(string (runeS2)) }
计算汉字长度 1 2 3 4 5 func main () { s := "何应良" chars := []rune (s) fmt.Println(len (chars)) }
字符串的len函数 字符串的len函数 : 返回字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import ( "fmt" "unicode/utf8" ) func main () { s1 := "jija" s2 := "忍者" fmt.Println(len (s1)) fmt.Println(len (s2)) fmt.Println(utf8.RuneCountInString(s2)) }
总结
字符 : 使用byte存储 , 本质是一个byte整型
字符串 : 使用[]int32存储 , 本质是一个byte整型 的 数组
值类型和引用类型
int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值 , 当使用等号 =
将一个变量的值赋值给另一个变量时,如:j = i
,实际上是在内存中将 i 的值进行了拷贝
引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
流程控制 if 1 2 3 4 5 6 7 8 9 10 func main () { age := 18 if age > 18 { fmt.Println("old" ) } else if age == 18 { fmt.Println("18" ) } else { fmt.Println("young" ) } }
go判断前添加执行语句 1 2 3 4 5 6 7 8 9 10 11 func main () { myAge := 17 if myAge++; myAge > 18 { fmt.Println("old" ) } else if myAge == 18 { fmt.Println("18" ) } else { fmt.Println("young" ) } }
注意 , 如果是定义变量的话 , 这个变量只存在于if语句中
1 2 3 4 5 6 7 8 9 10 11 func main () { if myAge := 17 ; myAge > 18 { fmt.Println("old" ) } else if myAge == 18 { fmt.Println("18" ) } else { fmt.Println("young" ) } }
switch
switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。
switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
1 2 3 4 5 6 7 8 9 10 11 12 var qq = 2 func main () { switch qq { case 1 : fmt.Println("first" ) case 2 : fmt.Println("secend" ) case 3 : fmt.Println("third" ) } }
for 1 2 3 4 5 func main () { for s := 0 ; s < 10 ; s++ { fmt.Println(s) } }
省略初始语句 1 2 3 4 5 6 7 var nu int = 0 func main () { for ; nu < 10 ; nu++ { fmt.Println(nu) } fmt.Println(nu) }
省略初始和结束语句 类似于python的while语句
1 2 3 4 5 6 7 func main () { nu := 0 for nu < 10 { fmt.Println(nu) nu++ } }
无限循环 1 2 3 4 5 6 7 8 9 10 func main () { nu := 0 for { nu++ fmt.Println(nu) if nu == 10 { break } } }
for range(键值循环) 使用for range
遍历数组、切片、字符串、map 及通道(channel)。
通过for range
遍历的返回值有以下规律:
数组、切片、字符串返回索引
和值
。
map返回键和值。
通道(channel)只返回通道内的值。
1 2 3 4 5 6 7 8 9 10 11 func main () { myStr := "aA何应良" for idx, val := range myStr { fmt.Printf("%v,%v--%c \n" ,idx, val, val) } }
goto
goto 语句可以无条件地转移到过程中指定的行。
goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { var a int = 10 LOOP: for a < 20 { if a == 15 { a = a + 1 goto LOOP } fmt.Printf("a的值为 : %d\n" , a) a++ } }
函数 1 2 3 func function_name ( [parameter list] ) [return_types ] { 函数体 }
func:函数由 func 开始声明
function_name:函数名称,函数名和参数列表一起构成了函数签名。
parameter list:参数列表。
return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func maxNum (num1 int , num2 int ) int { var res int if num1 > num2 { res = num1 } else { res = num2 } return res } func swap (x, y string ) (string , string ) { return y, x } func main () { a := "1" b := "heyingliang" fmt.Println(swap(a, b)) }
闭包
getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func getSequence () func () int { i:=0 return func () int { i+=1 return i } } func main () { nextNumber := getSequence() fmt.Println(nextNumber()) fmt.Println(nextNumber()) fmt.Println(nextNumber()) nextNumber1 := getSequence() fmt.Println(nextNumber1()) fmt.Println(nextNumber1()) }
方法
Go 语言中有函数和方法。
一个方法就是一个包含了接受者的函数 ,接受者可以是命名类型
或者结构体类型
的一个值或者是一个指针。
所有给定类型的方法属于该类型的方法集。
1 2 3 func (variable_name variable_data_type) function_name () [return_type ] { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Circle struct { radius float64 } func (c Circle) getArea () float64 { return 3.14 * c.radius * c.radius } func main () { var c1 Circle c1.radius = 10.00 fmt.Println("圆的面积 = " , c1.getArea()) }
变量作用域 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 var variable_name [SIZE] variable_typevar balance [10 ] float32 var balance = [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 } var balance = [...]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 } var variable_name [SIZE1][SIZE2]...[SIZEN] variable_typevar threedim [5 ][10 ][4 ]int a = [3 ][4 ]int { {0 , 1 , 2 , 3 } , {4 , 5 , 6 , 7 } , {8 , 9 , 10 , 11 }, } func getAverage (arr []int , size int ) float32 {}func getAverage (arr [5]int , size int ) float32 {}
指针
1 2 3 4 5 6 var var_name *var -type var ip *int var fp *float32
如何使用指针 指针使用流程:
定义指针变量。
为指针变量赋值。
访问指针变量中指向地址的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var a int = 20 var ip *int ip = &a fmt.Printf("a 变量的地址是: %x\n" , &a) fmt.Printf("ip 变量储存的指针地址: %x\n" , ip) fmt.Printf("*ip 变量的值: %d\n" , *ip) }
空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
1 2 3 4 func main () { var ptr *int fmt.Printf("ptr 的值为 : %x\n" , ptr) }
1 2 if (ptr != nil ) if (ptr == nil )
指针数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const MAX int = 3 func main () { a := []int {10 , 100 , 200 } var i int var ptr [MAX]*int for i = 0 ; i < MAX; i++ { ptr[i] = &a[i] } for i = 0 ; i < MAX; i++ { fmt.Printf("a[%d] = %d\n" , i, *ptr[i]) } }
指向指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var ptr **int ;func main () { var a int var ptr *int var pptr **int a = 3000 ptr = &a pptr = &ptr fmt.Printf("变量 a = %d\n" , a) fmt.Printf("指针变量 *ptr = %d\n" , *ptr) fmt.Printf("指向指针的指针变量 **pptr = %d\n" , **pptr) }
指针作为函数参数
向函数传递指针,只需要在函数定义的参数上设置为指针类型即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 ) { *x, *y = *y, *x }
结构体
数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体表示一项记录
类似于python中的具名元组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type struct_variable_type struct { member definition ... member definition } variable_name := structure_variable_type {value1, value2...valuen} variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen} 结构体.成员名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Books struct { title string author string subject string book_id int } func main () { fmt.Println(Books{"Go 语言" , "www.runoob.com" , "Go 语言教程" , 6495407 }) fmt.Println(Books{title: "Go 语言" , author: "www.runoob.com" , subject: "Go 语言教程" , book_id: 6495407 }) fmt.Println(Books{title: "Go 语言" , author: "www.runoob.com" }) var Book1 Books Book1.title = "Go 语言" Book1.book_id = 6495407 fmt.Printf("Book 1 title : %s\n" , Book1.title) }
结构体作为函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Books struct { title string author string subject string book_id int } func printBook (book Books) { fmt.Printf("Book title : %s\n" , book.title) } func main () { var Book1 Books Book1.title = "Go 语言" Book1.author = "www.runoob.com" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 printBook(Book1) }
结构体指针
1 2 3 4 5 6 var struct_pointer *Booksstruct_pointer = &Book1 struct_pointer.title
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Books struct { title string author string subject string book_id int } func printBook (book *Books) { fmt.Printf("Book title : %s\n" , book.title) } func main () { var Book1 Books Book1.title = "Go 语言" printBook(&Book1) }
切片(Slice)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var identifier []type var slice1 []type = make ([]type , len )slice1 := make ([]type , len ) make ([]T, length, capacity)s :=[] int {1 ,2 ,3 } s := arr[:] s := arr[startIndex:]
初始化数组和切片
1 2 3 4 var arr = [...]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 } var slice = []float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
空(nil)切片
1 2 3 4 5 6 7 8 9 10 11 func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) } func main () { var numbers []int printSlice(numbers) if (numbers == nil ){ fmt.Printf("切片是空的" ) } }
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少 。
1 2 3 4 5 6 7 8 9 10 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) }
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 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) }
范围(Range)
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 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 func main () { nums := []int {2 , 3 , 4 } sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:" , sum) for i, num := range nums { if num == 3 { fmt.Println("index:" , i) } } kvs := map [string ]string {"a" : "apple" , "b" : "banana" } for k, v := range kvs { fmt.Printf("%s -> %s\n" , k, v) } for i, c := range "go" { fmt.Println(i, c) } }
Map(集合) 定义 Map , 有两种形式
1 2 3 4 5 6 7 8 9 10 11 12 m2 := make (map [string ]string ) var map_variable map [key_data_type]value_data_type map_variable := make (map [key_data_type]value_data_type) m3 := map [string ]string { "a" : "aa" , "b" : "bb" , }
delete() 函数 1 2 3 4 5 6 7 8 9 10 11 12 func main () { countryCapitalMap := map [string ]string { "France" : "Paris" , "Italy" : "Rome" , "Japan" : "Tokyo" , "India" : "New delhi" , } delete (countryCapitalMap, "France" ) fmt.Println("法国条目被删除" ) }
递归函数 1 2 3 4 5 6 7 func recursion () { recursion() } func main () { recursion() }
1 2 3 4 5 6 7 8 9 10 11 12 func fibo (count int ) int { if count <= 2 { return 1 } else { return fibo(count-1 ) + fibo(count-2 ) } } func main () { count := 8 fmt.Println(fibo(count)) }
类型转换
1 2 3 4 5 6 7 8 func main () { var sum int = 17 var count int = 5 var mean float32 mean = float32 (sum) / float32 (count) fmt.Printf("mean 的值为: %f\n" , mean) }
接口 接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } type struct_name struct { } func (struct_name_variable struct_name) method_name1 () [return_type ] { } ... func (struct_name_variable struct_name) method_namen () [return_type ] { }
我们定义了一个接口Phone,接口里面有一个方法call()。
然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,输出结果如下:
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 type Phone interface { call() } type NokiaPhone struct {} type IPhone struct {} func (nokiaPhone NokiaPhone) call () { fmt.Println("I am Nokia, I can call you!" ) } func (iPhone IPhone) call () { fmt.Println("I am iPhone, I can call you!" ) } func main () { var phone Phone phone = new (NokiaPhone) phone.call() phone = new (IPhone) phone.call() }
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 type error interface { Error() string } func Sqrt (f float64 ) (float64 , error) { if f < 0 { return 0 , errors.New("math: square root of negative number" ) } }
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 type DivideError struct { dividee int divider int } func (de *DivideError) Error () string { strFormat := ` Cannot proceed, the divider is zero. dividee: %d divider: 0 ` return fmt.Sprintf(strFormat, de.dividee) } func Divide (varDividee int , varDivider int ) (result int , errorMsg string ) { if varDivider == 0 { dData := DivideError{ dividee: varDividee, divider: varDivider, } errorMsg = dData.Error() return } else { return varDividee / varDivider, "" } } func main () { if result, errorMsg := Divide(100 , 10 ); errorMsg == "" { fmt.Println("100/10 = " , result) } if _, errorMsg := Divide(100 , 0 ); errorMsg != "" { fmt.Println("errorMsg is: " , errorMsg) } }
并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
1 2 3 4 5 6 7 go 函数名( 参数列表 )go f(x, y, z)f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
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 import ( "fmt" "time" ) func say (s string ) { for i := 0 ; i < 5 ; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main () { go say("world" ) say("hello" ) }
通道(channel)
通道(channel)是用来传递数据 的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func sum (s []int , c chan int ) { sum := 0 for _, v := range s { sum += v } c <- sum } func main () { s := []int {7 , 2 , 8 , -9 , 4 , 0 } c := make (chan int ) go sum(s[:len (s)/2 ], c) go sum(s[len (s)/2 :], c) x, y := <-c, <-c fmt.Println(x, y, x+y) }
通道缓冲区 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
1 ch := make (chan int , 100 )
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { ch := make (chan int , 2 ) ch <- 1 ch <- 2 fmt.Println(<-ch) fmt.Println(<-ch) }
遍历通道与关闭通道
通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func fibonacci (n int , c chan int ) { x, y := 0 , 1 for i := 0 ; i < n; i++ { c <- x x, y = y, x+y } close (c) } func main () { c := make (chan int , 10 ) go fibonacci(cap (c), c) for i := range c { fmt.Println(i) } }