Go语言学习教程之goroutine和通道的示例详解

目录
  • goroutine
  • 通道
  • Range 和 Close
  • Select
  • 官方留的两道练习题
    • 等价的二叉树
    • 网络爬虫
  • 源码地址

goroutine

goroutine是由Go运行时管理的轻量级线程

go f(x, y, z)在一个新的goroutine中开始执行f(x, y,z)

goroutines运行在相同的地址空间中,所以对共享的内存访问必须同步。sync包提供了基本的同步原语(synchronization primitives),比如互斥锁(mutual exclusion locks)。

goroutines运行在相同的地址空间中,没有内存隔离,不同的goroutines可以访问同一个内存地址。这样对共享的内存的访问就可能出现问题,比如有一个全局变量A,goroutine 1开始修改A的数据,但是还没修改完,goroutine 2就开始读取A的数据了,这样读到的数据可能是不准确的,如果goroutine 2中也要修改A的数据,那A的数据就处于一种更不确定的状态了。所以需要使用互斥锁,当goroutine 1开始修改A的数据之前,先加个锁,表示这块内存已经被锁上了,等修改完A的数据再将锁解开。在goroutine 1修改数据A但还没修改完的期间,goroutine 2需要修改/读取A的内容,发现已经加锁,就会进入休眠状态,直到变量A的锁被解开才会执行goroutine 2中的修改/读取。

package main

import (
    "fmt"
    "time"
)

func main() {
    go say("a")
    say("b")
}

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(2000 * time.Millisecond)
        fmt.Println(s, time.Now().Format("15:04:05.000000"))
    }
}

执行go run goroutine.go的时候,会在主goroutine中执行main函数,当执行到go say("a")的时候,会在一个新的goroutine中执行say("a")(称这个子goroutine为goroutine 1),然后主goroutine中继续执行say("b"),主goroutine和goroutine 1中的函数执行是并发的。

因为是并发执行,打印出的字符串a和字符串b的顺序是无法确定的。

(仔细观察的话会发现打印的前2条数据的时间戳,b的时间戳在a的后面,但是先打印出了b,这说明这次执行中,两者的fmt.Println函数的执行(直到输出到终端)时间不同,先拿到了字符串a,但是打印字符串a的fmt.Println执行比打印字符串b的函数执行稍稍慢了一点,所以b先出现在了输出界面上。可能背后还有更复杂的原因,这里不作深究。)

通道

通道(channels)是一个类型化的管道(conduit),可以通过<-(通道运算符)来使用通道,对值进行发送和接收。

可选的<-操作符指定了通道的方向,如果给出了一个方向,通道就是定向的,否则就是双向的。

chan T // 可以被用来发送和接收类型为T的值
chan <- float64 // 只能被用来发送float64类型的值
<-chan int // 只能被用来接收int类型的值

如果有<-操作符的话,数据按照箭头的方向流动。

通道在使用前必须被创建:

make(chan int, 100)

通过内置的make函数创建一个新的、初始化的通道,接收的参数是通道类型和一个可选的容量。容量设置缓存区的大小。如果容量是0或者省略了,通道就是非缓存的,只在发送方和接收方都准备好的时候才能通信成功。否则通道就是缓存的,发送方的缓存区没有满,或者接收方的缓存区不为空,就能不阻塞地进行通信。

“发送方的缓存区没有满,或者接收方的缓存区不为空,就能不阻塞地进行通信。“这句话直白一点说,就是如果缓存区满了,就不能再往通道中发送数据了(chan <- 数据 ),如果缓存区是空的,就不能从通道中接收数据了(<-chan)。

1.无缓存通道例子:

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    example1()
    wg.Wait() // 等待所有goroutines执行完成
}

func example1() {
    chan1 := make(chan int)

    wg.Add(1)
    go a(chan1) // 向通道中发送数字1、2

    wg.Add(1)
    go b(chan1) // 等待1秒之后,从通道中拿数据,拿到的是数字2

    fmt.Println("接收数据A", <-chan1) // 这里拿到的是数字1
}

func a(chan1 chan int) {
    defer wg.Done()
    chan1 <- 1
    chan1 <- 2
}

