Golang基础语法

167 阅读6分钟

数组(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)不是更方便。这里其实要考虑函数的安全性和可扩展性。使用闭包的好处:

  1. 提高某个功能的灵活性。可以让使用方提供一部分功能的实现,但是可以控制这一部分的大小
  2. 动态替换某个功能逻辑
  3. 使得代码动态替换的粒度缩小到函数级别

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)

特性

  1. 对于同一个通道,发送互斥,接收互斥

    通道的发送操作和接收操作具有原子性。意思是在发送数据时,直到某个元素值被完全复制进该通道之后,其他针对该通道的发送操作才可能被执行;接收数据时,直到某个元素值完全被移出该通道之后,其他针对该通道的接收操作才可能被执行。

    另外,对于通道中的同一个元素值来说,发送操作和接收操作之间也是互斥的。

  2. 通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine

//只有发送没有接收,发生死锁
package main

func main() {  
    ch := make(chan int)
    ch <- 5
}

错误(error)和异常(panic)

错误指的是可能出现问题的地方出现了问题,可以预知

异常指的是不应该出现问题的地方出现了问题,不可预知

错误

定义

error实质上是一个接口类型。任何实现这个接口的类型都可以作为一个错误使用

type error interface {
  Error() string
}
提取错误
  1. 使用断言表达式或类型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中引入两个内置函数panicrecover来触发和终止异常处理流程,同时引入关键字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	
*/

相互转换

  1. 错误转异常,比如程序逻辑上尝试请求某个URL,最多尝试三次,尝试三次的过程中请求失败是错误,尝试完第三次还不成功的话,失败就被提升为异常了。
  2. ** 异常转错误**,比如panic触发的异常被recover恢复后,将返回值中error类型的变量进行赋值,以便上层函数继续走错误处理流程。

正确处理错误和异常

错误和异常的正确处理姿势