Go语言基础知识点html
go env
命令能够查看Go语言的环境变量设置wget https://studygolang.com/dl/golang/go1.9.2.linux-amd64.tar.gz
mysql
sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local
linux
vi /etc/profile
git
export GOROOT=/usr/local/go
github
export PATH=$PATH:$GOROOT/bin
golang
export GOPATH=/usr/local/go/path
web
source /etc/profile
sql
首先,咱们在解压的时候会获得一个名为go的文件夹,其中包括了全部Go语言相关的一些文件,在这下面又包含不少文件夹和文件,咱们来简单说明其中主要文件夹的做为:mongodb
这在Go中是一个很是重要的概念,在通常状况下,Go源码文件必须放在工做区中
,也就是说,咱们写的项目代码都必须放在咱们所设定的工做区中,虽然对于命令源码文件来讲,这不是必须的。但咱们大多都是前一种状况。工做区其实就是一个对应特定工程的目录,它应包含3个子目录:shell
若是一个源码文件被声明属于main代码包,且该文件代码中包含无参数声明喝结果声明的main函数,则它就是命令源码文件。命令源码文件可经过go run命令直接启动运行
安装完以后,咱们能够进入以下目录 cd $GOPATH/pkg/${GOOS}_${GOARCH} //能够看到以下文件 mymath.a 这个.a文件是应用包,那么咱们如何进行调用呢? 接下来咱们新建一个应用程序来调用这个应用包 新建应用包mathapp cd $GOPATH/src mkdir mathapp cd mathapp vim main.go $GOPATH/src/mathapp/main.go源码: package main import ( "mymath" "fmt" ) func main() { fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) } 能够看到这个的package是main,import里面调用的包是mymath,这个就是相对于$GOPATH/src的路径,若是是多级目录,就在import里面引入多级目录,若是你有多个GOPATH,也是同样,Go会自动在多个$GOPATH/src中寻找。 如何编译程序呢?进入该应用目录,而后执行go build,那么在该目录下面会生成一个mathapp的可执行文件 ./mathapp 输出以下内容 Hello, world. Sqrt(2) = 1.414213562373095 如何安装该应用,进入该目录执行go install,那么在$GOPATH/bin/下增长了一个可执行文件mathapp, 还记得前面咱们把$GOPATH/bin加到咱们的PATH里面了,这样能够在命令行输入以下命令就能够执行 mathapp 也是输出以下内容 Hello, world. Sqrt(2) = 1.414213562373095 这里咱们展现如何编译和安装一个可运行的应用,以及如何设计咱们的目录结构。
25个关键字
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var var和const Go语言基础里面的变量和常量申明 package和import已经有太短暂的接触 func 用于定义函数和方法 return 用于从函数返回 defer 用于相似析构函数 go 用于并发 select 用于选择不一样类型的通信 interface 用于定义接口 struct 用于定义抽象数据类型 break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考2.3流程介绍里面 chan用于channel通信 type用于声明自定义类型 map用于声明map类型数据 range用于读取slice、map、channel数据
package是最基本的分发单位和工程管理中依赖关系的体现
import “./model” //当前文件同一目录的model目录,可是不建议这种方式来import
import “shorturl/model” //加载gopath/src/shorturl/model模块
import "fmt" inport ( "fmt" ) import ( "log" "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "gin-blog/pkg/setting" )
import( . "fmt" )
import( f "fmt" )
import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" )
var
、func
、等等):=
简洁赋值语句在明确类型的地方,能够用于替代 var 定义。:=
这种形式有个局限只能用在函数内部,因此通常用var方式来定义全局变量_
(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。package main func main() { var i int }
var variableName type var vname1, vname2, vname3 type var variableName type = value var vname1, vname2, vname3 type= v1, v2, v3 // Go会根据其相应值的类型来帮你初始化它们 var vname1, vname2, vname3 = v1, v2, v3 vname1, vname2, vname3 := v1, v2, v3
import "fmt" import "os" const i = 100 const pi = 3.1415 const prefix = "Go_" var i int var pi float32 var prefix string 能够分组写成以下形式: import( "fmt" "os" ) const( i = 100 pi = 3.1415 prefix = "Go_" ) var( i int pi float32 prefix string )
const constantName = value const Pi float32 = 3.1415926
int 0 int8 0 int32 0 int64 0 uint 0x0 rune 0 //rune的实际类型是 int32 byte 0x0 // byte的实际类型是 uint8 float32 0 //长度为 4 byte float64 0 //长度为 8 byte bool false string ""
string
var s string = "hello" s[0] = 'c'
s := "hello" c := []byte(s) // 将字符串 s 转换为 []byte 类型 c[0] = 'c' s2 := string(c) // 再转换回 string 类型 fmt.Printf("%s\n", s2)
s := "hello," m := " world" a := s + m fmt.Printf("%s\n", a)
s := "hello" s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操做 fmt.Printf("%s\n", s)
m := `hello world`
整数类型
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
rune
是int32的别称,byte
是uint8的别称float32 float64
complex64 complex128
错误类型
err := errors.New("emit macho dwarf: elf header corrupted") if err != nil { fmt.Print(err) }
表达式 T(v) 将值 v 转换为类型 T
。
一些关于数值的转换:
var i int = 42 var f float64 = float64(i) var u uint = uint(f)
或者,更加简单的形式:
i := 42 f := float64(i) u := uint(f)
与 C 不一样的是 Go 的在不一样类型之间的项目赋值时须要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。
在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。
当右值定义了类型时,新变量的类型与其相同:
var i int j := i // j 也是一个 int
可是当右边包含了未指名类型的数字常量时,新的变量就多是 int 、 float64 或 complex128
。 这取决于常量的精度:
i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128
尝试修改演示代码中 v 的初始值,并观察这是如何影响其类型的。
<http://www.cnblogs.com/zsy/p/5370052.html> Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,const中每增长一行加1: package main import ( "fmt" ) const ( x = iota // x == 0 y = iota // y == 1 z = iota // z == 2 w // 常量声明省略值时,默认和以前一个值的字面相同。这里隐式地说w = iota,所以w == 3。其实上面y和z可一样不用"= iota" ) const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0 const ( h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同 ) const ( a = iota //a=0 b = "B" c = iota //c=2 d, e, f = iota, iota, iota //d=3,e=3,f=3 g = iota //g = 4 ) func main() { fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v) } 除非被显式设置为其它值或iota,每一个const分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,若是前面那个常量的值是iota,则它也被设置为iota。
Go 具备指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 nil
。
var p *int int型的指针
& 符号会生成一个指向其做用对象的指针。
i := 42 p = &i
fmt.Println(p) // 经过指针 p 读取 i p = 21 // 经过指针 p 设置 i
这也就是一般所说的“间接引用”或“非直接引用”。
与 C 不一样,Go 没有指针运算。
package main
import "fmt"
func main() { i, j := 42, 2701
p := &i // point to i fmt.Println(*p) // read i through the pointer *p = 21 // set i through the pointer fmt.Println(i) // see the new value of i p = &j // point to j *p = *p / 37 // divide j through the pointer fmt.Println(j) // see the new value of j
}
Go语言中,也和C或者其余语言同样,咱们能够声明新的类型,做为其它类型的属性或字段的容器。例如,咱们能够建立一个自定义类型person表明一我的的实体。这个实体拥有属性:姓名和年龄。这样的类型咱们称之struct
type person struct { name string age int } var P person // P如今就是person类型的变量了 P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. P.age = 25 // 赋值"25"给变量P的age属性 fmt.Printf("The person's name is %s", P.name) // 访问P的name属性.
P := person{"Tom", 25}
P := person{age:24, name:"Tom"}
P := new(person)
package main import "fmt" type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,那么默认Student就包含了Human的全部字段 speciality string } func main() { // 咱们初始化一个学生 mark := Student{Human{"Mark", 25, 120}, "Computer Science"} // 咱们访问相应的字段 fmt.Println("His name is ", mark.name) fmt.Println("His age is ", mark.age) fmt.Println("His weight is ", mark.weight) fmt.Println("His speciality is ", mark.speciality) // 修改对应的备注信息 mark.speciality = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ", mark.speciality) // 修改他的年龄信息 fmt.Println("Mark become old") mark.age = 46 fmt.Println("His age is", mark.age) // 修改他的体重信息 fmt.Println("Mark is not an athlet anymore") mark.weight += 60 fmt.Println("His weight is", mark.weight) }
mark.Human = Human{"Marcus", 55, 220} mark.Human.age -= 1
package main import "fmt" type Skills []string type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,struct Skills // 匿名字段,自定义的类型string slice int // 内置类型做为匿名字段 speciality string } func main() { // 初始化学生Jane jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} // 如今咱们来访问相应的字段 fmt.Println("Her name is ", jane.name) fmt.Println("Her age is ", jane.age) fmt.Println("Her weight is ", jane.weight) fmt.Println("Her speciality is ", jane.speciality) // 咱们来修改他的skill技能字段 jane.Skills = []string{"anatomy"} fmt.Println("Her skills are ", jane.Skills) fmt.Println("She acquired two new ones ") jane.Skills = append(jane.Skills, "physics", "golang") fmt.Println("Her skills now are ", jane.Skills) // 修改匿名内置类型字段 jane.int = 3 fmt.Println("Her preferred number is", jane.int) }
package main import "fmt" type Human struct { name string age int phone string // Human类型拥有的字段 } type Employee struct { Human // 匿名字段Human speciality string phone string // 雇员的phone字段 } func main() { Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} fmt.Println("Bob's work phone is:", Bob.phone) // 若是咱们要访问Human的phone字段 fmt.Println("Bob's personal phone is:", Bob.Human.phone) }
package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} p := &v p.X = 1e9 fmt.Println(v) } 结构体字段能够经过结构体指针来访问。
u := &User{UserId: 1, UserName: "tony"} j, _ := json.Marshal(u) fmt.Println(string(j)) // 输出内容:{"user_id":1,"user_name":"tony"} 若是在属性中不增长标签说明,则输出: {"UserId":1,"UserName":"tony"} 能够看到直接用struct的属性名作键值。 其中还有一个bson的声明,这个是用在将数据存储到mongodb使用的。
t := reflect.TypeOf(u) field := t.Elem().Field(0) fmt.Println(field.Tag.Get("json")) fmt.Println(field.Tag.Get("bson"))
1 package main 2 import ( 3 "fmt" 4 "reflect" // 这里引入reflect模块 5 ) 6 type User struct { 7 Name string "user name" //这引号里面的就是tag 8 Passwd string "user passsword" 9 } 10 func main() { 11 user := &User{"chronos", "pass"} 12 s := reflect.TypeOf(user).Elem() //经过反射获取type定义 13 for i := 0; i < s.NumField(); i++ { 14 fmt.Println(s.Field(i).Tag) //将tag输出出来 15 } 16 }
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 type S struct { 10 F string `species:"gopher" color:"blue"` 11 } 12 13 s := S{} 14 st := reflect.TypeOf(s) 15 field := st.Field(0) 16 fmt.Println(field.Tag.Get("color"), field.Tag.Get("species")) 17 18 }
var arr [n]type
:在[n]type中,n表示数组的长度,type表示存储元素的类型package main import "fmt" func main() { var a [2]string a[0] = "Hello" a[1] = "World" fmt.Println(a[0], a[1]) fmt.Println(a) }
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为一、二、3,其它默认为0 c := [...]int{4, 5, 6} // 能够省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度☆☆☆☆☆
// 声明了一个二维数组,该数组以两个数组做为元素,其中每一个数组中又有4个int类型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} // 上面的声明能够简化,直接忽略内部的类型 easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
package main import "fmt" func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) for i := 0; i < len(p); i++ { fmt.Printf("p[%d] == %d\n", i, p[i]) } }
// 声明一个数组 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个slice var aSlice, bSlice []byte // 演示一些简便操做 aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了所有的元素 // 从slice中获取slice aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7 bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f bSlice = aSlice[0:5] // 对slice的slice能够在cap范围内扩展,此时bSlice包含:d,e,f,g,h bSlice = aSlice[:] // bSlice包含全部aSlice的元素: d,e,f,g
nil
。package main import "fmt" func main() { var z []int fmt.Println(z, len(z), cap(z)) if z == nil { fmt.Println("nil!") } }
slice 由函数 make 建立。这会分配一个零长度的数组而且返回一个 slice 指向这个数组:
a := make([]int, 5) // len(a)=5
为了指定容量,可传递第三个参数到 make
:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
package main import "fmt" func main() { a := make([]int, 5) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := b[:2] printSlice("c", c) d := c[2:5] printSlice("d", d) } func printSlice(s string, x []int) { fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) }
从Go1.2开始slice支持了三个参数的slice,以前咱们一直采用这种方式在slice或者array基础上来获取一个slice
var array [10]int slice := array[2:4]
这个例子里面slice的容量是8,新版本里面能够指定这个容量
slice = array[2:4:7]
上面这个的容量就是7-2,即5。这样这个产生的新的slice就没办法访问最后的三个元素。
若是slice是这样的形式array[:i:j],即第一个参数为空,默认值就是0。
对于slice有几个有用的内置函数:
append 向slice里面追加一个或者多个元素,而后返回一个和slice同样类型的slice
若是 s 的底层数组过小,而不能容纳全部值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。
package main import "fmt" func main() { var a []int printSlice("a", a) // append works on nil slices. a = append(a, 0) printSlice("a", a) // the slice grows as needed. a = append(a, 1) printSlice("a", a) // we can add more than one element at a time. a = append(a, 2, 3, 4) printSlice("a", a) } func printSlice(s string, x []int) { fmt.Printf("%s len=%d cap=%d %v\n", s, len(x), cap(x), x) }
for 循环的 range 格式能够对 slice 或者 map 进行迭代循环。
package main import "fmt" var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} func main() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }
能够经过赋值给 _ 来忽略序号和值。 若是只须要索引值,去掉“, value”的部分便可。
package main import "fmt" func main() { pow := make([]int, 10) for i := range pow { pow[i] = 1 << uint(i) } for _, value := range pow { fmt.Printf("%d\n", value) } }
package main import "fmt" type Vertex struct { Lat, Long float64 } var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Bell Labs"] = Vertex{ 40.68433, -74.39967, } fmt.Println(m["Bell Labs"]) }
// 一、初始化一个字典 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // 二、插入元素 m[key] = elem // 三、得到元素 elem = m[key] // 四、map有两个返回值,第二个返回值,若是存在ok为true,若是不存在key,那么ok为false,而且 elem 是 map 的元素类型的零值 csharpRating, ok := rating["C#"] if ok { fmt.Println("C# is in the map and its rating is ", csharpRating) } else { fmt.Println("We have no rating associated with C# in the map") } // 五、删除元素 delete(rating, "C") // 删除key为C的元素
make用于内建类型(map、slice 和channel)的内存分配。new用于各类类型的内存分配。
内建函数new本质上说跟其它语言中的同名函数功能同样:new(T)分配了零值填充的T类型的内存空间,而且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点很是重要:
new返回指针。
内建函数make(T, args)与new(T)有着不一样的功能,make只能建立slice、map和channel,而且返回一个有初始值(非零)的T类型,而不是*T。本质来说,致使这三个类型有所不一样的缘由是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化以前,slice为nil。对于slice、map和channel来讲,make初始化了内部的数据结构,填充适当的值。
make返回初始化后的(非零)值。
Go里面最强大的一个控制逻辑就是for,它既能够用来循环读取数据,又能够看成while来控制逻辑,还能迭代操做
package main import "fmt" func main(){ sum := 0; for index:=0; index < 10 ; index++ { sum += index } fmt.Println("sum is equal to ", sum) } // 输出:sum is equal to 45
sum := 1 for ; sum < 1000; { sum += sum }
package main import "fmt" func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
for k,v:=range map { fmt.Println("map's key:",k) fmt.Println("map's val:",v) }
for _, v := range map{ fmt.Println("map's val:", v) }
package main func main() { for { } }
if x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") }
// 计算获取值x,而后根据x返回的大小,判断是否大于10。 if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") } //这个地方若是这样调用就编译出错了,由于x是条件里面的变量 fmt.Println(x)
if integer == 3 { fmt.Println("The integer is equal to 3") } else if integer < 3 { fmt.Println("The integer is less than 3") } else { fmt.Println("The integer is greater than 3") }
switch sExpr { case expr1: some instructions case expr2: some other instructions case expr3: some other instructions default: other code }
integer := 6 switch integer { case 4: fmt.Println("The integer was <= 4") fallthrough case 5: fmt.Println("The integer was <= 5") fallthrough case 6: fmt.Println("The integer was <= 6") fallthrough case 7: fmt.Println("The integer was <= 7") fallthrough case 8: fmt.Println("The integer was <= 8") fallthrough default: fmt.Println("default case") }
switch true
同样。package main import ( "fmt" "time" ) func main() { t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") } }
func myFunc() { i := 0 Here: //这行的第一个词,以冒号结束做为标签 println(i) i++ goto Here //跳转到Here去 }
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { //这里是处理逻辑代码 //返回多个值 return value1, value2 }
package main import "fmt" // 返回a、b中最大值. func max(a, b int) int { if a > b { return a } return b } func main() { x := 3 y := 4 z := 5 max_xy := max(x, y) //调用函数max(x, y) max_xz := max(x, z) //调用函数max(x, z) fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它 }
func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B return }
func myfunc(arg ...int) {}
for _, n := range arg { fmt.Printf("And the number is: %d\n", n) }
package main import "fmt" //简单的一个函数,实现了参数+1的操做 func add1(a *int) int { // 请注意, *a = *a+1 // 修改了a的值 return *a // 返回新值 } func main() { x := 3 fmt.Println("x = ", x) // 应该输出 "x = 3" x1 := add1(&x) // 调用 add1(&x) 传x的地址 fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" fmt.Println("x = ", x) // 应该输出 "x = 4" }
func ReadWrite() bool { file.Open("file") // 作一些工做 if failureX { file.Close() return false } if failureY { file.Close() return false } file.Close() return true } 咱们看到上面有不少重复的代码,Go的defer有效解决了这个问题。使用它后,不但代码量减小了不少,并且程序变得更优雅。在defer后指定的函数会在函数退出前调用。 func ReadWrite() bool { file.Open("file") defer file.Close() if failureX { return false } if failureY { return false } return true }
因此以下代码会输出4 3 2 1 0 for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) }
例如,函数 adder 返回一个闭包。每一个闭包都被绑定到其各自的 sum 变量上。 package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
package main import "fmt" type testInt func(int) bool // 声明了一个函数类型 func isOdd(integer int) bool { if integer%2 == 0 { return false } return true } func isEven(integer int) bool { if integer%2 == 0 { return true } return false } // 声明的函数类型在这个地方当作了一个参数 func filter(slice []int, f testInt) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main(){ slice := []int {1, 2, 3, 4, 5, 7} fmt.Println("slice = ", slice) odd := filter(slice, isOdd) // 函数当作值来传递了 fmt.Println("Odd elements of slice are: ", odd) even := filter(slice, isEven) // 函数当作值来传递了 fmt.Println("Even elements of slice are: ", even) } 函数当作值和类型在咱们写一些通用接口的时候很是有用,经过上面例子咱们看到testInt这个类型是一个函数类型,而后两个filter函数的参数和返回值与testInt类型是同样的,可是咱们能够实现不少种的逻辑,这样使得咱们的程序变得很是的灵活。
下面这个函数演示了如何在过程当中使用panic var user = os.Getenv("USER") func init() { if user == "" { panic("no value for $USER") } } 下面这个函数检查做为其参数的函数在执行时是否会产生panic: func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() //执行函数f,若是f中出现了panic,那么就能够恢复回来 return }
type ages int type money float32 type months map[string]int m := months { "January":31, "February":28, ... "December":31, }
package main import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := &Vertex{3, 4} fmt.Println(v.Abs()) }
package main import ( "fmt" "math" ) type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
package main import "fmt" const( WHITE = iota BLACK BLUE RED YELLOW ) type Color byte type Box struct { width, height, depth float64 color Color } type BoxList []Box //a slice of boxes func (b Box) Volume() float64 { return b.width * b.height * b.depth } func (b *Box) SetColor(c Color) { b.color = c } func (bl BoxList) BiggestColor() Color { v := 0.00 k := Color(WHITE) for _, b := range bl { if bv := b.Volume(); bv > v { v = bv k = b.color } } return k } func (bl BoxList) PaintItBlack() { for i := range bl { bl[i].SetColor(BLACK) } } func (c Color) String() string { strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} return strings[c] } func main() { boxes := BoxList { Box{4, 4, 4, RED}, Box{10, 10, 1, YELLOW}, Box{1, 1, 20, BLACK}, Box{10, 10, 1, BLUE}, Box{10, 30, 1, WHITE}, Box{20, 20, 20, YELLOW}, } fmt.Printf("We have %d boxes in our set\n", len(boxes)) fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String()) fmt.Println("The biggest one is", boxes.BiggestColor().String()) fmt.Println("Let's paint them all black") boxes.PaintItBlack() fmt.Println("The color of the second one is", boxes[1].color.String()) fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) }
副本
做为操做对象,并不对原实例对象发生操做package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } type Employee struct { Human //匿名字段 company string } //在human上面定义了一个method func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} mark.SayHi() sam.SayHi() }
package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } type Employee struct { Human //匿名字段 company string } //Human定义method func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Employee的method重写Human的method func (e *Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //Yes you can split into 2 lines here. } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} mark.SayHi() sam.SayHi() }
type Human struct { name string age int phone string } type Student struct { Human //匿名字段Human school string loan float32 } type Employee struct { Human //匿名字段Human company string money float32 } //Human对象实现Sayhi方法 func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } // Human对象实现Sing方法 func (h *Human) Sing(lyrics string) { fmt.Println("La la, la la la, la la la la la...", lyrics) } //Human对象实现Guzzle方法 func (h *Human) Guzzle(beerStein string) { fmt.Println("Guzzle Guzzle Guzzle...", beerStein) } // Employee重载Human的Sayhi方法 func (e *Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //此句能够分红多行 } //Student实现BorrowMoney方法 func (s *Student) BorrowMoney(amount float32) { s.loan += amount // (again and again and...) } //Employee实现SpendSalary方法 func (e *Employee) SpendSalary(amount float32) { e.money -= amount // More vodka please!!! Get me through the day! } // 定义interface type Men interface { SayHi() Sing(lyrics string) Guzzle(beerStein string) } type YoungChap interface { SayHi() Sing(song string) BorrowMoney(amount float32) } type ElderlyGent interface { SayHi() Sing(song string) SpendSalary(amount float32) }
package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } //Human实现SayHi方法 func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human实现Sing方法 func (h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } //Employee重载Human的SayHi方法 func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee实现 // 由于这三个类型都实现了这两个方法 type Men interface { SayHi() Sing(lyrics string) } func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定义Men类型的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存储Employee i = tom fmt.Println("This is tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //这三个都是不一样类型的元素,可是他们实现了interface同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } }
// 定义a为空接口 var a interface{} var i int = 5 s := "Hello world" // a能够存储任意类型的数值 a = i a = s
咱们知道interface的变量里面能够存储任意类型的数值(该类型实现了interface)。那么咱们怎么反向知道这个变量里面实际保存了的是哪一个类型的对象呢?目前经常使用的有两种方法:
package main import ( "fmt" "strconv" ) type Element interface{} type List [] Element type Person struct { name string age int } //定义了String方法,实现了fmt.Stringer func (p Person) String() string { return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } func main() { list := make(List, 3) list[0] = 1 // an int list[1] = "Hello" // a string list[2] = Person{"Dennis", 70} for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d\n", index, value) } else if value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { fmt.Printf("list[%d] is of a different type\n", index) } } }
element.(type)
语法不能在switch外的任何逻辑里面使用,若是你要在switch外面判断一个类型就使用comma-ok
package main import ( "fmt" "strconv" ) type Element interface{} type List [] Element type Person struct { name string age int } //打印 func (p Person) String() string { return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } func main() { list := make(List, 3) list[0] = 1 //an int list[1] = "Hello" //a string list[2] = Person{"Dennis", 70} for index, element := range list{ switch value := element.(type) { case int: fmt.Printf("list[%d] is an int and its value is %d\n", index, value) case string: fmt.Printf("list[%d] is a string and its value is %s\n", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) default: fmt.Println("list[%d] is of a different type", index) } } }
咱们能够看到源码包container/heap里面有这样的一个定义 type Interface interface { sort.Interface //嵌入字段sort.Interface Push(x interface{}) //a Push method to push elements into the heap Pop() interface{} //a Pop elements that pops elements from the heap } 咱们看到sort.Interface其实就是嵌入字段,把sort.Interface的全部method给隐式的包含进来了。也就是下面三个方法: type Interface interface { // Len is the number of elements in the collection. Len() int // Less returns whether the element with index i should sort // before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) } 另外一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface: package main import ( "fmt" "os" ) type Reader interface { Read(b []byte) (n int, err error) } type Writer interface { Write(b []byte) (n int, err error) } type ReadWriter interface { Reader Writer } func main() { var w Writer // os.Stdout 实现了 Writer w = os.Stdout fmt.Fprintf(w, "hello, writer\n") }
t := reflect.TypeOf(i) //获得类型的元数据,经过t咱们能获取类型定义里面的全部元素 v := reflect.ValueOf(i) //获得实际的值,经过v咱们获取存储在里面的值,还能够去改变值
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) 若是要修改相应的值,必须这样写 var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1) 由于传值,传的是拷贝的副本,若是想改变就必须改变原始值
type Stringer struct { String() string }
package main import ( "fmt" "strconv" ) type Human struct { name string age int phone string } // 经过这个方法 Human 实现了 fmt.Stringer func (h Human) String() string { return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱" } func main() { Bob := Human{"Bob", 39, "000-7777-XXX"} fmt.Println("This Human is : ", Bob) }
type error interface { Error() string } (与 fmt.Stringer 相似,`fmt` 包在输出时也会试图匹配 `error`。) 一般函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 `nil`, 来进行错误处理。 i, err := strconv.Atoi("42") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) } fmt.Println("Converted integer:", i) error 为 nil 时表示成功;非 nil 的 error 表示错误。 package main import ( "fmt" "time" ) type MyError struct { When time.Time What string } func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } func run() error { return &MyError{ time.Now(), "it didn't work", } } func main() { if err := run(); err != nil { fmt.Println(err) } }
go f(x, y, z)
ci := make(chan int) cs := make(chan string) cf := make(chan interface{})
ch <- v // 发送v到channel ch. v := <-ch // 从ch中接收数据,并赋值给v
无缓冲的信道永远不会存储数据,只负责数据的流通,为何这么讲呢?
默认状况下,channel接收和发送数据都是阻塞的,除非另外一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不须要显式的lock。所谓阻塞,也就是若是读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。☆☆☆☆☆无缓冲channel是在多个goroutine之间同步很棒的工具☆☆☆☆☆
package main import "fmt" func sum(a []int, c chan int) { total := 0 for _, v := range a { total += v } c <- total // send total to c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) }
var ch chan int = make(chan int) func foo() { ch <- 0 // 向ch中加数据,若是没有其余goroutine来取走这个数据,那么挂起foo, 直到main函数把0这个数据拿走 } func main() { go foo() <- ch // 从ch取数据,若是ch中还没放数据,那就挂起main线,直到foo函数中放数据为止 }
死锁1 func main() { ch := make(chan int) <- ch // 阻塞main goroutine, 信道c被锁 }
死锁2 var ch1 chan int = make(chan int) var ch2 chan int = make(chan int) func say(s string) { fmt.Println(s) ch1 <- <- ch2 // ch1 等待 ch2流出的数据 } func main() { go say("hello") <- ch1 // 堵塞主线 }
ch := make(chan type, value) 当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入
package main import "fmt" func main() { c := make(chan int, 2)//修改2为1就报错,修改2为3能够正常运行 c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } //修改成1报以下的错误: //fatal error: all goroutines are asleep - deadlock!
package main import ( "fmt" ) func fibonacci(n int, c chan int) { x, y := 1, 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) } } for i := range c可以不断的读取channel里面的数据,直到该channel被显式的关闭(这句话说明这个循环会一直存在,直到你调用了close)。上面代码咱们看到能够显式的关闭channel,生产者经过内置函数close关闭channel。关闭channel以后就没法再发送任何数据了,在消费方能够经过语法v, ok := <-ch测试channel是否被关闭。若是ok返回false,那么说明channel已经没有任何数据而且已经被关闭。
只有一个channel
的状况,那么若是存在多个channel
的时候,咱们该如何操做呢,Go里面提供了一个关键字select,经过select能够监听channel上的数据流动。package main import "fmt" func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
select { case i := <-c: // use i default: // 当c阻塞的时候执行这里 }
func main() { c := make(chan int) o := make(chan bool) go func() { for { select { case v := <- c: println(v) case <- time.After(5 * time.Second): println("timeout") o <- true break } } }() <- o }
runtime包中有几个处理goroutine的函数: