golang 疑问知识点总结

函数作为值 作为类型

在go 中函数也是一种变量, 我们可以通过type 来定义它, 他的类型就是所有拥有相同的参数, 相同的返回值的一种类型。

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

函数作为类型的好处:可以把这个函数当作类型来传递

函数 当作值和类型在我们写的一些通用接口的时候非常有用。
通过下面的例子可以看到testInt 这个类型是一个函数类型 , 然后两个filter函数的参数和返回值与testInt 类型是一样的, 我们可以实现很多这样的逻辑 可以让我们得程序非常的灵活。

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)
}

switch

逻辑判断比较多 用if else 比较长夜不太好维护
switch 可以很好的解决这个问题 代码:

switch sExpr {
case expr1:
	some instructions
case expr2:
	some other instructions
case expr3:
	some other instructions
default:
	other code
}

注意:
sExpr和expr1、expr2、expr3的类型必须一致。Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果switch没有表达式,它会匹配true

go 中switch 默认相当于每一个case 最后带有break 匹配和成功后不会向下执行其他的case, 而是跳出整个switch 但是可以使用 fallthrough 强制执行case 后面的代码

example :

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")
}

变参

Go 函数支持变参。接受变参的函数有着不定数量的参数。

func myfunc(arg ...int) {}

使用参数

for _, n := range arg {
	fmt.Printf("And the number is: %d\n", n)
}

传参 传值与传指针

当我们传递一个参数值到被调用函数里面时候, 实际传递的是一这个值的copy, 当在被调用函数中修改参数的值的时候, 调用函数中相应实参不会发生 任何变化, 因为数值变化只作用在copy 上面。

要想 在多个函数中修改 一个变量 使用 指针。变量在内存中是存放在一定地址上的, 修改变量实际是修改变量内存的地址,下面是 传递指针方式

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"
}

传递指针的 好处:

  • 指针使得多个多个函数能够操作 同一个对象
  • 传递指针比较轻量级 (8bytes),只是传递内存地址 我们可以用指针传递 体积比较大的结构体。 如果用参数传递的话, 在每次copy 上面就会花费相对较多的系统开销 (内存和时间)。 所以当你传递大的结构体的时候, 用指针是一个明智的选择。
  • Go 语言中channel slice map 这三种类型实现机制类似于指针, 所以可以直接传递, 而不用取地址后然后传递指针。(注意:若函数需要改变 slice 的长度, 则仍需要取地址传递指针)

defer

延迟语句, 可以在函数中添加多个defer 语句。 当函数执行到最后这些defer 语句会按照逆序执行, 最后该函数返回。 特别是当你在打开资源的操作时候, 遇到错误需要提前返回, 在返回前你需要关闭相应的资源, 不然很容易造成资源泄露等问题。
如果 有很多调用defer 那么defer 是采用先进后出的模式。

for i := 0; i < 5; i++ {
	defer fmt.Printf("%d ", i)
}
会输出 4 3 2 1 0

golang 中defer 的使用规则

规则一 当defer 被声明时, 其参数就会被实时解析

package main

import "fmt"

func a() {
	i := 0
	defer fmt.Println(i)
	i++
	return
}
func main() {
	a()
}

根据上面的运行结果进行分析
输出 : 0

分析:
defer 函数 是在return 调用之后调用, 但是这里输出0 而不是1 这是一个我们在defer 后面定义的是一个带变量的函数:fmt.Println(i), 但是这个变量 (i),在defer 被声明的时候, 就已经确定了他的值。 上面这段代码等同于:

func a() {
	i := 0
	defer fmt.Println(0) //因为i=0,所以此时就明确告诉golang在程序退出时,执行输出0的操作
	i++
	return
}

为了更加明确的说明这个问题, 我们继续定义一个defer

package main

import "fmt"

func b() {
	i := 0
	defer fmt.Println(i) //输出0,因为i此时就是0
	i++
	defer fmt.Println(i) //输出1,因为i此时就是1
	return
}
func main() {
	b()
}

运行结果 1 0

可以看到defer 输出的值, 就是定义时候的值。 而不是defer 真正执行的时候变量的值

为什么是 1 0 而不是 01 呢 是因为 规则二 defer 执行顺序为先进后出

规则二 defer 执行顺序为先进后出

可以运行

func b() {
	for i := 0; i < 4; i++ {
	defer fmt.Print(i)
	}
}

输出3 2 1 0

规则三 defer 可以读取有名返回值

package main

import "fmt"

func c() (i int) {
	defer func() { i++ }()
	return 1
}

