作者:汉臣Y | 来源:互联网 | 2023-09-14 12:14
1.数组的特点:声明数组时需要指定内部存储的数据的类型和存储的元素的数量,一旦声明数组里存储的数据类型和数组长度就都不能改变了。声明数组:vararray1[5]string声明并
1.数组的特点:声明数组时需要指定内部存储的数据的类型和存储的元素的数量,一旦声明数组里存储的数据类型和数组长度就都不能改变了。
声明数组:var array1 [5]string
声明并初始化数组:var array2 = [5]string{"red", "blue", "green", "yellow", "pink"}
具体值初始化数组:var array2 := [5]string{“red”, “blue”, “green”, “yellow”, “pink”}
数组赋值(相互拷贝):array1 = array2
在函数间传递数组(传地址):foo(&array1)
2.切片的特点:围绕动态数组的概念构建可以按需自动增长和缩小
make创建切片:slice := make([]string, 5, 10)
使用切片字面量来创建切片:slice := []string{“red”, “blue”, “green”}
使用切片创建切片:newslice := slice[1:2]
增加切片的容量:newslice = append(newslice, 10)
迭代切片:for index, value := range slice { }
函数间传递切片(传值):foo(slice)
3 映射的特点:用来存储一系列无序的键值对,能够基于键快速检索数据
创建映射:colors := map[string]string{“red”: “1379” }
迭代映射:for key, value := range colors { }
删除键值:delete(colors, “red”)
函数间传递映射(传值)
4 类型系统
4.1 用户定义的类型
声明一个结构类型 : type user struct{
name string
email string
}
Go语言是一种静态类型的编程语言,编译器需要在编译时知晓程序里每一个值的类型
4.2 方法
方法:给用户定义的类型添加新的行为.
1)使用指针接收者实现一个方法时,这个方法会共享调用方法时接收者所指向的值
指针接收:func (u *user) notify() { }
使用指针接收者声明:bill := &user{“bill”, “[email protected]”}
2)使用值接收者实现一个方法时,调用时会使用这个值的一个副本来执行
值接收: func (u user) notify() { }
使用值接收者声明:bill := user{“bill”, “[email protected]”}
使用指针接收者声明:bill := &user{“bill”, “[email protected]”}
总结:值接收者使用值的副本来调用方法,而指针接收者使用实际值来调用方法
4.3 类型的本质
如果给一个类型增加或者删除某个值,是要创建一个新值,还是要更改当前的值?如果是要创建一个新值,该类型的方法就使用值接收者。如果是要修改当前值,就使用指针接收者。
1)内置类型 如数值类型,字符串类型和布尔类型本质上是原始的类型,因此,当对这些值进行增加或者删除的时候会创建一个新值。
2)引用类型:切片,映射,通道,接口和函数类型
3)结构类型可以用来描述一组数据值,这组值的本质即可以是原始的,也可以是非原始的
4)是使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定,这个决策应该基于该类型的本质。
4.4 接口
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以副给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。对接口值方法的调用会执行接口值里存储的用户定义的类型的值对应的方法。因为任何用户定义的elixir都可以实现任何接口,所以对接口值方法的调用自然就是一种多态。在这个关系里,用户定义的类型通常叫作实体类型,原因是如果离开内部存储的用户定义的类型的值的实现,接口值并没有具体的行为。
1)实现
与实体值赋值相比类型信息会存储一个指向保存的类型的指针而不是类型本身,而接口值第二个字依旧保存指向实体值的指针
2)方法集
(1)问题代码

运行结果:
要了解用指针接收者来实现接口时为什么user类型的值无法实现该接口,需要先了解方法集。方法集定义了一组关联到给定类型的值或者指针的方法。定义方法时使用的接收者的类型决定了这个方法是关联到值,还是关联到指针,还是两者都关联
3)多态
代码:
因为sendNotification接受notifier类型的接口值,所以这个函数可以同时执行user和admin实现的行为

运行结果:

4)嵌入类型
Go语言允许用户扩展或者修改已有类型的行为。这个功能对代码复用很重要,在修改已有类型以符合新类型的时候也很重要。这个功能是通过嵌入类型完成的。嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型被称为新的外部类型的内部类型。
代码:
可以直接访问内部类型的方法ad.user.notify()
内部类型的方法也被提升到外部类型ad.notify()

运行结果:

(1)如果外部类型并不需要使用内部类型的实现,而想要使用自己的一套实现,我们只需要实现外部类型值的指针调用方法。如果要访问内部类型可以使用直接访问内部类型的方法ad.user.notify(),访问外部类型的方法使用ad.notify()
5 并发
1)go语言里的并发指的是能让某个函数独立于其他函数的运行能力。当一个函数创建为goroutine时,go会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。
代码:
var wg sync.WaitGroup 是一个计数信号量,可以用来记录并维护运行的goroutine
wg.Add(2)表示要等待两个goroutine
wg.Done()用来通知main函数工作已经完成
wg.Wait()大于0时阻塞

运行结果:

在知道了如何创建goroutine,并了解了背后发生的事情了,下面要了解一下写并发程序时的潜在危险,以及需要注意的事情。
2)竞争状态
如果两个或者多个goroutine在没有互相同步的情况下,访问某个共享资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称为竞争状态
3)锁住共享资源
Go语言提供了传统的同步goroutine的机制,就是对共享资源加锁。atomic包里的几个函数 与 sync包里的mutex类型
(1)atomic.AddInt64(&counter, 1)
(2)互斥锁(mutex)
代码:

运行结果:

4)通道
当一个资源需要在goroutine之间共享时,通道在goroutine之间架起了一个通道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据类型。可以通过通道共享内置类型,命名类型,结构类型和引用类型的值或者指针。
(1)使用make创建通道
无缓冲
unbuffer := make(chan int)
有缓冲
buffer := make(chan string, 10)
通过通道发送一个字符串
buffered <- “gopher”
从通道接收一个字符串
value := <-buffered
代码:
运行结果: