Go语言学习笔记(三)

Go语言范围(Range)

Go语言中range关键字用于for循环中迭代数组(array),切片(slice),通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回key-value对。

package main
import "fmt"
func main() {
    //这是我们使用range去求一个slice的和。使用数组跟这个很类似
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)
    //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }
    //range也可以用在map的键值对上。
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }
    //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}

输出结果

sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111

Go语言Map(集合)

Map是一种无序的键值对的集合。Map最重要的一点是通过key来快速检索数据,key类似于索引,指向数据的值。

Map是无序的,因为Map使用hash表来实现的。

map的键Key是唯一的,且必须是支持"=="操作符和"!="操作符的类型。切片、函数以及包含切片的结构类型由于具有引用语义,不能作为map的键。map只有len没有cap.

声明

var  map_variable map[key_data_type]value_data_type

初始化一

map_variable := make(map[key_data_type]value_data_type)

初始化二

map_variable := map[string]string{“a”:"b"}

全局变量初始化  var map_variable = make(map[key_data_type]value_data_type)

map取值, m := mapData["name"]    m, ok := mapData["name"] 第二种ok参数为bool值

全局变量map的初始化
错误示范:
var ErrorData = make(map[string]string)
ErrorData ["4001"] = "接口验证失败"
以上代码如果放在函数体外会报错。
正确写法
var ErrorData = map[string]string {
	"4001": "接口验证失败",   //注意此行末尾有逗号
}

如果不初始化map,那么就会创建一个nil map。nil map不能用于存放键值对。

func main() {
    countryCapitalMap := make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "巴黎"
    countryCapitalMap [ "Italy" ] = "罗马"


    /*使用键输出地图值 */
    for country := range countryCapitalMap { //此时range前面只有一个变量,则此变量代表key,如果有两个,则第二个变量代表value
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
    /*fmt.Println(capital) */
    /*fmt.Println(ok) */
    if (ok) { //存在ok为true
        fmt.Println("American 的首都是", capital)
    } else {
        fmt.Println("American 的首都不存在")
    }
}

delete()函数用于删除集合的元素,参数为map和其对应的key

delete(countryCapitalMap, "France")

Go语言递归函数

递归,就是在运行的过程中调用自己.需要设置递归条件,否则将陷入无限循环。

func recursion(){
    recursion()
}

func main(){
    recursion()
}


//实现阶乘
func Factorial(n uint64)(result uint64) {
    if (n > 0) {
        result = n * Factorial(n-1)
        return result
    }
    return 1
}

func main() {  
    var i int = 15
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

Go语言类型转换

类型转转用于将一种数据类型的变量转换为另外一种类型的变量。

type_name(expression)   第一个为类型,第二个为表达式

例 a := 7    float32(a)

Go语言接口

Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

type interface_name interface { //定义接口
    method_name1 [return_type]
    method_name2 [return_type]
    method_name3 [return_type]
}

type struct_name struct { //定义结构体

}

func (struct_name_variable struct_name) method_name1()[return_type]{ //实现接口方法

}



package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

interface{}类型其实是个空接口,即没有方法的接口。go的每一种类型都实现了改接口,因此,任何其他类型的数据都可以复制给interface{}类型

定义结构体
type Stu struct {
    Name  string `json:"name"`
    Age   int
    HIgh  bool
    sex   string
    Class *Class `json:"class"`
}

type Class struct {
    Name  string
    Grade int
}
赋值
stu := Stu{
    Name: "张三",
    Age:  18,
    HIgh: true,
    sex:  "男",
}
//指针变量
cla := new(Class)
cla.Name = "1班"
cla.Grade = 3
stu.Class=cla


也可以用空接口的方式定义结构体并赋值
type Stu struct {
    Name  interface{} `json:"name"`
    Age   interface{}
    HIgh  interface{}
    sex   interface{}
    Class interface{} `json:"class"`
}

type Class struct {
    Name  string
    Grade int
}

赋值过程与上述赋值完全相同

Go错误处理

Go语言通过内置的错误接口提供二楼非常简单的错误处理机制。

type error interface {
    Error() string
}

我们可以在编码中通过实现error接口类型来生成错误信息。

函数通常在最后的返回值中返回错误信息,使用errors.New可返回一个错误信息。

func Sqrt(f float64)(float64, error) {
    if f < 0 {
        return 0, errors.New("math:square root of negative number")
    }
}
type De struct {
    f1 int 
    f2 int 
}

func (de De) Error() string {
    str := `
        error === 
        f1 = %d
        f2 = 0 
    `   
    return fmt.Sprintf(str, de.f1)  //字符串格式化
}

func Divide(f1 int, f2 int)( result int, msg string ) { 
    if f2 == 0 { 
        de := De{f1, f2} 
        return 0, de.Error()
    }else{
        return f1/f2, ""
    }   
}

func main(){
   if a, b := Divide(11, 0); b == ""{ //此处声明的a,b为if语句的局部变量,只能在if else内使用
       print(a, b)
   }else{
        print(a, b)
   }   

}

Go并发

Go语言支持并发,我们需要通过go关键字开启goroutine即可。

goroutine是轻量级线程,goroutine 的调度是由Golang运行时进行管理的。

使用go语句开启一个新的运行期线程,以一个不同的,新创建的goroutine来执行一个函数。同一个程序中所有goroutine共享一个地址空间。

package main

import (
    "fmt"
    "time"
)

func say(s string){
    for i := 0; i< 5; i++{
        time.Sleep(100*time.Millisecond)
        fmt.Println(s)
    }   
}

func main(){
    go say("world")
    go say("hello")
    time.Sleep(1000*time.Millisecond) //此处将主线程延迟结束不然看不到上述的执行结果
}

/* 执行结果,看出没有固定的先后顺序,是两个goroutine在执行
world
hello
hello
world
world
hello
hello
world
world
hello
*/

通道(channel)

通道(channel)是用来传递数据的一个数据结构。

通道可用于两个goroutine之间传递一个指定类型的值来同步运行和通讯。操作符 < - 用于指定通道的方向,发送或者接收。如果未指定方向,则为双向通道。

ch <- v //把v发送到通道ch
v := <-ch //从ch接收数据,并把值赋给v

//声明通道很简单,使用chan关键字,通道在使用前必须先创建。
ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

//通过两个goroutine来计算数字之和

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}

通道缓冲区

通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小      ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。

注意:如果通道不带缓冲,发送发会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}