func main() {
	aa := c()
	fmt.Println(aa)
}

en 你猜猜打印 多少 0, 1, 2?

恭喜你猜对了 2
在开头说过 defer 是在return 调用之后才执行。这里需要明确的是defer 代码块的作用域仍然在函数之内,结合上面的函数, defer 仍然可以读取c 函数内的变量(如果无法读取函数内的变量, 那有如何进行清理呢。。)
当执行 return 1 的时候 i 的值就为1 , 此刻defer 代码块开始执行, 对i 进行自增操作, 因此输出2

注意:
在遇到defer 代码的时候 根据这三个规则 明确得知 期望结果

  • defer 代码块 在 调用reture 之后
  • defer 被声明 参数就会被实时解析
  • 执行顺序先进后出
  • defer

defer return 返回值之间总结

其中有规则三 defer 可以读取 有名返回值

package main

func main() {
	fmt.Println(test())
	fmt.Println(test1())
	fmt.Println(test2())

}

func test() (result int) {  // 打印 2
	defer func() {
		result++
	}()
	return 1
}

// 无名返回值
func test1() (result int) { // 打印 5
	t := 5
	defer func() {
		t = t + 5
	}()
	return t
}

// 有名返回值
func test2() (result int) { // 打印 10
	result = 5
	defer func() {
		result = result + 5
	}()
	return result
}



打印:
2
5
10

在看期望 打印值的时候 要看是有名还是无名的返回值 。

如果函数的返回值是无名的, 则go 语言会在执行return 的时候会执行一个类似于创建一个临时变量作为保存return 值的动作, defer 执行 变量 不会影响这个return 要返回的值。 而有名返回值的函数, 由于返回值是在函数定义的时候已经将该变量进行定义, 在执行return 的时候会先执行返回值保存操作, 而后续的defer 函数 会改变这个返回值 (虽然defer是在return之后执行的,但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值)

证明 defer 是在return 函数之后执行

package main
 
import "fmt"
 
func main() {
	fmt.Println("return a ", a())
	fmt.Println("---------------------------------------")
	b1,b2 := b()
	fmt.Println("return b ", b1, b2)
	fmt.Println("---------------------------------------")
	c1,c2 := c()
	fmt.Println("return c ", c1, c2)
	fmt.Println("---------------------------------------")
}
 
//有名返回值
func a() (i int) {
	defer func() {
		i++
		fmt.Println("defer a 2:", i)
	}()
	
	defer func() {
		i++
		fmt.Println("defer a 1", i)
	}()
	return i
}
 
//无名返回值
func b() (t *int, m int) {
	var i int
	defer func() {
		i++
		fmt.Println("defer b 2:", &i, i)
	}()
	
	defer func() {
		i++
		fmt.Println("defer b 1", &i, i)
	}()
	return &i,i
}
 
func c() (*int,int) {
	var i int
	defer func() {
		i++
		fmt.Println("defer c 2:", &i, i)
	}()
	
	defer func() {
		i++
		fmt.Println("defer c 1", &i, i)
	}()
	return &i,i
}

打印值

defer a 1 1
defer a 2: 2
return a  2
---------------------------------------
defer b 1 0x41602c 1
defer b 2: 0x41602c 2
return b  0x41602c 0
---------------------------------------
defer c 1 0x416048 1
defer c 2: 0x416048 2
return c  0x416048 0
---------------------------------------

defer 打印的值大于 返回值打印的值

测试返回值

func testPr() {
	for i := 0; i < 5; i++ {
		defer func() {
			println(i)
		}()
	}
}

全部打印5

func testPr2() {
	for i := 0; i < 5; i++ {
		defer func(i int) {
			println(i)
		}(i)
	}
}

打印 4 3 2 1 0

Panic 和 Recover

  • panic
    是一个内建函数,可以中断原有的控制流程,进入一个panic状态中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。

  • recover
    是一个内建的函数,可以让进入panic状态的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。

main init 函数

这两个是保留函数 init() 可以应用于所有的package main 函数只能应用于package main

这两个函数在定义时候不能有任何的参数和返回值
从可读性和维护上来说 建议在一个package 中每个文件只写一个init 函数

go 程序会自动调用init() main() 函数 所以不需要再任何地方调用这两个 函数
每个package 中的init 都是可选的 但是package main 就必须包含一个main 函数

程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:

在这里插入图片描述

main 函数 引入包初始化流程图

import

  • 相对路径
    import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import
  • 绝对路径
    import “shorturl/model” //加载gopath/src/shorturl/model模块