func b(chan1 chan int) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println("接收数据B", <-chan1)
}

如果把以下这两句注释掉,运行代码就会报错:fatal error: all goroutines are asleep - deadlock!

    wg.Add(1)
    go b(chan1) // 等待1秒之后,从通道中拿数据

把这句注释掉,代码变成了往无缓存通道中发送了2个元素,但是只接收了1个元素。由于向通道中发送的元素2没被接收,通道会阻塞,sync包又在等待数字2的发送(chan1 <- 2)完成,就造成了死锁。

最终在无缓存通道中的元素个数为0,无缓存通道就不会阻塞。

2.有缓存通道例子:

...
var wg sync.WaitGroup

func main() {
    example2()
    wg.Wait() // 等待所有goroutines执行完成
}

func example2() {
    chan1 := make(chan int, 2)

    wg.Add(1)
    go a(chan1) // 向通道中发送数字1、2、3

    fmt.Println("接收数据", <-chan1)
}

func a(chan1 chan int) {
    defer wg.Done()
    chan1 <- 1
    chan1 <- 2
    chan1 <- 3
}

func b(chan1 chan int) {
    defer wg.Done()
    time.Sleep(time.Second)
    fmt.Println("接收数据", <-chan1)
}

以上代码向容量为2的缓存通道中发送了3个元素,但是只接收了1个,此时通道中还有2个元素,不会阻塞。

如果在a函数的最后一行再加上一句chan1 <- 4,再执行代码,就会报错fatal error: all goroutines are asleep - deadlock!。因为发送了4个元素,只接收了1个元素,还剩3个元素没被接收,3 > 2,缓存已经满了,由于代码中没有别的地方来接收元素,通道阻塞,但是sync包又在等待chan1 <- 4的完成,所以会造成死锁。

最终在有缓存通道中的元素个数小于等于容量,有缓存通道就不会阻塞。

3.使用通道在goroutines间进行通信的例子:

func main() {
    example3()
}

func example3() {
    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

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

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum
}

这段代码将数组的内容分为两部分,在两个goroutines中分别进行计算,最后再进行求和。

这里两个子goroutines是与主goroutine并发执行的,但主goroutine中的x, y := <-c, <-c依然拿到了两个子goroutines中往通道发送的数据(c <- sum)。这是因为通道的发送和接收会阻塞,直到另一边准备好。

x拿到的是先计算完的和,y拿到的是后计算完的和,xy的值是不确定的,可能是-5 17 或者 17 -5,就看哪个子goroutine中的计算先完成。

Range 和 Close

发送方可以close一个通道来表明没有更多的值会被发送。接收方可以通过赋值第二个参数给接收表达式,测试一个通道是否已经被关闭。

执行如下语句:

v, ok := <-ch

如果没有更多的值要接收,并且通道已经关闭了,ok的值就为false

for i := range c循环,从通道中重复地接收值,直到通道关闭。

注意:

  • 只有发送方可以关闭一个通道,接收方不可以。在一个已经关闭的通道上进行发送会导致一个错误(panic)。
  • 通道不像文件,不需要总是关闭它们。关闭只有必须告诉接收方不会再来更多值时,才是必须的,比如终止一个range循环。
func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    // 必须在遍历结束之后关闭通道
    // 否则 for i := range c 会一直等待通道关闭
    close(c)
}

以上代码求斐波那契数列,依次将求得的值发送到通道。

如果把close(c) 语句注释掉,运行代码,就会报错:fatal error: all goroutines are asleep - deadlock!。因为for i := range c一直在等通道关闭,但是整个执行过程中并没有关闭通道,造成了死锁。

Select

select语句让一个goroutine等待多个通信操作。

一个select 会阻塞,直到它的cases中的一个可以运行,然后它就会执行该case。如果多个通信都准备好了,就会随机选择一个。

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

上述代码还是实现一个斐波那契数列的计算。

在子goroutine(称之为goroutine 1)中循环10次,依次从通道c中接收数据,循环结束之后,将数字0发送到通道quit。

