Golangѧϰ

匿名 (未验证) 提交于 2019-12-02 22:56:40

这篇文档零散的记载了一些知识点以及容易犯错的语法知识

结构体绑定方法分为两种情况:指针绑定和赋值绑定,下面是两种情况的地址情况

import "fmt" import "unsafe"  type MyStruct struct {     id int }  func (self MyStruct) Assignment() {     fmt.Println(unsafe.Pointer(&self)) }  func (self MyStruct) modify_value_assignment() {     self.id = 2 }  func (self *MyStruct) Pointer() {     fmt.Println(unsafe.Pointer(&self)) }  func (self *MyStruct) modify_value_pointer() {     self.id = 2 }  func main() {      s0 := MyStruct{1}     fmt.Println(s0)     fmt.Println(unsafe.Pointer(&s0)) //打印地址      s0.modify_value_assignment()     fmt.Println(s0) // {1}     s0.Assignment()      s0.modify_value_pointer()     fmt.Println(s0) // {2}     s0.Pointer() }  执行的输出是   {1}   0xc420084008   {1}   0xc420084038   {2}  0xc420092020  

由此可见

  • 指针绑定 拷贝了指向结构体的指针,二者指向同一块内存
  • 赋值绑定 拷贝了整个结构体变量(内存),新的结构体变量改变与原变量无关

  • slice
    切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。
    切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个与指向数组的动态窗口。
    给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个长度可变的数组。
    Slice 的数据结构定义如下:
type slice struct {     array unsafe.Pointer     len   int     cap   int }

切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。
- map
hashmap

  • new
    是一个用来分配内存的内建函数,但是与C++不一样的是,它并不初始化内存,只是将其置零。也就是说,new(T)会为T类型的新项目,分配被置零的存储,并且返回它的地址,一个类型为*T的值。在Go的术语中,其返回一个指向新分配的类型为T的指针,这个指针指向的内容的值为zerovalue(zerovalue指的是各个类型的默认初始值),注意并不是指针为零,而是指针所指的内存.
    比如,对于bool类型,零值为false;int的零值为0;string的零值是空字符串:
b := new(bool) fmt.Println(*b) i := new(int) fmt.Println(*i) 输出: false 0
  • make
    内建函数make(T,len,cap)与new(T)的用途不一样。它只用来创建slice,map和channel,并且返回一个初始化的(而不是置零)类型为T的值(而不是*T)。
    之所以有所不同,是因为这三个类型的背后引用了使用前必须初始化的数据结构。例如,slice是一个三元描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是nil的。对于slice,map和channel,make初始化这些内部数据结构,并准备好可用的值。

  • goroutine
    类似我们熟知的线程,但是更轻。
    以下的程序,我们串行地去执行两次loop函数:
func loop() {     for i := 0; i < 10; i++ {         fmt.Printf("%d ", i)     } } func main() {     loop()     loop() }

毫无疑问,输出会是这样的:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
下面我们把一个loop放在一个goroutine里跑,我们可以使用关键字go来定义并启动一个goroutine:

func main() {     go loop() // 启动一个goroutine     loop() }

这次的输出变成了:
0 1 2 3 4 5 6 7 8 9
可是为什么只输出了一趟呢?明明我们主线跑了一趟,也开了一个goroutine来跑一趟啊。
原来,在goroutine还没来得及跑loop的时候,主函数已经退出了。
main函数退出地太快了,我们要想办法阻止它过早地退出,一个办法是让main等待一下:

func main() {     go loop()     loop()     time.Sleep(time.Second) // 停顿一秒 }

这次确实输出了两趟,目的达到了。
可是采用等待的办法并不好,如果goroutine在结束的时候,告诉下主线说“Hey, 我要跑完了!”就好了, 即所谓阻塞主线的办法,回忆下我们Python里面等待所有线程执行完毕的写法:

for thread in threads:     thread.join()

是的,我们也需要一个类似join的东西来阻塞住主线。那就是信道,将在下面的内容介绍。

- channel
channel是goroutine之间互相通讯的东西。类似我们Unix上的管道,用来goroutine之间发消息和接收消息。其实,就是在做goroutine之间的内存共享。
- 无缓冲信道
使用make来建立一个信道:

var channel chan int = make(chan int)
// 或
channel := make(chan int)

那如何向信道存消息和取消息呢? 一个例子:
“`
func main() {
var messages chan string = make(chan string)

    go func(message string) {         messages <- message // 存消息     }("Ping!")      fmt.Println(<-messages) // 取消息 } ``` 默认的,信道的存消息和取消息都是阻塞的,叫做无缓冲的信道,也就是说, 无缓冲的信道在取消息和存消息的时候都会挂起当前的goroutine,除非另一端已经准备好。  比如以下的main函数和foo函数: ``` 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函数中放数据为止 } ``` 那既然信道可以阻塞当前的goroutine, 那么「如何让goroutine告诉主线我执行完毕了」 的问题, 使用一个信道来告诉主线即可: ``` var complete chan int = make(chan int)  func loop() {     for i := 0; i < 10; i++ {         fmt.Printf("%d ", i)     }     complete <- 0 // 执行完毕了,发个消息 }  func main() {     go loop()     <- complete // 直到线程跑完, 取到消息. main在此阻塞住 } ``` 如果不用信道来阻塞主线的话,主线就会过早跑完,loop线都没有机会执行  <br> 无缓冲信道从不存储数据,流入的数据必须要流出才可以。   观察以下的程序: ``` var ch chan int = make(chan int)  func foo(id int) { //id: 这个routine的标号     ch <- id }  func main() {     // 开启5个routine     for i := 0; i < 5; i++ {         go foo(i)     }     // 取出信道中的数据     for i := 0; i < 5; i++ {         fmt.Print(<- ch)     } } ``` 我们开了5个goroutine,然后又依次取数据。其实整个的执行过程细分的话,5个线的数据 依次流过信道ch, main打印之, 而宏观上我们看到的即 无缓冲信道的数据是先到先出,但是 无缓冲信道并不存储数据,只负责数据的流通.   <br>    - 缓冲信道   缓冲这个词意思是,缓冲信道不仅可以流通数据,还可以缓存数据。它是有容量的,存入一个数据的话 , 可以先放在信道里,不必阻塞当前线而等待该数据取走。   当缓冲信道达到满的状态的时候,就会表现出阻塞了,因为这时再也不能承载更多的数据了,「你们必须把 数据拿走,才可以流入数据」。   在声明一个信道的时候,我们给make以第二个参数来指明它的容量(默认为0,即无缓冲):   ` var ch chan int = make(chan int, 2) // 写入2个元素都不会阻塞当前goroutine   `   存储个数达到2的时候会阻塞 如下的例子,缓冲信道ch可以无缓冲的流入3个元素: ``` func main() {     ch := make(chan int, 3)     ch <- 1     ch <- 2     ch <- 3 } ``` 如果你再试图流入一个数据的话,信道ch会阻塞main线, 报死锁。也就是说,缓冲信道会在满容量的时候加锁。   其实,缓冲信道是先进先出的,我们可以把缓冲信道看作为一个线程安全的队列: ``` func main() {     ch := make(chan int, 3)     ch <- 1     ch <- 2     ch <- 3      fmt.Println(<-ch) // 1     fmt.Println(<-ch) // 2     fmt.Println(<-ch) // 3 } ``` - 信道数据读取和信道关闭 你也许发现,上面的代码一个一个地去读取信道简直太费事了,Go语言允许我们使用range来读取信道: ``` func main() {     ch := make(chan int, 3)     ch <- 1     ch <- 2     ch <- 3      for v := range ch {         fmt.Println(v)     } } ``` <br> 

Interface

  • 用法一:
    假设某公司有两个员工,一个普通员工和一个高级员工,但是基本薪资是相同的,高级员工多拿奖金。计算公司为员工的总开支。
// 薪资计算器接口 type SalaryCalculator interface {     CalculateSalary() int } // 普通员工 type Contract struct {     empId  int     basicpay int } // 有技术证的员工 type Permanent struct {     empId  int     basicpay int     jj int // 奖金 }  func (p Permanent) CalculateSalary() int {     return p.basicpay + p.jj }  func (c Contract) CalculateSalary() int {     return c.basicpay } // 总开支 func totalExpense(s []SalaryCalculator) {     expense := 0     for _, v := range s {         expense = expense + v.CalculateSalary()     }     fmt.Printf("总开支 $%d", expense) }  func main() {     pemp1 := Permanent{1,3000,10000}     pemp2 := Permanent{2, 3000, 20000}     cemp1 := Contract{3, 3000}     employees := []SalaryCalculator{pemp1, pemp2, cemp1}     totalExpense(employees) }
  • 用法二:
type Test interface {     Tester() }  type MyFloat float64  func (m MyFloat) Tester() {     fmt.Println(m) }  func describe(t Test) {     fmt.Printf("Interface 类型 %T ,  值: %v\n", t, t) }  func main() {     var t Test     f := MyFloat(89.7)     t = f     describe(t)     t.Tester() }
  • 空接口
    具有0个方法的接口称为空接口。它表示为interface{}。由于空接口有0个方法,所有类型都实现了空接口。
func describe(i interface{}) {     fmt.Printf("Type = %T, value = %v\n", i, i) }  func main() {     // 任何类型的变量传入都可以     s := "Hello World"     i := 55     strt := struct {         name string     }{         name: "Naveen R",     }     describe(s)     describe(i)     describe(strt) }


  • defer
    在函数return之后,返回调用它的函数之前运行
  • panic
    当golang中遇到panic时,如果不进行recover,便会导致整个程序挂掉
  • recover
    golang 的错误处理流程:当一个函数在执行过程中出现了异常或遇到panic(),正常语句就会立即终止,然后执行 defer语句,再报告异常信息,最后退出goroutine。如果在defer中使用了recover()函数,则会捕获错误信息,使该错误信息终止报告。
func main() {     i := 10000     for j := 0; j < 3; j++ {         // 使用多协程处理,其中可以预见的是除数为0会抛出异常         divide(i, j)     } }  func divide(i, j int) {     defer func() {         if r := recover(); r != nil {             // 这里可以对异常进行一些处理和捕获             fmt.Println("Recovered:", r)         }     }()     fmt.Println(i / j) // 第一次触发除0异常,panic }  运行结果:   10000 5000


当有多个defer的时候,出现故障,此时跳转到包含recover()的defer函数执行

func main() {     defer func() {         fmt.Println("1")     }()     defer func() {         if err := recover(); err != nil {             fmt.Println(err)         }     }()     panic("fault")     fmt.Println("2") }  运行结果: fault 1
  • 类型断言
    类型断言用于提取接口的基础值,语法:i.(T)
func assert(i interface{}){     s:= i.(int)     fmt.Println(s) }  func main(){   var s interface{} = 55   assert(s) }

程序打印的是int值, 但是如果我们给s 变量赋值的是string类型,程序就会panic。
当程序改为:

func assert(i interface{}) {       v, ok := i.(int)     fmt.Println(v, ok) } func main() {       var s interface{} = 56     assert(s)     var i interface{} = "Steven Paul"     assert(i) }

如果 i 的值是int类型, 那么v就是i 对应的值,ok就是true。否则ok为false,程序并不会panic。

  • 类型判断
    类型判断的语法类似于类型断言。在类型断言的语法i.(type)中,类型type应该由类型转换的关键字type替换。
func findType(i interface{}) {       switch i.(type) {     case string:         fmt.Printf("String: %s\n", i.(string))     case int:         fmt.Printf("Int: %d\n", i.(int))     default:         fmt.Printf("Unknown type\n")     } } func main() {       findType("Naveen")     findType(77)     findType(89.98) }


还可以将类型与接口进行比较。如果我们有一个类型并且该类型实现了一个接口,那么可以将它与它实现的接口进行比较。

type Describer interface {       Describe() } type Person struct {       name string     age  int }  func (p Person) Describe() {       fmt.Printf("%s is %d years old", p.name, p.age) }  func findType(i interface{}) {       switch v := i.(type) {     case Describer:         v.Describe()     default:         fmt.Printf("unknown type\n")     } }  func main() {       findType("Naveen")     p := Person{         name: "Naveen R",         age:  25,     }     findType(p) } 输出结果: unknown type   Naveen R is 25 years old  


当一个类型实现了多个接口,switch case按照编写的顺序优先执行,如以下代码:

type Describer interface {     Describe() }  type Describer2 interface {     Describe2() }  type Person struct {     name string     age  int }  func (p Person) Describe() {     fmt.Println("describe") }  func (p Person) Describe2() {     fmt.Println("describe2") }  func findType(i interface{}) {     switch v := i.(type) {     case Describer2:         v.Describe2()     case Describer:         v.Describe()     default:         fmt.Printf("unknown type\n")     } }  func main() {     p := Person{         name: "Naveen R",         age:  25,     }     findType(p) }  输出结果: describe2
文章来源: Golangѧϰ
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!