转载:https://www.luozhiyun.com/archives/206java
GOPATH简单理解成Go语言的工做目录,它的值是一个目录的路径,也能够是多个目录路径,每一个目录都表明Go语言的一个工做区(workspace)。linux
在GOPATH放置Go语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。git
好比,一个已存在的代码包的导入路径是github
github.com/labstack/echo,
那么执行命令进行源码的安装算法
go install github.com/labstack/echo
在安装后若是产生了归档文件(以“.a”为扩展名的文件),就会放进该工做区的pkg子目录;若是产生了可执行文件,就可能会放进该工做区的bin子目录。数组
上面该命令在安装后生成的归档文件的相对目录就是 github.com/labstack, 文件名为echo.a。安全
除此以外,归档文件的相对目录与pkg目录之间还有一级目录,叫作平台相关目录。平台相关目录的名称是由build(也称“构建”)的目标操做系统、下划线和目标计算架构的代号组成的。闭包
好比,构建某个代码包时的目标操做系统是Linux,目标计算架构是64位的,那么对应的平台相关目录就是linux_amd64。架构
咱们来看一下下面的代码:并发
var block = "package" func main() { block := "function" { block := "inner" fmt.Printf("The block is %s.\n", block) } fmt.Printf("The block is %s.\n", block) blockFun() }
这个命令源码⽂件中有四个代码块,它们是:全域代码块、main包表明的代码块、main函数表明的代码块,以及在main函 数中的⼀个⽤花括号包起来的代码块。
若是运行该代码,那么会获得以下结果:
The block is inner. The block is function.
在go中,首先,代码引⽤变量的时候总会最优先查找当前代码块中的那个变量。
其次,若是当前代码块中没有声明以此为名的变量,那么程序会沿着代码块的嵌套关系,从直接包含当前代码块的那个代 码块开始,⼀层⼀层地查找。
⼀般状况下,程序会⼀直查到当前代码包表明的代码块。若是仍然找不到,那么Go语⾔的编译器就会报错了。
因此上面的例子中,main代码块首先没法引用到最内层代码块中的变量,最内层的代码块也会优先去找本身代码块的变量。
须要注意一点的是,在不一样的代码块中,变量的名字能够相同可是类型能够不一样的。
其实若是使用过java,就会发现这些都和java的变量申明是同样的。
在java中,咱们能够用instanceof来判断类型,在go中要稍微麻烦一点,具体的以下:
func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} fmt.Printf("The element is %q.\n", container[1]) value2, ok2 := interface{}(container).(map[int]string) value1, ok1 := interface{}(container).([]string) fmt.Println(value1) fmt.Println(value2) if !(ok1 || ok2) { fmt.Printf("Error: unsupported container type: %T\n", container) return } }
也就是说须要经过interface{}(container).(map[int]string)
这样的一句表达式来实现判断类型。
它包括了⽤来把container变量的值转换为空接⼝值的interface{}(container)。 以及⼀个⽤于判断前者的类型是否为map类型 map[int]string 的 .(map[int]string)。
这个表达式返回两个变量,ok表明是否判断成功,若是为true,那么被判断的值将会被自动转换为map[int]string,不然value将被赋 予nil(即“空”)。
咱们通常能够经过以下的方式实现类型转换:
var srcInt = int16(-255) dstInt := int8(srcInt) fmt.Println(dstInt)
在上面的类型转换中须要注意的是,这里是范围大的类型转换成范围小的类型,Go语⾔会把在较⾼ 位置(或者说最左边位置)上的8位⼆进制数直接截掉,因此dstInt的值就是1。
相似的快⼑斩乱麻规则还有:当把⼀个浮点数类型的值转换为整数类型值时,前者的⼩数部分会被所有截掉。
因此在类型转换的时候要时刻提防类型范围的问题。
别名类型与其源类型的区别恐怕只是在名称上,它们 是彻底相同的。
type MyString = string
定义新的类型,这个类型会不一样于其余任何类型。
type MyString2 string // 注意,这⾥没有等号。
若是两个值潜在类型相同,却属于不一样类型,它们之间是能够进⾏类型转换的。以下:
type MyString string str := "BCD" myStr1 := MyString(str) myStr2 := MyString("A" + str)
可是两个类型的潜在类型相同,它们的值之间也不能进⾏判等或⽐较,它们的变量之间也不能赋值。以下:
type MyString2 string str := "BCD" myStr2 := MyString2(str) //myStr2 = str // 这里的赋值不合法,会引起编译错误。 //fmt.Printf("%T(%q) == %T(%q): %v\n", // str, str, myStr2, myStr2, str == myStr2) // 这里的判等不合法,会引起编译错误。
对于集合类的类型[]MyString2与[]string来讲是不能够进⾏类型转换和比较的,由于[]MyString2与[]string的潜在类型不 同,分别是MyString2和string。以下:
type MyString string strs := []string{"E", "F", "G"} var myStrs []MyString //myStrs := []MyString(strs) // 这里的类型转换不合法,会引起编译错误。
通道类型的值自己就是并发安全的,这也是Go语⾔⾃带的、惟⼀⼀个能够满⾜并发安全性的类型。
当容量为0时,咱们能够称通道为⾮缓冲通道,也就是不带缓冲的通道。⽽当容量⼤于0时,咱们能够称为缓冲通道,也就是 带有缓冲的通道。
⼀个通道至关于⼀个先进先出(FIFO)的队列。也就是说,通道中的各个元素值都是严格地按照发送的顺序排列的,先被发 送通道的元素值⼀定会先被接收。元素值的发送和接收都须要⽤到操做符<-。咱们也能够叫它接送操做符。⼀个左尖括号紧 接着⼀个减号形象地表明了元素值的传输⽅向。
func main() { ch1 := make(chan int, 3) //往channel中放入元素 ch1 <- 2 ch1 <- 1 ch1 <- 3 //往channel中获取元素 elem1 := <-ch1 fmt.Printf("The first element received from channel ch1: %v\n", elem1) }
在同⼀时刻,Go语⾔的运⾏时系统(如下简称运⾏时系统)只会执⾏对同⼀个通道的任意个发 送操做中的某⼀个。直到这个元素值被彻底复制进该通道以后,其余针对该通道的发送操做才可能被执⾏。
相似的,在同⼀时刻,运⾏时系统也只会执⾏,对同⼀个通道的任意个接收操做中的某⼀个。
另外,对于通道中的同⼀个元素值来讲,发送操做和接收操做之间也是互斥的。例如,虽然会出现,正在被复制进通道但还未 复制完成的元素值,可是这时它毫不会被想接收它的⼀⽅看到和取⾛。
须要注意的是:进⼊通道的并非在接收操做符右边的那个元素 值,⽽是它的副本。
发送操做和接收操做中对元素值的处理都是不可分割的。
如发送操做要么还没复制元素值,要么已经复制完毕,毫不会出现只复制了⼀部分的状况。
发送操做在彻底完成以前会被阻塞。接收操做也是如此。
发送操做包括了“复制元素值”和“放置副本到通道内部”这两个步骤。
在这两个步骤彻底完成以前,发起这个发送操做的那句代码会⼀直阻塞在那⾥。也就是说,在它以后的代码不会有执⾏的机 会,直到这句代码的阻塞解除。
因为发送操做在这种状况下被阻塞后,它们所在的goroutine会顺序地进⼊通道内部的发送等待队列,因此通知的顺序老是公平的。
// 示例1。 ch1 := make(chan int, 1) ch1 <- 1 //ch1 <- 2 // 通道已满,所以这里会形成阻塞。 // 示例2。 ch2 := make(chan int, 1) //elem, ok := <-ch2 // 通道已空,所以这里会形成阻塞。 //_, _ = elem, ok ch2 <- 1
⽆论是发送操做仍是接收操做,⼀开始执⾏就会被阻塞,直到配对的操做也开始执⾏,才 会继续传递。由此可⻅,⾮缓冲通道是在⽤同步的⽅式传递数据。也就是说,只有收发双⽅对接上了,数据才会被传递。
ch1 := make(chan int ) ch1 <- 10 fmt.Println("End." )//这里会形成阻塞。
对于⼀个已初始化的通道来讲,若是通道一旦关闭,再对它进⾏发送操做,就会 引起panic。
若是试图关闭⼀个已经关闭了的通道,也会引起panic。
因此咱们在关闭通道的时候应当让发送方作这件事,接收操做是能够感知到通道的关闭的,并可以安全退出。
若是通道关闭时,⾥⾯还有元素值未被取出,那么接收表达式的第⼀个结果,仍会是通道中的某⼀个元素值,⽽第⼆个 结果值⼀定会是true。
func main() { ch1 := make(chan int, 2) // 发送方。 go func() { for i := 0; i < 10; i++ { fmt.Printf("Sender: sending element %v...\n", i) ch1 <- i } fmt.Println("Sender: close the channel...") close(ch1) }() // 接收方。 for { elem, ok := <-ch1 if !ok { fmt.Println("Receiver: closed channel") break } fmt.Printf("Receiver: received an element: %v\n", elem) } fmt.Println("End.") }
以下,这表示了这个通道是单向的,而且只能发⽽不能收。
var uselessChan = make(chan<- int, 1)
单向通道最主要的⽤途就是约束其余代码的⾏为。
例如:
func main() { // 初始化一个容量为3的通道 intChan1 := make(chan int, 3) //将通道传入到函数中 SendInt(intChan1) } //使用单向通道限制这个函数只能放入元素到通道中 func SendInt(ch chan<- int) { ch <- rand.Intn(1000) }
在SendInt函数中的代码只能 向参数ch发送元素值,⽽不能从它那⾥接收元素值。这就起到了约束函数⾏为的做⽤。
一样单通道也能够做为函数的返回值:
func main() { intChan2 := getIntChan() for elem := range intChan2 { fmt.Printf("The element in intChan2: %v\n", elem) } } func getIntChan() <-chan int { num := 5 ch := make(chan int, num) for i := 0; i < num; i++ { ch <- i } close(ch) return ch }
函数getIntChan会返回⼀个<-chan int类型的通道,这就意味着获得该通道的程序,只能从通道中接收元素值。
select语句只能与通道联⽤,它⼀般由若⼲个分⽀组成。每次执⾏这种语句的时候,⼀般只有⼀个分⽀中的代码会被运⾏。
咱们经过下面的例子来展现:
func example1() { // 准备好几个通道。 intChannels := [3]chan int{ make(chan int, 1), make(chan int, 1), make(chan int, 1), } // 随机选择一个通道,并向它发送元素值。 index := rand.Intn(3) fmt.Printf("The index: %d\n", index) intChannels[index] <- index // 哪个通道中有可取的元素值,哪一个对应的分支就会被执行。 select { case <-intChannels[0]: fmt.Println("The first candidate case is selected.") case <-intChannels[1]: fmt.Println("The second candidate case is selected.") case elem := <-intChannels[2]: fmt.Printf("The third candidate case is selected, the element is %d.\n", elem) default: fmt.Println("No candidate case is selected!") } }
在使用select语句中,须要注意:
select 里面会根据两个case的返回时间来选择运行,哪一个先返回哪一个就先执行,因此利用这个功能,能够实现超时返回。
func TestSelect(t *testing.T) { //select 里面会根据两个case的返回时间来选择运行 //哪一个先返回哪一个就先执行 //因此利用这个功能,能够实现超时返回 select { case ret:=<-AsyncService(): t.Log(ret) case <-time.After(time.Microsecond*100): t.Error("time out") } } func AsyncService() chan string { retCh := make(chan string,1) go func() { ret := service() fmt.Println("return result.") retCh <- ret fmt.Println("service exited.") }() return retCh }
咱们能够先申明一个函数类型:
type operate func(x, y int) int
而后将这个函数当作参数传入到函数内
func calculate(x int, y int, op operate) (int, error) { if op == nil { return 0, errors.New("invalid operation") } return op(x, y), nil }
能够借闭包在程序运⾏的过程当中,根据须要⽣成功能不一样的函数,继⽽影响后续的程序⾏为。
例如:
type calculateFunc func(x int, y int) (int, error) func genCalculator(op operate) calculateFunc { return func(x int, y int) (int, error) { if op == nil { return 0, errors.New("invalid operation") } return op(x, y), nil } } func main() { x, y = 56, 78 add := genCalculator(op) result, err = add(x, y) fmt.Printf("The result: %d (error: %v)\n", result, err) }
分为两种类型来处理,值类型和引用类型
以下:
func main() { // 示例1。 array1 := [3]string{"a", "b", "c"} fmt.Printf("The array: %v\n", array1) array2 := modifyArray(array1) fmt.Printf("The modified array: %v\n", array2) fmt.Printf("The original array: %v\n", array1) fmt.Println() } // 示例1。 func modifyArray(a [3]string) [3]string { a[1] = "x" return a }
返回的是:
The array: [a b c] The modified array: [a x c] The original array: [a b c]
因为数组是值类型,因此每⼀次复制都会拷⻉它,以及它的全部元素值。我在modify函数中修改的只是原数组的副本⽽已, 并不会对原数组形成任何影响。
以切⽚值为例,如此复制的时候,只是拷⻉了它指向底层数组中某⼀个元素的指针,以及它的⻓度值和容量值,⽽它的底层数 组并不会被拷⻉。
以下:
func main() { slice1 := []string{"x", "y", "z"} fmt.Printf("The slice: %v\n", slice1) slice2 := modifySlice(slice1) fmt.Printf("The modified slice: %v\n", slice2) fmt.Printf("The original slice: %v\n", slice1) fmt.Println() } func modifySlice(a []string) []string { a[1] = "i" return a }
返回:
The slice: [x y z] The modified slice: [x i z] The original slice: [x i z]
因为类modifySlice传入的是一个指针的引用,因此当指针所指向的底层数组发生变化,那么原值就会发生变化。
以下:
func main() { complexArray1 := [3][]string{ []string{"d", "e", "f"}, []string{"g", "h", "i"}, []string{"j", "k", "l"}, } fmt.Printf("The complex array: %v\n", complexArray1) complexArray2 := modifyComplexArray(complexArray1) fmt.Printf("The modified complex array: %v\n", complexArray2) fmt.Printf("The original complex array: %v\n", complexArray1) } func modifyComplexArray(a [3][]string) [3][]string { a[1][1] = "s" a[2] = []string{"o", "p", "q"} return a }
返回:
The complex array: [[d e f] [g h i] [j k l]] The modified complex array: [[d e f] [g s i] [o p q]] The original complex array: [[d e f] [g s i] [j k l]]
实际上仍是和上面的同样的理论,传入modifyComplexArray方法的数组是复制的,可是数组里面的元素传的是引用,因此直接修改引用的切片值会影响到原来的值,可是直接以这样的方式a[2] = []string{"o", "p", "q"}
新建了一个数组则不会改变。