特殊的引入

  • 点操作
import(
     . "fmt"
 )

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println(“hello world”)可以省略的写成Println(“hello world”)

  • 别名操作
    别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import(
     f "fmt"
	 )

别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println(“hello world”)

  • _操作
import (
	    "database/sql"
	    _ "github.com/ziutek/mymysql/godrv"
	)

_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。

struct

使用struct 匿名字段 简单省事
不写字段名的方式 也就是匿名字段 也称为嵌入字段

当匿名字段是struct 的时候, 那么这个struct 所拥有的全部字段都被隐式的引入 了当前定义的这个struct

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)
}

struct 不仅能够将 struct 作为匿名字段, 自定义,类型 内置类型都可以作为匿名字段
而且可以在相应的字段上面进行函数操作。

面向对象 method

method : 带有接收者的函数

method 是附属在一个给定的类型上的, 它的语法和函数的声明语法几乎一样, 只是在func 后面增加一个receiver (也就是method 所依从的主体)

method 语法

func (r ReceiverType) funcName(parameters) (results) 
package main

import (
	"fmt"
	"math"
)

type Rectangle struct {
	width, height float64
}

type Circle struct {
	radius float64
}

func (r Rectangle) area() float64 {
	return r.width*r.height
}

func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}


func main() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9, 4}
	c1 := Circle{10}
	c2 := Circle{25}

	fmt.Println("Area of r1 is: ", r1.area())
	fmt.Println("Area of r2 is: ", r2.area())
	fmt.Println("Area of c1 is: ", c1.area())
	fmt.Println("Area of c2 is: ", c2.area())
}

注意

  • 虽然method 的名字一样但是如果接受这不一样 那么method 就不一样
  • method 里面可以访问接受者的字段
  • 调用method 通过 . 访问就像struct 里面访问字段一样

以上方法的receiver 是以值传递, 而非 引用传递 receiver 还可以是指针, 两者的差别在于 指针作为 receiver 会对实例对象的内容发生操作, 而普通类型作为Receiver 仅仅是以副本作为操作对象, 并不对原实例对象发生操作。

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())
}

指针作为receiver

现在让我们回过头来看看SetColor这个method,它的receiver是一个指向Box的指针,是的,你可以使用*Box。想想为啥要使用指针而不是Box本身呢?

我们定义SetColor的真正目的是想改变这个Box的颜色,如果不传Box的指针,那么SetColor接受的其实是Box的一个copy,也就是说method内对于颜色值的修改,其实只作用于Box的copy,而不是真正的Box。所以我们需要传入指针。

这里可以把receiver当作method的第一个参数来看,然后结合前面函数讲解的传值和传引用就不难理解

这里你也许会问了那SetColor函数里面应该这样定义*b.Color=c,而不是b.Color=c,因为我们需要读取到指针相应的值。

你是对的,其实Go里面这两种方式都是正确的,当你用指针去访问相应的字段时(虽然指针没有任何的字段),Go知道你要通过指针去获取这个值,看到了吧,Go的设计是不是越来越吸引你了。

也许细心的读者会问这样的问题,PaintItBlack里面调用SetColor的时候是不是应该写成(&bl[i]).SetColor(BLACK),因为SetColor的receiver是*Box,而不是Box。

你又说对了,这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。

说明:
如果 一个method 的receiver 是 *T, 你可以在一个T 类型的实例变量V 上调用这个method, 而不需要&V 去调用这个method
类似的
如果一个method 的receiver 是T, 你可以在一个 *T 类型的变量P 上面调用这个method, 而不需要 *p 去调用这个method
你不用担心你是调用的指针的method还是不是指针的method,Go知道你要做的一切

接口interface

什么是 interface
简单的说, interface 就是一组method 签名的组合, 我们通过interface 来定义对象的一组行为。
一组抽象方法的集合

interface 类型
interface 定义了一组方法, 如果某个对象实现了某个接口的所有方法, 则此对象实现了此接口

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)
}

interface 可以被任意的对象实现
任意的类型都实现了空 interface

interface 的值

interface 里面到底存储什么值呢?
如果我们定义了一个interface 的变量, 那么这个变量可以存任何实现了这个interface 的任意类型的对象。

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()
	}
}

通过上面 我们知道:
interface 是一组抽象 方法的集合, 但是踏必须由其他非interface 类型实现, 而不能是 自己实现。

一个函数把interface{} 作为参数, 那么他可以接受任意类型的值作为参数, 如果一个函数返回interface{}, 那么也就可以返回任意类型的值。

