数组(array)和切片(slice)
区别与联系
-
相同点:它们都属于集合类的类型,并且,它们的值也都可以用来存储某一种类型的值。
-
不同点:数组的长度是固定的,是值类型;而切片是可变长的,是引用类型。
-
联系:数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用。
创建
//创建一个数组,初始化就要声明长度
arr1 := [3]int{1,2,3} //显示声明长度
arr2 := [...]int{4,5,6} //隐式声明长度
//创建一个切片
slice1 := []int{1,2,3} //声明式创建 length为3,capacity为3
slice2 := make([]int,3,8) //函数创建 length为3,capacity为8.[0,0,0]
slice3 := arr1[0:2] //从数组中引用
slice4 := slice[:] //从切片中引用
函数(function)与方法(method)
一等公民(函数式编程)
在 Go 语言中,函数是一等的(first-class)公民。函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等,就像切片和字典的值那样。对于函数类型来说,它是一种对一组输入、输出进行模板化的重要工具。
type Employee struct {
name string
salary int
}
//函数
func showName(e Employee){
fmt.Printf("name is:", e.name)
}
//方法
func (e Employee) showName(){
fmt.Printf("name is:",e.name)
}
高阶函数与闭包
- 接受其他的函数作为参数传入
- 把其他的函数作为结果返回
满足其中一个特点的函数就叫高阶函数
//定义operate函数类型
type operate func (x,y int) int
type calculateFunc func(x int, y int) (int, error)
op := func (x,y int) int {
return x + y
}
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
}
}
//调用
x,y = 67, 98
add := genCalculator(op)
result, err = add(x,y)
fmt.Printf("result is: %d (error:%v)\n", result,err)
表面上看,
genCalculator只是延迟实现了一部分程序逻辑而已,实际上是在动态地生成那部分程序逻辑。这里的匿名的函数就是一个闭包函数,它里面使用的op变量是一个自由变量,只有在genCalculator函数被调用的时候才能确定。因此可以借此在程序运行的过程中,根据需要生成功能不同的函数,继而影响后续的程序行为。
感想
绕了几圈后还是调用的op函数。直接调用op(x,y)不是更方便。这里其实要考虑函数的安全性和可扩展性。使用闭包的好处:
- 提高某个功能的灵活性。可以让使用方提供一部分功能的实现,但是可以控制这一部分的大小
- 动态替换某个功能逻辑
- 使得代码动态替换的粒度缩小到函数级别
js中函数的地位同样非常高,也有闭包的概念(好像大多数语言都有这个概念)。平日我自己写业务逻辑的时候用到闭包的情况并不多,对闭包的理解不是很深刻。我想一是因为业务太具体,没有封装的必要性;二是耦合性比较低。总之还是翻过的山、路过的河太少,还是要多见见世面。
通道(channel)
定义
通道就是goroutine之间的通道。它可以让goroutine之间相互通信。
一个通道相当于一个先进先出(FIFO)的队列。元素值的发送和接收都需要用到操作符<-。一个左尖括号紧接着一个减号形象地代表了元素值的传输方向。
方法
//1. 声明 此时ch1 是 nil 的,不可使用
var 通道名 chan 数据类型
ex:var cha1 = chan int
//2. 创建。缓冲容量默认没0,表示是非缓冲通道;如果设置了值则为缓冲通道
通道名 = make(chan 数据类型,缓冲容量)
ex: ch1 = make(chan int,3)
ch1 := make(chan int, 3) //上述2步简写
//3. 关闭。一般由发送方手动关闭,也可以利用关的动作来给接收方传递一个信号。
close (ch1) //发送方
v, ok := <- ch1 //接收方.ok为false表示通道关闭
//4. 读取
ch1 := make(chan int ,3)
ch1 <- 1
ch1 <- 2
ch1 <- 3
//range 读取
for v:= range ch1 {
fmt.Println("读取数据:",v)
}
//select读取
分类
按照存取方向可分为(双向)通道、单向通道;按照是否可缓存分为缓存通道、非缓存通道
通道默认是双向的,既可以接收也可以发送;单向通道就是只能发不能收,或者只能收不能发的通道
//单向发送通道
sendCh := make(chan<- int, 1)
//单向接收通道
receiCh := make(->chan int,1)
特性
-
对于同一个通道,发送互斥,接收互斥
通道的发送操作和接收操作具有原子性。意思是在发送数据时,直到某个元素值被完全复制进该通道之后,其他针对该通道的发送操作才可能被执行;接收数据时,直到某个元素值完全被移出该通道之后,其他针对该通道的接收操作才可能被执行。
另外,对于通道中的同一个元素值来说,发送操作和接收操作之间也是互斥的。
-
通道是
goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中
//只有发送没有接收,发生死锁
package main
func main() {
ch := make(chan int)
ch <- 5
}
错误(error)和异常(panic)
错误指的是可能出现问题的地方出现了问题,可以预知;
异常指的是不应该出现问题的地方出现了问题,不可预知。
错误
定义
error实质上是一个接口类型。任何实现这个接口的类型都可以作为一个错误使用
type error interface {
Error() string
}
提取错误
- 使用断言表达式或类型
switch语句
```go
func main() {
/*
net库中有两个方法Timeout() bool、Temporary() bool,分别用来判断是否超时、是否是临时的。使用if语句判断网络错误类型是超时还是临时还是其他情况
*/
addr, err := net.LookupHost("golangbot123.com")
if err, ok := err.(*net.DNSError); ok {
if err.Timeout() {
fmt.Println("operation timed out")
} else if err.Temporary() {
fmt.Println("temporary error")
} else {
fmt.Println("generic error: ", err)
}
return
}
fmt.Println(addr)
}
//因为golangbot123.com是一个不存在的网站,因此运行结果generic error: lookup golangbot123.com: no such host
```
2. 直接比较
```go
func main() {
/*
1.Glob函数用于返回与模式匹配的所有文件的名称,如果发生错误则返回一个ErrBadPattern
2.ErrBadPattern是filepath中定义好的一个变量:errors.New("syntax error in pattern")
*/
files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
fmt.Println(error) //syntax error in pattern
return
}
fmt.Println("matched files", files)
}
//运行结果:syntax error in pattern
```
3. 解析字符串
go //解析这个错误消息并从中获取文件路径"/test.txt" open /test.txt: No such file or directory
一般优先选择前2种错误处理方案。由于错误信息是可以更改的,第3种方法获取到的信息不太可靠。
恐慌
定义
运行时恐慌(panic),这种异常只会在程序运行的时候被抛出来,造成的后果也很严重,有可能使程序无法提供任何功能(也可以说僵死)或者直接崩溃并终止运行(也就是真死)。对面这种情况要求对程序要予以保护,Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。
举例
func main() {
fmt.Println("1111")
defer func(){
fmt.Println("2222")
if p := recover(); p != nil {
fmt.Printf("panic: %s\n", p)
}
fmt.Println("3333")
}()
// 引发panic
panic(errors.New("wrong"))
fmt.Println("4444")
}
/* 运行结果
11111
2222
panic:wrong
33333
*/
相互转换
- 错误转异常,比如程序逻辑上尝试请求某个
URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。 - ** 异常转错误**,比如
panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。