Go语言的基础组成-->包声明/引入包/函数/变量/语句&表达式/注释
package main -->定义了包名,必须在源文件中非注释的第一行指明这个文件属于哪个包。package main表示一个
可独立执行的程序,每个Go应用程序都包含一个名为main的包 import "fmt" -->告诉Go编译器这个程序需要使用fmt包(的函数,或其他元素),fmt包实现了格式化IO
(输入/输出)的函数
-->func main()是程序开始执行的函数。main函数是每一个可执行程序所必须包含的,一般都是在
启动后第一个执行的函数,如果有init()函数会先执行该函数
func main() { // --》 { 不能放在单独的行上 /*注释*/ fmt.Println("Hello, world!") }
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,那么使用这种形式的
标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包)-->导出,像面向对象
语言中的public
标识符如果以小写字母开头,则对包外是不可见的,但在整个包的内部是可见并且可用的-->protected
运行go代码-->go run hello.go
生成二进制文件-->go build hello.go --> .exe
Go语言基础语法
Go标记:Go程序可以由多个标记组成,可以是关键字、标识符、常量、字符串、符号
Go语言数据类型
布尔型-->var b bool = true
数字类型 --> int float32 float64
字符串类型 -->
派生类型-->指针类型Pointer、数组类型、结构化类型struct、Channel类型、函数类型、切片类型、接口类型interface、Map类型
与其他主要编程语言的差异:
- Go语言不允许隐式类型转换
- 别名和原有类型也不能进行隐式类型转换
package day0527 import "testing" func TestImplicit(t *testing.T) { var a int = 1 var b int64 b = a //cannot use a (type int) as type int64 in assignment t.Log(a, b) }
package day0527 import "testing" type MyInt int64 //定义别名 func TestImplicit(t *testing.T) { var a int = 1 var b int64 b = int64(a) var c MyInt c = b //cannot use b (type int64) as type MyInt in assignment t.Log(a, b, c) }
类的预定义值:
math.MaxInt64
math.MaxFloat64
math.MaxUint32
声明变量使用var关键字,var identifier type
package main import "fmt" func main() { var a string = "Runoob" fmt.Println(a) var b, c int = 1, 2 fmt.Println(b, c) }
package main var x, y int var ( //这种因式分解关键字的写法一般用于声明全局变量 a int b bool ) var c, d int = 1, 2 var e, f = 123, "hello" func main() { g, h := 123, "hello" println(x, y, a, b, c, d, e, f, g, h) }
值类型和引用类型
int、float、bool、string这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值
可以通过&i来获取变量i的内存地址。值类型的变量的值存储在栈中
一个引用类型的变量r1存储的是r1的值所在的内存地址,或内存地址中第一个字所在的位置
使用变量的首选形式 :=
全局变量允许声明但不使用,局部变量声明了就必须使用否则报错
交换两个变量的值,可以使用a,b = b,a
空白标识符_也被用于抛弃值,如值5在: _, b = 5, 7中被抛弃
_实际上是一个只写变量,不能得到它的值。这样做是因为Go语言中必须使用所有被声明的变量,但有时并不需要使用从一个函数得到的所有返回值
Go语言运算符
算数运算符
比较运算符
位运算符
&^ 按位置清零 1 &^ 0 -- 1 1 &^ 1 -- 0 0 &^ 1 -- 0 0 &^ 0 -- 0 右边的二进制位是1,左边对应二进制位清零;右边的二进制位是0,左边对应二进制位不变
Go语言常量
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
常量定义:const identifier [type] = value
可以省略类型说明符[type],因为编译器可以根据变量的值来推断其类型
package main import "fmt" func main() { const LENGTH int = 10 const WIDTH int = 5 var area int const a, b, c = 1, false, "str" area = LENGTH * WIDTH fmt.Printf("面积为:%d", area) println() println(a, b, c) }
//常量还可以用作枚举 const ( Unknown = 0 Female = 1 Male = 2 )
//常量可以用len(),cap(),unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过 package main import "unsafe" const ( a = "abc" b = len(a) //3 c = unsafe.Sizeof(a) //16 ) func main() { println(a, b, c) }
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)
iota可以被用作枚举值
const ( Monday = 1 + iota Tuesday
Wednesday
)
const (
Readable = 1 << iota
Writable
Executable
)
Go语言运算符
Go语言条件语句
Go语言循环语句-->仅有for循环
goto语句,go语言的goto语句可以无条件地转移到过程中指定的行。goto语句通常与条件语句配合使用,可用来实现条件转移,构成循环,跳出循环体等功能
无限循环while(true)-->for {}
while条件循环 while(n<5)-->for n < 5 {}
if条件
if condition{ } else{ }
支持变量赋值 if var declaration; condition {}
package day0527 import "testing" func TestIfMultiSec(t *testing.T) { if a := 1 == 2; a { t.Log("1==1") } //方法支持多返回值 v本身返回值 err返回一个错误 if v, err := someFun(); err==nil { t.Log("1==1") }else{ t.Log("1==1") } }
switch条件
条件表达式不限制为常量或者整数
单个case中,可以出现多个结果选项,使用逗号分隔
与C语言等规则相反,Go语言不需要用break来明确退出一个case
可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if else的逻辑作用等同
package day0527 import "testing" func TestSwitchMultiCase(t *testing.T) { for i:=0;i<5;i++{ switch i{ case 0,2: t.Log("Even") case 1,3: t.Log("Odd") default: t.Log("it is not 0-3") } } } func TestSwitchCaseCondition(t *testing.T){ for i:=0;i<5;i++{ switch { case i%2 == 0: t.Log("Even") case i%2 == 1: t.Log("Odd") default: t.Log("unknow") } } }
Go语言函数
与其他编程语言的区别:可以有多个返回值;所有参数都是值传递(slice、map、channel会有传引用的错觉);函数可以作为变量的值;函数可以作为参数和返回值
package day0529 import ( "fmt" "math/rand" "testing" "time" ) //go函数可以有多个返回值 func returnMultiValues()(int, int){ return rand.Intn(10), rand.Intn(20) } //函数可以作为参数和返回值,闭包?装饰? func timeSpent(inner func (op int) int) func(op int)int{ return func(n int) int{ start := time.Now() ret:=inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFn(op int) int{ time.Sleep(time.Second * 1) return op } func TestFn(t *testing.T){ a, b := returnMultiValues() t.Log(a, b) tsSF := timeSpent(slowFn) t.Log(tsSF(10)) } //可变参数-->本质:转为数组 func Sum(ops ...int) int{ ret := 0 for _, op := range ops{ ret += op } return ret } func TestVarParam(t *testing.T){ t.Log(Sum(1, 2, 3)) t.Log(Sum(1, 2, 3, 4)) } func Clear(){ fmt.Println("Clear resources.") } //defer 延迟运行 类似finally func TestDefer(t *testing.T){ defer Clear() fmt.Println("start") panic("err")//异常中断,后面的代码不会被执行,但会执行前面defer的函数 }
函数是基本的代码块,用于执行一个任务
Go语言最少有个main函数
函数声明告诉了编译器函数的名称,返回类型,和参数
函数定义: func function_name([parameter list]) [return_types] {函数体}
func-->函数由func开始声明
function_name-->函数名称,函数名和参数列表一起构成了函数签名
parameter list-->参数列表
return_types-->返回类型
//函数返回两个数的最大值 func max(num1, num2 int) int { //声明局部变量 var result int if (num1 > num2) { result = num1 } else { result = num2 } return result }
闭包
package main import "fmt" func getSequence() func() int{ i := 0 return func() int { i += 1 return i } } func main() { nextNumber := getSequence() /*调用nextNumber函数,i变量自增1并返回*/ fmt.Println(nextNumber()) fmt.Println(nextNumber()) fmt.Println(nextNumber()) /*创建新的函数nextNumber1,并查看结果*/ nextNumber1 := getSequence() fmt.Println(nextNumber1()) fmt.Println(nextNumber1()) }
Go语言函数方法
Go语言中同时有函数和方法。一个方法就是一个包含了接收者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针
package main import ( "fmt" ) /*定义结构体*/ type Circle struct { radius float64 } func main() { var c1 Circle c1.radius = 10.00 fmt.Println("圆的面积 = ", c1.getArea()) } // func (variable_name variable_data_type) function_name() [return_type] {/*函数体*/} func (c Circle) getArea() float64 { return 3.14 * c.radius * c.radius }
Go语言变量作用域
Go语言中变量可以在三个地方声明:
- 函数内定义的变量称为局部变量
- 函数外定义的变量称为全局变量
- 函数定义中的变量称为形式参数
局部变量-->在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量
全局变量-->在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用
Go语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑
形式参数-->作为函数的局部变量来使用
package main import "fmt" /*声明全局变量*/ var a int = 20; func main() { var a int = 10 var b int = 20 var c int = 0 fmt.Printf("main()函数中 a = %d ", a) c = sum(a, b) fmt.Printf("main()函数中c = %d ", c) } func sum(a, b int) int { fmt.Printf("sum()函数中a = %d ", a) fmt.Printf("sum()函数中b = %d ", b) return a + b }
Go语言数组和切片
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型
Go语言数组声明需要指定元素类型及元素个数-->var variable_name [SIZE] variable_type
var a [3]int //声明并初始化为默认值
b := [3]int{1, 2, 3}
c := [2][2]int{{1, 2}, {3, 4}}
Go语言中,数组==比较的是值
package day0527 import "testing" func TestCompareArray(t *testing.T) { a := [...]int{1, 2, 3, 4} b := [...]int{1, 3, 4, 5} //c := [...]int{1, 2, 3, 4, 5} d := [...]int{1, 2, 3, 4} t.Log(a == b) //false t.Log(a == d) //true }
Go语言字符串
与其他主要编程语言的差异
- string是数据类型-->0值是空不是nil,不是引用或指针类型
- string是只读的byte slice,len函数可以得到它所包含的byte数
- string的byte数组可以存放任何数据
Unicode是一种字符集(code point)
UTF8是unicode的存储实现(转换为字节序列的规则)
Go语言切片-->动态数组
Go语言切片是对数组的抽象,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大
定义切片-->可以声明一个未指定大小的数组来定义切片
var identifier []type
切片不需要说明长度,或使用make()函数来创建切片
var slice1 []type = make([]type, len)
或者
slice1 := make([]type, len)
package day0527 import "testing" func TestArrayInit(t *testing.T) { var arr [3]int arr1 := [4]int{1, 2, 3, 4} arr3 := [...]int{1, 3, 4, 5} arr1[1] = 5 t.Log(arr[1], arr[2]) t.Log(arr1, arr3) } func TestArrayTravel(t *testing.T){ arr3 := [...]int{1, 3, 4, 5} for i:=0;i<len(arr3);i++{ t.Log(arr3[i]) } for idx, e := range arr3 { t.Log(idx, e) } } func TestArraySection(t *testing.T) { arr3 := [...]int{1, 2, 3, 4, 5} arr3_sec := arr3[:3] t.Log(arr3_sec) }
len()和cap()函数
切片是可索引的,并且可以由len()方法获取长度
切片提供了计算容量的方法cap()可以测量切片最长可以达到多少
切片内部结构
package day0527 import "testing" func TestSliceInit(t *testing.T){ var s0 []int t.Log(len(s0), cap(s0)) //0 0 s0 = append(s0, 1) t.Log(len(s0), cap(s0)) s1 := []int{1, 2, 3, 4} t.Log(len(s1), cap(s1)) s2 := make([]int, 3, 5) //默认初始化只初始化len个元素 t.Log(len(s2), cap(s2)) t.Log(s2[0], s2[1], s2[2], s2[3], s2[4]) } //切片是如何实现可变长的 func TestSliceGrowing(t *testing.T){ s := []int{} for i:=0;i<10;i++{ s = append(s, i) t.Log(len(s), cap(s)) } }
切片共享存储结构:
func TestSliceShareMemory(t *testing.T) { year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} Q2 := year[3:6] t.Log(Q2, len(Q2), cap(Q2)) summer := year[5:8] t.Log(summer, len(summer), cap(summer)) summer[0] = "Unknow" t.Log(Q2) t.Log(year) }
append()和copy()函数
如果想增加切片的容量,必须创建一个新的更大的切片并把原分片的内容都拷贝过来
相同维度相同长度的数组可以比较,切片不可以比较,切片只能和nil比较
Go语言指针
变量是一种使用方便的占位符,用于引用计算机内存地址
Go语言的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址
指针-->一个指针变量指向了一个值的内存地址
var ip *int
使用指针:定义指针变量-->为指针变量赋值-->访问指针变量中指向地址的值
package main import "fmt" func main() { var a int = 20 var ip *int ip = &a fmt.Printf("a变量的地址是:%x ", &a) /*指针变量的存储地址*/ fmt.Printf("ip变量储存的指针地址:%x ", ip) /*使用指针访问值*/ fmt.Printf("*ip变量的值:%d ", *ip) }
Go空指针
当一个指针被定义后没有分配到任何变量时,它的值为nil
一个指针变量通常缩写为ptr
指向指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量
Go语言指针作为函数参数
Go语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可
指针类型
与其他主要编程语言的差异:不支持指针运算;string是值类型,其默认的初始化值为空字符串,而不是nil
Go语言结构体
Go语言中数组可以存储同一类型的数据,但在结构体中可以为不同项定义不同的数据类型
定义结构体:使用type和struct语句
struct语句定义一个新的数据类型,结构体中有一个或多个成员
type语句设定了结构体的名称
type struct_variable_type struct {
member definition
member definition
......
member definition
}
package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { //创建一个新的结构体 fmt.Println(Books{"Go 语言", "www.runoob.com", "Go", 12}) //可以使用key=>value格式 fmt.Println(Books{title: "Go 语言", author:"www.run.com", subject:"Go", book_id: 00}) }
访问结构体成员-->
结构体.成员名
结构体作为函数参数-->
可以向其他数据类型一样将结构体类型作为参数传递给函数
结构体指针-->
可以定义指向结构体的指针 var struct_pointer *Books
使用结构体指针访问结构体成员,使用“.”操作符
Go面向对象编程 不支持继承
//数据和行为封装 type Employee struct { Id string Name string Age int } //实例创建及初始化 e := Employee{"0", "Bob", 20} e1 := Employee{Name:"Mike", Age:30} e2 := new(Employee) //这里返回的是引用/指针,相当于e:=&Employee{} e2.Id = "2" e2.Age=22 e2.Name = "Rose"
//行为(方法)定义 //与其他编程语言的主要差异 //第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制 func (e Employee) String() string{ return fmt.Println("ID:%S-Name:%s-Age:%d", e.Id, e.Name, e.Age) } //为了避免内存拷贝,通常使用第二种定义方式 func (e *Employee) String() string { return fmt.Sprintf("ID:%s/Name:%s/Age%d", e.Id, e.Name, e.Age) }
Go语言范围Range
Go语言中range关键字用于for循环中迭代数组、切片、通道或集合的元素,在数组和切片中它赶回元素的索引和索引对应的值,在集合中返回key-value对
package main import "fmt" 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 ", k, v) } for i, c := range "go" { fmt.Println(i, c) } }
Go语言Map
Map是一种无序的键值对的集合。Map最重要的是通过key来快速检索数据,key类似于索引,指向数据的值
Map是一种集合,可以像迭代数组和切片那样迭代它。Map是无序的,无法决定它的返回顺序,因为Map是使用hash表来实现的
与其他主要编程语言的差异:
在访问的Key不存在时,仍会返回零值,不能通过返回nil来判断元素是否存在
遍历:for range
Map声明
m := map[string]int{"one":1, "two":2, "three":3}
m1 := map[string]int{}
m2 := make(map[string]int, 10/*Initial Capacity*/)
package day0527 import "testing" func TestInitMap(t *testing.T) { m1 := map[int]int{1:1,2:4,3:9} t.Log(m1[2]) t.Logf("len m1=%d", len(m1)) m2 := map[int]int{} m2[4]=16 t.Logf("len m2=%d", len(m2)) m3 := make(map[int]int, 10) t.Logf("len m3=%d", len(m3)) } func TestAccessNotExistingKey(t *testing.T) { m1 := map[int]int{} t.Log(m1[1]) m1[2] = 0 t.Log(m1[2]) if v,ok:=m1[3];ok{ t.Log("key 3's value is %d", v) }else{ t.Log("key 3 is not existing") } }
定义Map
可以使用内建函数make也可以使用map关键字来定义Map
//声明变量,默认map是nil var map_variable map[key_data_type]value_data_type //使用make函数 map_variable := make(map[key_data_type]value_data_type)
package main import "fmt" func main() { var countryCapitalMap map[string]string countryCapitalMap = make(map[string]string) //map插入key-value对 countryCapitalMap["France"] = "巴黎" countryCapitalMap["Italy"] = "罗马" countryCapitalMap["Japan"] = "东京" countryCapitalMap["India"] = "新德里" for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap[country]) } //查看元素在集合中是否存在 capital, ok := countryCapitalMap["American"] if (ok) { fmt.Println("American的首都是", capital) } else { fmt.Println("American的首都不存在") } }
delete()函数:用于删除集合的元素,参数为map和其对应的key
package main import "fmt" func main() { countryCapitalMap := map[string]string{"France": "Pairs", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"} fmt.Println("原始地图") for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap[country]) } delete(countryCapitalMap, "France") fmt.Println("法国条目被删除") fmt.Println("删除元素后地图") for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap[country]) } }
map遍历
//遍历 func TestTravelMap(t *testing.T) { m1 := map[int]int{1:1, 2:4, 3:9} for k, v:=range m1{ t.Log(k, v) } }
Map与工厂模式
- Map的value可以是一个方法-->实现工厂模式
- 与Go的Dock type接口方式一起,可以方便的实现单一方法对象的工厂模式
package day0528 import "testing" func TestMapWithFunValue(t *testing.T) { m := map[int]func(op int) int{} m[1] = func(op int) int { return op } m[2] = func(op int) int { return op*op } m[3] = func(op int) int { return op*op*op } t.Log(m[1](2), m[2](2), m[3](2)) }
实现Set
Go的内置集合中没有Set实现,可以map[type]bool
元素的唯一性
基本操作:添加元素;判断元素是否存在;删除元素;元素个数
func TestMapForSet(t *testing.T) { mySet:=map[int]bool{} mySet[1]=true n := 1 if mySet[n]{ t.Logf("%d is existing", n) }else{ t.Logf("%d is not existing", n) } mySet[3]=true t.Log(len(mySet)) delete(mySet,1) }
Go语言递归函数
func recursion() {
recursion()
}
Go语言接口
接口与依赖
//Programmer.java public interface Programmer { String WriteCodes(); } //GoProgrammer.java public class GoProgrammer implements Programmer { @Override public String WriteCodes() { return "fmt.Println("Hello World")"; } } //Task.java public class Task { public static void main(String[] args) { Programmer prog = new GoProgrammer(); String codes = prog.WriteCodes(); System.out.println(codes); } }
package _interface import "testing" type Programmer interface { WriteHelloWorld() string } type GoProgrammer struct { } func (go *GoProgrammer) WriteHelloWorld() string{ return "fmt.Println("Hello World")" } func TestClient(t *testing.T) { var p Programmer p = new(GoProgrammer) t.Log(p.WriteHelloWorld()) }
Go接口与其他主要编程语言的差异:接口为非入侵性,实现不依赖于接口定义;接口的定义可以包含在接口使用者包内
接口变量
自定义类型
package customer_type import ( "fmt" "time" ) type IntConv func(op int) int func timeSpent(inner IntConv) IntConv { return func(n int) int { start := time.Now() ret :=inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } }
Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
package main
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]() {
}
package main import ( "fmt" ) type Phone interface { call() } type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I an iPhone, I can call you!") } func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call() }
空接口与断言
空接口可以表示任何类型 类似于java的object类型 c/c++的void *类型
通过断言来将空接口转换为指定类型 v, ok := p.(int) //ok=true时为转换成功
package empty_interface func DoSomething(p interface{}) { if i, ok := p.(int); ok{ fmt.Println("Integer", i) return } if i, ok := p.(string); ok{ fmt.Println("string", s) return } fmt.Println("Unknow Type") } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) DoSomething("10") }
package empty_interface func DoSomething(p interface{}) { switch v := p.(type){ case int: fmt.Println("Integer", v) case string: fmt.Println("String", v) default: fmt.Println("Unknown Type", v) } } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) DoSomething("10") }
Go接口最佳实践
//倾向于使用小的接口定义,很多接口只包含一个方法 type Reader interface { Read(p []byte) (n int, err error) } //较大的接口定义,可以由多个小接口定义组合而成 type ReadWriter interface{ Reader Writer } //只依赖于必要功能的最小接口 func StoreData(reader Reader) error{ ... }
Go语言不支持继承
扩展与复用
package extension import ( "fmt" "testing" ) type Pet struct {} func (p *Pet) Speak() { fmt.Print("...") } func (p *Pet) SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { p *Pet } func (d *Dog) Speak() { d.p.Speak() } func (d *Dog) SpeakTo(host string) { d.p.SpeakTo(host) } func TestDog(t *testing.T) { dog := new(Dog) //... dog.SpeakTo("a")//a }
package extension
import (
"fmt"
"testing"
)
type Pet struct {}
func (p *Pet) Speak() {
fmt.Print("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(" ", host)
}
type Dog struct {
Pet //匿名复用-->可以拥有父类的特性
}
func TestDog(t *testing.T) {
dog := new(Dog) //...
dog.SpeakTo("a")//a
}
Go错误处理
与其他主要编程语言的差异
1、没有异常机制
2、error类型实现了error接口
3、可以通过errors.New来快速创建错误实例
type error interface { Error() string } errors.New("n must be in the range [0, 1]")
//errors是一个package
Go语言通过内置的错误接口提供了非常简单的错误处理机制
//error类型是一个接口类型
type error interface {
Error() string
}
可以在编码中通过实现error接口类型来生成错误信息
函数通常在最后的返回值中返回错误信息
package main import ( "fmt" ) //定义一个DivideError结构 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) } }
package err_test import ( "errors" "fmt" "testing" ) var LessThanTwoError = errors.New("n should be not less than 2") var LargerThanHunderError = errors.New("n should be not larger than 100") func GetFibonacci(n int) ([]int, error) { if n < 2 { return nil, LessThanTwoError } if n > 100 { return nil, LargerThanHunderError } fibList := []int{1, 1} for i := 2; i < n; i++ { fibList = append(fibList, fibList[i - 2] + fibList[i - 1]) } return fibList, nil } func TestGetFibonacci(t *testing.T){ if v, err := GetFibonacci(-10); err != nil{ if err == LessThanTwoError{ fmt.Println("It is less.") } t.Error(err) } else { t.Log(v) } }
panic和recover
panic vs. os.Exit
os.Exit退出时不会调用defer指定的函数
os.Exit退出时不输出当前调用栈信息
recover
defer func(){ if err := recover(); err != nil{ //恢复 } }
包和依赖管理
package
1、基本复用模块单元 以首字母大写来表明可被包外代码访问
2、代码的package可以和所在的目录不一致(Java中目录名和pachage要保持一致)
3、同一目录里的Go代码的package要保持一致
1、通过go get来获取远程依赖 go get -u强制从网络更新远程依赖
2、注意代码在GitHub上的组织形式,以适应go get
直接以代码路径开始,不要有src
init方法
- 在main被执行前,所有依赖的package的init方法都会被执行
- 不同包的init函数按照包导入的依赖关系决定执行顺序
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数,这点比较特殊
Thread vs. Groutine
1、创建时默认的stack的大小
JDK5以后Java Thread stack默认为1M
Groutine的Stack初始化大小为2K
2、和KSE(Kernel Space Entity)的对应关系
Java Thread是1:1
Groutine是M:N
package groutine_test import ( "fmt" "testing" "time" ) func TestGroutine(t *testing.T) { for i:=0; i<10; i++{ go func (i int) { fmt.Println(i) }(i) } time.Sleep(time.Millisecond * 50) } //----------------------------------------值传递,不会产生竞态条件 1 0 5 2 3 4 7 6 8 9
package groutine_test import ( "fmt" "testing" "time" ) func TestGroutine(t *testing.T) { for i:=0; i<10; i++{ go func ( ) { fmt.Println(i) }() } time.Sleep(time.Millisecond * 50) } //-----------------------------------------i被共享了,竞态条件 10 10 10 10 10 10 10 10 10 10
Go并发
Go语言支持并发,只需要通过go关键字来开启goroutine即可
goroutine是轻量级线程,goroutine的调度是由Golang运行时进行管理的
goroutine语法格式:
go 函数名(参数列表)
Go允许使用go语句开启一个新的运行期线程,即goroutine,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中的所有goroutine共享同一个地址空间
package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { //go f(x, y, z) --> f(x, y, z) go say("world") say("hello") }
共享内存并发机制
Lock Lock lock = ...; lock.lock(); try { //process (thread-safe) }catch(Exception ex){ }finally{ lock.unlock(); }
//
package sync
Mutex
RWLock
package share_mem import ( "sync" "testing" "time" ) func TestCounter(t *testing.T) { counter := 0 for i:=0; i<5000; i++{ go func() { counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) } func TestCounterThreadSafe(t *testing.T) { var mut sync.Mutex counter := 0 for i := 0; i < 5000; i++ { go func() { defer func(){ mut.Unlock() }() mut.Lock() counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) }
//WaitGroup var wg sync.WaitGroup for i := 0; i < 5000; i++ { wg.Add(1) go func() { defer func() { wg.Done() }() ...... }() } wg.Wait()
package share_mem import ( "sync" "testing" "time" ) func TestCounter(t *testing.T) { counter := 0 for i:=0; i<5000; i++{ go func() { counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) } func TestCounterThreadSafe(t *testing.T) { var mut sync.Mutex counter := 0 for i := 0; i < 5000; i++ { go func() { defer func(){ mut.Unlock() }() mut.Lock() counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) } func TestCounterWaitGroup(t *testing.T) { var mut sync.Mutex var wg sync.WaitGroup counter := 0 for i := 0; i < 5000; i++ { wg.Add(1) go func() { defer func(){ mut.Unlock() }() mut.Lock() counter++ wg.Done() }() } wg.Wait() t.Logf("counter = %d", counter) }
CSP并发机制
Communicating sequential processes
CSP vs. Actor
- 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些
- Go中channel是有容量限制并且独立于处理Groutine,而如Erlang、Actor模式中的mailbox容量是无限的,接收进程也总是被动地处理消息
异步返回
private static FutureTask<String> service() { FutureTask<String> task = new FutureTask<String>(()->"Do something"); new Thread(task).start(); return task; } FutureTask<String> ret = service(); System.out.println("Do something else"); System.out.println(ret.get());
通道channel
通道channel是用来传递数据的一个数据结构
通道可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<-用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道
ch <- v //把v发送到通道ch v := <-ch //从ch接收数据并把值赋给v //声明一个通道,使用chan关键字 ch := make(chan int) //默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据
package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum //把sum发送到通道c } 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 //从通道c中接收 fmt.Println(x, y, x+y) }
package main //通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小 //ch := make(chan int, 100) //带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态 //如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值 //如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞 import "fmt" func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 fmt.Println(<- ch) fmt.Println(<- ch) }
package main //Go遍历通道与关闭通道 //Go通过range关键字来实现遍历读取到的数据,类似于与数组或切片 格式 v, ok := <-ch //如果通道接收不到数据后ok就为false,这时通道就可以使用close()函数来关闭 import ( "fmt" ) 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) } }
多路选择和超时
select
//多渠道的选择 select { case ret := <-retCh1: t.Logf("result %s", ret) case ret := <-retCh2: t.Logf("result %s", ret) default: t.Error("No one returned") } //超时控制 select { case ret := <-retCh: t.Logf("result %s", ret) case <-time.After(time.Second * 1): t.Error("time out") }
Go语言开发工具