在主goroutine中,调用fibonacci函数:

  • c <- x是向通道中发送数据,只要有地方从通道中接收数据,向通道中发送数据就能继续运行。每次在goroutine 1的循环中<-c,主goroutine中的select语句中的case c <- x中的语句就会执行。
  • <-quit是从通道中接收数据,只要有地方向通道中发送数据,从通道中接收数据就能继续运行。当goroutine 1中循环结束之后quit <- 0case <-quit中的语句就会执行。

一个select中的default case,在没有其他case准备好的时候就会运行。

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)

    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

每隔100毫秒,通道tick就会收到一次数据,case <-tick中的语句会执行,打印一次tick.;500毫秒之后,通道boom会收到数据,case <-boom中的语句会执行,打印BOOM!,并且使用return结束程序的执行。在这期间,由于for语句是一直在循环的,当通道tick和通道boom中都没收到数据时,就会执行default中的语句:打印一个点并且等待50毫秒。

粗略看了下time.Ticktime.After代码,两者返回的值都是类型为<-chan Time的通道,使用轮询,在满足时间条件之后,向通道中发送当前时间。如果想看通道中传递的时间数据的话,可以使用以下代码:

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    var x, y time.Time
    for {
        select {
        case x, _ = <-tick:
            fmt.Println(x, "tick.")
        case y, _ = <-boom:
            fmt.Println(y, "BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

sync.Mutex

如果我们想要避免冲突,确保一次只有一个goroutine可以访问一个变量(这个概念称为互斥),则可以使用互斥锁(mutex)。

Go的标准库提供了互斥的使用,需要用到sync.Mutex和它的两个方法LockUnlock

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    c := SafeCounter{v: make(map[string]int)}
    for i := 0; i < 1000; i++ {
        go c.Inc("somekey")
    }
    time.Sleep(time.Second)
    fmt.Println(c.Value("somekey"))
}

type SafeCounter struct {
    mu sync.Mutex
    v  map[string]int
}

// 使用给定的key递增计数器
func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    // 锁住之后,一次只能有一个goroutine可以访问映射c.v
    c.v[key]++
    c.mu.Unlock()
}

// 返回 给定key的 计数器的当前值
func (c *SafeCounter) Value(key string) int {
    c.mu.Lock()
    // 锁住之后,一次只能有一个goroutine可以访问映射c.v
    defer c.mu.Unlock()
    return c.v[key]
}

官方留的两道练习题

官方留了两道练习题,没有给出完整的代码。可以作为了解了以上知识之后的练手。

等价的二叉树

有很多不同的二叉树,存储着相同的值的序列。例如,下图两棵二叉树存储的序列是1, 1, 2, 3, 5, 8, 13。

1.实现Walk函数。

2.测试Walk函数。

函数tree.New(k)构造了一个随机结构(但总是排序的)的二叉树来存储值k2k3k,...,10k

创建一个新的通道ch并开始遍历:

go Walk(tree.New(1), ch)

然后打印树中包含的10个值,应该是数字1,2,3,...,10。

3.实现Same函数,使用Walk来决定t1t2是否存储相同的值。

4.测试Same函数:

  • Same(tree.New(1), tree.New(1)) 应该返回true
  • Same(tree.New(1), tree.New(2)) 应该返回false

代码实现

主要部分代码如下:

package main

import (
    "equbintrees/tree"
    "fmt"
)

func main() {
    tree1 := tree.New(1)
    tree2 := tree.New(2)
    fmt.Println(Same(tree1, tree2))
}

// 函数遍历树 t,将树中的所有值依次发送到通道中
func Walk(t *tree.Tree, ch chan int) {
    if t == nil {
        return
    }
    if t.Left != nil {
        Walk(t.Left, ch)
    }
    ch <- t.Value
    if t.Right != nil {
        Walk(t.Right, ch)
    }
}

// 判断两棵树是否包含相同的值
func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go Walk(t1, ch1)
    go Walk(t2, ch2)

    var count int

    for {
        if <-ch1 == <-ch2 {
            count++
            // 这里的count等于10,是因为题目要求里面随机生成的树的节点个数就是10个
            // 一般的树可以给树添加一个Len属性表示节点个数,用Len属性来判断
            if count == 10 {
                return true
            }
        } else {
            return false
        }
    }
}

网络爬虫

使用Go的并发功能来并发网络爬虫。