//不带缓冲区的通道,一次只能发一个数据,通过goroutine来实现传入两个值
func SetC(c chan int, s int){
    c <- s
}

func main(){
    c := make(chan int)
    go SetC(c, 1)
    go SetC(c, 2)
    fmt.Println(<-c)
    fmt.Println(<-c)
}

Go遍历通道与关闭通道

go通过range关键字来遍历读取的数据,类似于数组或切片。    v, ok := <-ch

如果通道接收不到数据,ok就为false,这时通道就可以使用close()函数来关闭。

package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}

func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

select

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case来描述,与switch语句相比,select有比较多的限制,其中最大的限制就是每个case语句里必须是一个channel操作,大致结构如下。

select {
    case <- chan1:
    //如果成功读取到数据,则进行该case处理语句
    case chan2 <- 1:
    //如果成功向chan2写入数据,则进行该case处理语句
    default:
    //如果上面都没有成功,则进入default处理流程
}

并发编程中,最需要处理的就是超时问题。即向channel写数据时发现channel已满,或者channel试图读取空数据时发现channel空。如果不正确处理这些情况,很可能会导致整个goroutine锁死。

使用select可以方便的解决超时问题。

//首先我们实现并执行一个匿名的超时等待函数
timeout := make(chan bool, 1)
go func() {
    time.Sleep(1e9) //等待1秒钟
    timeout <- true
}

select {
    case <- ch:
        //从ch中读取数据
    case <- timeout:
        //一直没有从ch中读取到数据,但从timeout中读取到了数据
}
这样就能避免永久等待的问题,因为程序会在timeout中获取到一个数据后继续执行,无论对ch的读取是否还处于等待状态,从而达成1秒超时的效果。

单向channel

单向channel概念,其实只是对channel的一种使用限制。

单向channel声明
var ch1 chan int //一个正常channel
var ch2 chan<- float64 //ch2单向channel,只用于写float64数据
var ch3 <-chan int  //ch3是单向channel,只用于读取int数据
channel是一个原生类型,因此不仅支持被传递,还支持类型转换。
ch4 := make(chan int)
ch5 := <-chan int(ch4) //ch5是一个单向的读取channel
ch6 := chan<- int(ch4) //ch6就是一个单向的写入channel

关闭channel

关闭channel非常简单,直接使用Go语言内置的close()函数即可。

close(ch)

如何判断一个channel是否已经被关闭,我们可以再读取时使用多返回值的方式

x,ok := <- ch

这个用法与map中按键获取value的过程比较类似,只需要看第二个bool返回值即可。如果返回值时false则表示ch已经关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值