interface 变量存储的类型
怎么知道interface 存储的什么类型呢
Go 里面有一个语法, 可以直接判断是否是该类型的变量: value, ok = element.(T)

这里value 就是变量的值 , ok 就是一个bool类型, element是interface变量, T 是断言的类型
如果interface 确实存储了T 类型的数值, 那么ok 返回true 否则返回false

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)
			}
		}
	}
	

另外一种实现方式 上面都是if else 不好维护比较累赘 改用switch

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)
			}
		}
	}

注意:

这里需要强调的是: `element.(type)` 语法不能在 switch 外的任何逻辑使用。如果要在switch 外面判断一个类型就是用 if else 里面的代码  使用 `comma-ok` 断言

反射

反射: 能检查程序 在运行时的状态

要去反射一个类型的值(这些值都实现了空的interface),首先需要把它转化成reflect对象(reflect.Type 或者reflect.Value 根据不同情况调用不同的函数)

两种获取方式:

t :=reflect.TypeOf(i) // 得到类型的元数据, 通过t 我们能过获得类型定义里面所有的元素
v  := reflect.ValueOf(i) // 得到实际的值, 通过v 我们获取存储在里面的值, 还可以去改变值

获取反射值能够返回 响应的类型和数值

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
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

反射字段必须是可读写的意思
这样写会报错的

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

array 、slice、 map

array

var arr [n]type

声明方式

a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度

slice

slice 不是真正 意义上面的动态数组, 而是一个引用类型。 slice 总是指向一个底层array, slice 的声明也可以像array  一样  只是不需要长度

// 和声明array一样,只是少了长度

var fslice []int

注意:
slice 是引用类型索引当引用 改变其中元素值的时候, 其他所有的引用都会改变其值

slice 像一个结构体 这个结构体包含3个元素:

  • 一个指针, 指向数组中slice 指定的开始位置
  • 长度 即slice 的长度
  • 最大长度, 也就是slice开始位置到数组的最后位置的长度

slice 内置函数

  • len
  • cap 获取slice 的 最大容量
  • append
  • copy 数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数

map

numbers = make(map[string]int)

map 需要注意的点:

  • map 是无序的
  • map 长度不固定
  • len() 表示拥有key 的数量
  • map 的值方便修改
  • map 和其他基本型别不一样, 它不是thread-safem 在多个go-routine 存取的时候必须使用mutex lock 机制