修改Crawl函数来并发获取URLs,并且相同的URL不会获取2次。

提示:你可以使用映射缓存已经获取到的URL,但是只使用映射对于并发使用来说是不安全的。

代码实现

这部分我尝试实现了下,主要思路是在递归的过程中,将遍历到链接中包含的urls发送到通道ch中,用for urls := range ch遍历通道中的元素,以此来等待所有发送到通道中的urls都被接收,在递归过程中判断深度是否达到4,达到4之后调用close(ch)关闭通道。

但是有问题,因为不能仅凭 深度是否达到 来判断 是否关闭通道。给出的例子实际只有4层链接,如果设置深度需要到达到5,当递归到尽头的时候就应该关闭通道了,但是因为没有达到深度5,没有关闭通道,for urls := range ch还会继续等通道接收数据,但已经不会再往通道中发送数据了,造成死锁。总之,手动调用close(ch)来正确关闭通道有点难,因为很难找到递归和并发请求时不会再往通道中发送数据的那个时机。

我从这个链接找到了大佬的代码实现:https://rmoff.net/2020/07/03/learning-golang-some-rough-notes-s01e10-concurrency-web-crawler/

主要思路就是使用sync.WaitGroup,用Add方法添加WaitGroup计数,用wg.Wait()等待所有的goroutines执行结束。

主要部分代码如下:

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go Crawl("https://golang.org/", 5, fetcher, wg)
    wg.Wait()
}

type URLs struct {
    c   map[string]bool // 用于存放表示一个链接是否被抓取过的映射
    mux sync.Mutex      // 使用互斥锁在并发的执行中进行安全的读写
}

var u URLs = URLs{c: make(map[string]bool)}

// 检查链接是否已经被抓取过
func (u URLs) IsCrawled(url string) bool {
    fmt.Printf("\n Checking if %v has been crawled…", url)
    u.mux.Lock()
    defer u.mux.Unlock()
    if _, ok := u.c[url]; ok == false {
        fmt.Printf("…it hasn't\t")
        return false
    }
    fmt.Printf("…it has\t")
    return true
}

// 将链接标记为抓取过
func (u URLs) Crawled(url string) {
    u.mux.Lock()
    u.c[url] = true
    u.mux.Unlock()
}

// 递归地请求抓去url的数据,直到一个最大深度
func Crawl(url string, depth int, fetcher Fetcher, wg *sync.WaitGroup) {
    defer wg.Done()

    if depth <= 0 {
        return
    }

    if u.IsCrawled(url) == true {
        return
    }

    fmt.Printf("\n️ Crawling %v", url)
    body, urls, err := fetcher.Fetch(url)
    u.Crawled(url)

    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("\n\t-> found: %s %q\n", url, body)

    for _, z := range urls {
        wg.Add(1)

        go Crawl(z, depth-1, fetcher, wg)
    }

}

源码地址

https://github.com/renmo/myBlog/tree/master/2022-05-31-goroutine