csharpRating, ok := rating[“C#”] // 第一个是值 第二个是 判断在不在

delete(ranting , “a1”) 删除key 为a1 的元素

map 也是一种引用
如果 两个map 同时指向一个底层, 那么一个改变 另一个也相应改变

new make 区别

make 用于内建类型 的内存分配 (map slice channel)
new 用于所有类型 的内存分配

new :
内建函数new : new(T) 分配了零值 填充的T 类型的内存空间, 并且返回其地址, 即一个 *T。 返回了一个指针 指向新的分配内存的类型T 的 零值。 划重点 了啊: new 返回指针

make
内建函数make(T, args) 和new(T) 有着不同的功能, make 只能创建slice map channel 并且返回 一个有初始值(非0 )的T 类型, 而不是 *T 。 本质上来讲, 导致这3个类型不同的原因是指向数据结构的引用在在使用前必须被初始化。
例如 一个slice 是一个包含指向数据(内部array) 的指针, 长度 、容量的三项描述符; 在这些项目被初始化之前, slice 为nil.
对于slice map channel 来说make 初始化了内部结构, 填充适当的值

make 返回初始化后的非0 值

零值理解

所指的是不是空值 , 而是一种变量未填充之前 的默认值, 通常为0  此处罗列部分 0值
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  ""

总结:
new 返回一个指针, 指向新分配的类型T 的零值
make 返回有初始值(非零)的T 类型 而不是 *T

并发go

goroutine

协程 比线程更小, 十几个线程体现在底层可能就五六个线程, go 语言内部实现了这些goroutine之间内存的共享。执行 goroutine 只需要极少的栈内存(4-5k)可同时运行成千上万个并发任务。 goroutine 比thread 更易用 更高效 更轻便

goroution 是通过go 的runtime管理的一个线程管理器。 goroutine 通过go 关键字实现

go hello(a, b, c)

channel

goroutione 运行在相同的地址空间, 因此访问共享内存必须做好同步。 go 的同步机制 channel.
可以通过 channel 发送或者接受值。
这些值只能是特定的类型:channel 类型

定义一个channel 时候也需要定义发送到channel 值的类型。

注意 必须使用channel 创建channel:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

channel 通过 操作符 <- 来接收和发送数据

	ch <- v // 发送v 到channel ch 
	v := <- ch  // 从ch 中 接收数据并 赋值给v

看具体的例子:

package main

import "fmt"

func sum(a []int, c chan int) {
	total := 0
	for _, v := range a {
		total += v
	}
	c <- total
}
func main() {
	a := []int{7, 2, 6, 35, 8, -1}
	c := make(chan int)
	go sum(a[:len(a)/2], c)
	go sum(a[len(a)/2:], c)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}

思考:
在生产环境 goroutine 就是这样使用的?

默认情况下 channel 接收和发送都是阻塞的,除非另一端已经准备好了, 这样使 groutines 同步变得简单, 而不需要显示的lock,
所谓 阻塞 , 也就是如果读取 (value := < ch) 它将被阻塞, 直到有数据接受。 其次任何发送 (ch <- 5) 将会被阻塞, 直到有数据被读出。 无缓冲channel 是在多个goroutine 之间同步很棒的工具。

buffered channels

上面是非缓存类型的channel go 也允许指定channel 的缓冲大小。 就是channel 可以存储 多少元素
ch :=make(chan bool 4) 创建 可以存储 4个元素的bool 型channel. 在这个channel 中, 前4个元素可以无阻塞的写入。 当写到第5个的时候, 代码会阻塞 直到 其他goroutine 从channel 中读取一些元素 腾出空间。

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!

思考: 在生产环境如何确定这个值的大小呢

range 和close

上面这个例子读取了两次c 不方便 可以使用range 读取

package main

import "fmt"

func fib(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 fib(cap(c), c)
	//for h,i := range c { // 通道只有一个接受
	//	fmt.Println(h,i)
	//}
	for i := range c {
		fmt.Println(i)
	}
}

for i :=range c 能够不断读取channel 里面的数据, 直到channel 被显示关闭。 上面我们显示的关闭通道, 生产者通过内置函数close 关闭channel 关闭channel 之后就无法再发送任何数据了, 在消费方面可以通过语法

v , ok := <- ch 测试 channel 是否关闭。 如果ok 返回false 那么说明channel 已经没有任何数据并且已经被关闭

记住应该在生成者的地方关闭channel , 而不是消费的地方去关闭它这样容易引起 panic
另外就是 记住channel 不像 文件之类 不需要经常去关闭。 只有当你完全没有任何数据发送, 或者你想显示的结束range 循环之类

select

上面都是只有一个 channel 的情况
如果有多个channel 就得使用关键字select

select 用来监听和channel 有关的IO 操作 。 IO 操作发生时候, 触发相应的动作

select 监听 channel 数据的流动

默认select 是阻塞的, 只有当监听的channel 中有发送或者接受可以进行时才会运行。当多个channel 都准备好的时候 select 是随机选择的一个。

分析代码

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)
}

for 在不断的循环 当监听到chan c 有数据读出的时候 她就写入 当监听到 quit chan 有写入的时候 就读出 看case 后面的 行为

当把 fibonacci(c, quit) 调用放在 go func() 上面的时候就会报错 程序会阻塞走不到下面。

在select 还有default 方法 select 类似于switch 功能 。 default 就是当监听的channel 都没有准备好的时候 默认执行的(select 不在阻塞 等待 channel )

select {
case i := <-c:
	// use i
default:
	// 当c阻塞的时候执行这里
}

注意 :
一般default 不会写在里面, select 中的default 子句总是可以 运行的, 因为会消耗CPU 资源

select { //不停的在这里检测
case <-chanl : //检测有没有数据可以读 如果有可以 读的 就执行这个case  处理语句

case chan2 <- 1 : //检测有没有可以写  如果可以 写就 执行这个case 处理 句子

default:
//如果以上都没有符合条件,那么则进行default处理流程
}

超时

有时候会出现 goroutine 阻塞的情况, 那么如果我们如何避免程序进入阻塞的情况呢。 可以利用select 来设置超时

package main

import "time"

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

runtime 中处理goroutine 的函数

  • Goexit
    退出当前执行的goroutine, 但是defer 函数还会继续调用

  • Gosched
    让出当前goroutine 的执行权限, 调度器安排其他等待的任务运行, 并在下次某个时候从该位置恢复执行。

  • NumCPU
    返回cpu 的核数

  • NumGorutine
    返回正在执行的和排队的任务总数

  • GOMAXPROCS
    用来设置可以并行计算的CPU 核数的最大值, 并返回之前的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值