以上就是Go语言学习教程之goroutine和通道的示例详解的详细内容,更多关于Go语言 goroutine 通道的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go语言中的通道channel详情

    目录 一.Go语言通道基础概念 1.channel产生背景 2.channel工作方式 二.通道使用语法 1.通道的声明与初始化 2.将数据放入通道内 3.从通道内取出数据 4.关闭通道close 三.单项通道及通道的状态分析 1.单项输出通道 2.单项输入通道 3.通道的状态 四.通道死锁原因分析 一.Go语言通道基础概念 1.channel产生背景 线程之间进行通信的时候,会因为资源的争夺而产生竟态问题,为了保证数据交换的正确性,必须使用互斥量给内存进行加锁,go语言并发的模型是CSP,提倡

  • Go语言七篇入门教程四通道及Goroutine

    目录 1. 前言 2. 通道简介 2.1 声明 2.1 读写 2.3 通道详解 2.3.1 例子 2.3.2 死锁 2.3.3 关闭通道 2.3.4 缓冲区 2.3.5 通道的长度和容量 2.3.6 单向通道 2.3.7 Select 2.3.8 default case 块 2.3.9 空 select 2.3.10 Deadlock 2.3.11 nil通道 2.4 多协程协同工作 2.5 WaitGroup 2.5.1 简介 2.5.2工作池 2.5.3 Mutex 3. 结语 如何学习G

  • Go语言中的并发goroutine底层原理

    目录 一.基本概念 ①并发.并行区分 ②从用户态线程,内核态线程阐述go与java并发的优劣 ②高并发为什么是Go语言强项? ③Go语言实现高并发底层GMP模型原理解析 二.上代码学会Go语言并发 ①.开启一个简单的线程 ②.动态的关闭线程 一.基本概念 ①并发.并行区分 1.概念 并发:同一时间段内一个对象执行多个任务,充分利用时间 并行:同一时刻,多个对象执行多个任务 2.图解 类似于超市柜台结账,并行是多个柜台结多个队列,在计算机中是多核cpu处理多个go语言开启的线程,并发是一个柜台结账

  • Go并发编程之goroutine使用正确方法

    目录 1. 对创建的gorouting负载 1.1 不要创建一个你不知道何时退出的 goroutine 1.2 不要帮别人做选择 1.3 不要作为一个旁观者 1.4 不要创建不知道什么时候退出的 goroutine 1.5 不要创建都无法退出的 goroutine 1.6 确保创建出的goroutine工作已经完成 2. 总结 3. 参考 并发(concurrency): 指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时

  • Go语言使用goroutine及通道实现并发详解

    目录 使用通道接收数据 阻塞接收数据 非阻塞接收数据 接收任意数据,忽略掉接收的数据 循环接收数据 使用通道接收数据 在上一篇文章中介绍了通道以及使用通道发送数据,本篇接着了解通道的基本内容,如何使用通道接收数据: 通道的接收同样使用"<-"操作符: 使用通道接收数据的特性如下: 通道的发送和接收操作在不同的两个goroutine间进行,由于通道中的数据在没有接收方接收时会持续阻塞,所以通道的接收必定在另外一个goroutine中进行: 如果在接收方接收时,通道中没有发送方发送数

  • Golang 并发以及通道的使用方式

    Golang最擅长的就是并发编程,使用Golang可以很方便的进行并发编程.先看一段普通的代码 package main import ( "fmt" "time" ) func Foo(i int) { fmt.Printf("%d will sleep\n", i) time.Sleep(5 * time.Second) fmt.Printf("%d wake up\n", i) } func main() { for i

  • Golang 语言控制并发 Goroutine的方法

    goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理.Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU. 01介绍 Golang 语言的优势之一是天生支持并发,我们在 Golang 语言开发中,通常使用的并发控制方式主要有 Channel,WaitGroup 和 Context,本文我们主要介绍一下 Golang 语言中并发控制的这三种方式怎么使用?关于它们各自的详细介绍在之前的文章已经介绍过,感兴趣的读者朋友们可以按需翻阅. 02

  • Go语言学习教程之goroutine和通道的示例详解

    目录 goroutine 通道 Range 和 Close Select 官方留的两道练习题 等价的二叉树 网络爬虫 源码地址 goroutine goroutine是由Go运行时管理的轻量级线程. go f(x, y, z)在一个新的goroutine中开始执行f(x, y,z). goroutines运行在相同的地址空间中,所以对共享的内存访问必须同步.sync包提供了基本的同步原语(synchronization primitives),比如互斥锁(mutual exclusion loc

  • Angular2学习教程之ng中变更检测问题详解

    开发中遇到的问题 在开发中遇到一个这样的问题,代码不便透露,这里用简单的例子还原一下问题所在: 有三个组件,第一个是用来展示Todo列表的组件TodoComponent,Todo是个类,包含id和name属性. @Component({ selector: 'todo-list', template: ` <p *ngFor='let item of todos'>{{ item.name }}</p> `, }) export class TodoComponent{ @Inpu

  • es7学习教程之Decorators(修饰器)详解

    本文主要给大家介绍的是关于es7 Decorators(修饰器)的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: ES6 Decorators(修饰器) 修饰器(Decorator)是一个函数,用来修改类的行为.这是ES7的一个提案,目前Babel转码器已经支持 我们在游戏大型项目种经常会用到的方法,现在es6直接支持 想要使用Decorator的话需要我们配置一下文件夹,配置一下环境 npm install babel-plugin-transform-decorato

  • C语言嵌入式实现支持浮点输出的printf示例详解

    目录 简介 背景 C语言可变参数函数 踩坑 功能实现 简介 mr-printf 模块为 mr-library 项目下的可裁剪模块,以C语言编写,可快速移植到各种平台(主要以嵌入式mcu为主). mr-printf 模块用以替代 libc 中 printf, 可在较小资源占用的同时支持绝大部分 printf 功能,于此同时还支持对单独功能模块的裁剪以减少用户不需要功能的资源占用. 背景 printf 大家应该使用的比较多,但是在嵌入式平台中,尤其是单片机中,libc中的printf对内存的占用较高

  • C语言编程题杨氏矩阵算法快速上手示例详解

    目录 题目概要 一.解题思路 二.具体代码 题目概要 有一个数字矩阵,矩阵的每行从左到右都是递增的,矩阵从上到下都是递增的,请编写程序在这样的矩阵中查找某个数字是否存在? 一.解题思路 对于查找一个数组中元素是否存在,很多同学第一想法就是从头到尾遍历一遍.这样的想法优点是代码简单且无脑容易上手,但是这样的缺点也很明显,比如是m *n的数组,你从头到尾遍历,最坏情况要找m *n次.题目给的相关条件比如从左向右递增,从上向下递增你也完全没有使用,这样的暴力求解显然不是我们想看到的 我们来介绍一种方法

  • Go语言基础Json序列化反序列化及文件读写示例详解

    目录 概述 JSON序列化 结构体转JSON map转JSON 切片转JSON JSON反序列化 JSON转map JSON转结构体 JSON转切片 写JSON文件 map写入JSON文件 切片写入JSON文件 结构体写入JSON文件 读JSON文件 解码JSON文件为map 解码JSON文件为切片 解码JSON文件为结构体 示例 概述 JSON(JavaScript Object Notation,JavaScript对象表示法)是一种轻量级的.键值对的数据交换格式.结构由大括号'{}',中括

  • Go语言基础for循环语句的用法及示例详解

    目录 概述 语法 注意 示例一  死循环,读取文件 示例二  打印乘法表 示例三  遍历字符串,数组,map 概述 for循环是一个循环控制结构,可以执行指定次数的循环 语法 第一种 for {} //无线循环 第二种 for 条件语句{} 第三种 for 初始语句; 条件语句; 迭代后语句 {} 第四种 for key,value:=range 表达式/变量{} 注意 省略初始条件,相当于while循环体必须用 { } 括起来初始化语句和后置语句是可选的如果只剩下条件表达式了,那么那两个分号也

  • Go语言基础switch条件语句基本用法及示例详解

    目录 概述 语法 第一种[switch 带上表达式] 第二种[switch 不带表达式] 第三种[switch 初始化,表达式] 注意 示例一[根据今天的日期打印今天星期几] 示例二[根据分数打印A,B,C,D] 示例三[算数] 概述 传入条件的不同,会执行不同的语句 每一个case分支都是唯一的,从上到下逐一测试,直到匹配为止. 语法 第一种[switch 带上表达式] switch 表达式 { case 表达式1,表达式2, ... : 语句块1 case 表达式3, 表达式4, ... :

  • C/C++语言八大排序算法之桶排序全过程示例详解

    基本思路是将所有数的个位十位百位一直到最大数的最高位一步步装桶,先个位装桶然后出桶,直到最高位入桶出桶完毕. 首先我们要求出一个数组的最大数然后求出他的最大位数 //求最大位数的函数 int getmaxweisu(int* a,int len)// { int max = a[0]; for (int i = 0; i < len; i++) { if (max < a[i]) { max = a[i]; } } int count = 1; while (max/10) { count++

  • Go语言leetcode题解953验证外星语词典示例详解

    目录 题目描述 思路分析 AC 代码 题目描述 953. 验证外星语词典 某种外星语也使用英文小写字母,但可能顺序 order 不同.字母表的顺序(order)是一些小写字母的排列. 给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true:否则,返回 false. 示例 1: 输入:words = ["hello","leetcode"], order = "hlabcdefgi

随机推荐