0%

Let's Go


Let‘s Go

大一曾经的工作室招新题go语言部分

Go基础

  • 反转链表

    • /**
       * Definition for singly-linked list.
       * type ListNode struct {
       *     Val int
       *     Next *ListNode
       * }
       */
      
      package main
      
      import (
          "fmt"
      )
      
      func reverseList(head *ListNode) *ListNode {
          if head == nil || head.Next == nil {
              return nil
          }
          p := head.Next
          last := head.Next
          head.Next = nil
          for last != nil {
              last = last.Next
              p.Next = head
              head = p
              if last == nil {
                  break
              }
              p = last
          }
          return p
      }//这是在leetcode的代码
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54

      - ```go
      package main

      import (
      "fmt"
      )

      type ListNode struct {
      Val int
      Next *ListNode
      }

      func reverseList(head *ListNode) *ListNode {
      var data int
      fmt.Scanln(&data)
      head = &ListNode{Val: data, Next: nil}
      LastNode := head
      for {
      _, err := fmt.Scanln(&data)
      if err != nil {
      fmt.Println("错误")
      break
      }
      NewNode := &ListNode{Val: data, Next: nil}
      LastNode.Next = NewNode
      LastNode = NewNode
      }
      if head == nil || head.Next == nil {
      return nil
      }
      p := head.Next
      last := head.Next
      head.Next = nil
      for last != nil {
      last = last.Next
      p.Next = head
      head = p
      if last == nil {
      break
      }
      p = last
      }
      return p
      }

      func main() {
      var head *ListNode = nil
      p := reverseList(head)
      for p != nil {
      fmt.Println(p.Val)
      p = p.Next
      }
      }//这是自己的完整代码
  • 螺旋数组

    • package main
      
      import "fmt"
      
      func PutNum(matrix [][]int, n int, floor int) {//floor为递归第几层,
          //相当于第几个环,n为宽度
          if n <= 0 {
              return
          }
          for i := 0; i < n; i++ {
              if floor == 1 && i == 0 {
                  matrix[0][0] = 1
              } else {
                  matrix[floor-1][floor+i-1] = matrix[floor-1][floor+i-2] + 1
              }
          }
          for i := 1; i <= n-1; i++ {
              if n > 1 {
                  matrix[floor+i-1][floor+n-2] = matrix[floor+i-2][floor+n-2] + 1
              }
          }
          for i := 1; i <= n-1; i++ {
              matrix[floor+n-2][floor+n-2-i] = matrix[floor+n-2][floor+n-i-1] + 1
          }
          for i := 1; i <= n-2; i++ {
              matrix[floor+n-2-i][floor-1] = matrix[floor+n-i-1][floor-1] + 1
          }
          PutNum(matrix, n-2, floor+1)
      }
      func generateMatrix(n int) [][]int {
          matrix := make([][]int, n)
          for i := 0; i < n; i++ {
              matrix[i] = make([]int, n)
          }
          PutNum(matrix, n, 1)
          return matrix
      }//想用递归实现,写的有些草率和难看,但通过了数据测试
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44

      - 逆波兰表达式

      - ```go
      package main

      import (
      "fmt"
      "strconv"
      )

      func evalRPN(tokens []string) int {
      var StackNum [100]int
      var top int = 10
      var ascll int
      for i := 0; i < len(tokens); i++ {
      _, err := strconv.Atoi(tokens[i])
      if err != nil {//如果是运算符而不是数字就进行运算
      ascll = int(tokens[i][0])
      switch ascll {
      case 43:
      StackNum[top-1] = StackNum[top] + StackNum[top-1]
      top--
      fmt.Println(StackNum[top])
      case 45:
      StackNum[top-1] = StackNum[top] - StackNum[top-1]
      top--
      fmt.Println(StackNum[top])
      case 47:
      StackNum[top-1] = StackNum[top-1] / StackNum[top]
      top--
      fmt.Println(StackNum[top])
      case 42:
      StackNum[top-1] = StackNum[top] * StackNum[top-1]
      top--
      fmt.Println(StackNum[top])
      }
      } else {//数字入栈
      top++
      StackNum[top], _ = strconv.Atoi(tokens[i])
      }
      }
      return StackNum[11]
      }//用栈实现,不知道为什么定义top=-1会越界,所以让top取大一些

Go项目

  • Task1:

    • A.命令行参数

      • package main
        
        import (
            "flag"
            "fmt"
        )
        
        var infile *string = flag.String("i", "infile", "File contains values for sorting")
        var outfile *string = flag.String("o", "outfile", "File to receive sorted values")
        var algorithm *string = flag.String("a", "qsort", "Sort algorithm")
        
        func main() {
            flag.Parse()
        
            if infile != nil {
                fmt.Println("infile =", *infile, "outfile =", *outfile, "algorithm = ", *algorithm)
            }
        }
        //flag包用来解析命令行参数
        //在cmd的main目录下go run main.go 然后依次输入-i ...  -o ...  -a ... 就会获得相应参数并打印
        //例如我在main目录下输入go run main.go -i "D:\test\test100\input.txt" -o "D:\test\test100\output.txt" -a bubble
        //输出为infile = D:\test\test100\input.txt outfile = D:\test\test100\output.txt algorithm =  bubble
        //其中-i 用于指定输入文件名-o 用于指定输出文件名-a 用于指定排序算法
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
          
        - Go build会编译源代码生成可执行文件并保存,而Go run会编译并执行程序但不生成可执行文件,而是在内存中直接编译并运行。

        - 实现报错

        ```go
        package main

        import (
        "flag"
        "fmt"
        "os"
        )

        var infile *string = flag.String("i", "infile", "File contains values for sorting")
        var outfile *string = flag.String("o", "outfile", "File to receive sorted values")
        var algorithm *string = flag.String("a", "qsort", "Sort algorithm")

        func main() {
        flag.Parse()

        if flag.NFlag() == 0 {
        flag.PrintDefaults()
        os.Exit(1)
        }//命令行没参数,打印默认参数

        if *algorithm != "qsort" && *algorithm != "bubblesort" {
        fmt.Println("Error: Unsupported sorting algorithm")
        os.Exit(1)
        }

        fmt.Println("infile =", *infile, "outfile =", *outfile, "algorithm =", *algorithm)
        }
        //引入os包实现错误时的程序退出文件读写操作在后面主函数处
      • 文件读写在后面主函数实现

      • defer会创建一个栈,把语句放入其中,当函数结束后会执行这个栈的语句,能方便释放资源

      • 切片和数组是什么

        1. 数组是一种顺序储存的数据结构,它有固定长度,在声明的时候就要指定

        2. 切片是对底层数组的一个引用,是一个动态长度的数据结构,可以增删切片元素

      • 函数返回值是什么,有名和匿名区别,return和c区别

        1. 返回值就是函数执行完成后所返回的变量值

        2. 有名返回值可以在函数内使用而不用声明,且return是可以不用再写返回的变量名称,而匿名的不行

        3. golang的return可返回多个值,返回值也可分为匿名返回值和有名(具名)返回值,还可以额外返回error来判断函数是否执行成功

      • c语言指针和go指针区别

        1. c语言指针常初始化为null,而go是nil

        2. c语言指针可以进行一定算数操作,但也易形成野指针,而go没有

        3. go有逃逸分析来确定变量生命周期和内存分配位置,c没有

      • 变量生命周期

        1. 在Go语言中,变量的生命周期由它的作用域(scope)决定。变量的作用域是指变量在程序中可以被访问的范围。在Go语言中,变量的生命周期从它被声明的地方开始,直到它不再被引用为止。当一个变量不再被引用时,它会被Go的垃圾回收机制自动回收,释放相关的内存空间。

          例如,在一个函数内部声明的局部变量的生命周期在函数执行期间,当函数执行完毕时,这些局部变量就会被销毁。全局变量的生命周期是整个程序的运行期间,它在程序启动时被创建,在程序结束时被销毁。

        2. 在C语言中,变量的生命周期由它的作用域和存储类别(storage class)决定。C语言中的变量可以有不同的存储类别,包括自动变量(auto)、静态变量(static)、寄存器变量(register)、外部变量(extern)等。不同的存储类别决定了变量的生命周期和可见性。是程序员自己分配和决定的

      • Qsort

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      package qsort

      import "fmt"

      func Qsort(values []int, left int, right int) {
      lp := left
      rp := right
      defer func() {
      if err := recover(); err != nil {
      fmt.Println(err)
      }
      }()//如果出现问题捕获错误
      var MidNum int = values[(lp+rp)/2]
      for {
      for {
      if values[lp] >= MidNum {
      break
      }
      lp++
      }
      for {
      if values[rp] <= MidNum {
      break
      }
      rp--
      }
      if lp <= rp {
      values[lp], values[rp] = values[rp], values[lp]
      lp++
      rp--
      }
      if lp > rp {
      break
      }
      }
      if lp < right {
      Qsort(values, lp, right)
      }
      if rp > left {
      Qsort(values, left, rp)
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      package qsort

      import (
      "testing"
      )

      func TestQsort1(t *testing.T) {
      values := []int{9, 2, 5, 6, 1, 4, 3, 7}
      Qsort(values, 0, (len(values) - 1))
      if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 ||
      values[4] != 5 || values[5] != 6 || values[6] != 7 || values[7] != 9 {
      t.Error("Qsort failed Got", values, "Expected 1 2 3 4 5 6 7 9")
      }
      }

      func TestQsort2(t *testing.T) {
      values := []int{3, 2, 1, 5, 4}
      Qsort(values, 0, len(values)-1)
      if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 ||
      values[4] != 5 {
      t.Error("Qsort failed Got", values, "Expected 1 2 3 4 5")
      }
      }

      func TestQsort3(t *testing.T) {
      values := []int{1, 2, 3, 4, 5, 7, 6, 8}
      Qsort(values, 0, len(values)-1)
      if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 ||
      values[4] != 5 || values[5] != 6 || values[6] != 7 || values[7] != 8 {
      t.Error("Qsort failed Got", values, "Expected 1 2 3 4 5 6 7 8")
      }
      }
      • 主程序
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        package main

        import (
        "bufio"
        "encoding/json"
        "flag"
        "fmt"
        "os"
        "strconv"
        "time"

        "main.com/algorithms/bubblesort"
        "main.com/algorithms/qsort"
        )

        var infile *string = flag.String("i", "infile", "File contains values for sorting")
        var outfile *string = flag.String("o", "outfile", "File to receive sorted values")
        var algorithm *string = flag.String("a", "qsort", "Sort algorithm")

        func readValues(infile string) (values []int, err error) {
        file, err := os.Open(infile)
        if err != nil {
        fmt.Println("无法打开文件", err)
        return
        }
        defer file.Close()
        scanner := bufio.NewScanner(file)
        var i int = 0
        for scanner.Scan() {
        strnum := scanner.Text()
        Num, _ := strconv.Atoi(strnum)
        values = append(values, Num)
        fmt.Println(values[i], i)//显示读到的值和位次
        i++
        }
        return values, err
        }
        func writeValues(values []int, outfile string) error {
        file, err := os.OpenFile(outfile, os.O_WRONLY, 0666)
        if err != nil {
        fmt.Println("无法打开结果文件", err)
        return err
        }
        defer file.Close()
        jsonValues, err := json.Marshal(values)
        jsonValues = append(jsonValues, '\n') //每组数据后换行
        if err != nil {
        fmt.Println("转换为json出错", err)
        return err
        }
        _, err = file.Write(jsonValues)
        if err != nil {
        fmt.Println("写入错误", err)
        }
        return nil
        }
        func main() {
        flag.Parse()
        if infile != nil {
        fmt.Println("infile =", *infile, "outfile =", *outfile, "algorithm = ", *algorithm)
        }
        values, err := readValues(*infile)
        if err == nil {
        fmt.Println("Read Values:", values)
        t1 := time.Now()
        switch *algorithm {
        case "qsort":
        qsort.Qsort(values, 0, len(values)-1)
        case "bubblesort":
        bubblesort.BubbleSort(values)
        default:
        fmt.Println("Error: Unsupported sorting algorithm")
        os.Exit(1)
        }
        t2 := time.Now()

        fmt.Println("The Sorting costs", t2.Sub(t1), "to complete")

        writeValues(values, *outfile)
        } else {
        fmt.Println(err)
        }
        }

    Go协程与管道

  • 什么是并发?什么是并行

    • 并发(Concurrency) 指的是一个系统能够同时处理多个任务或者同时处理多个事件。这并不意味着系统同时处理所有的任务,而是系统在多个任务之间快速切换,使得在宏观上给人的感觉是同时执行的。并发通常用于提高系统资源的利用率,提高响应速度和改善用户体验。在并发中,任务之间的执行顺序可能是不确定的,这取决于系统的调度算法。
    • 并行(Parallelism) 指的是系统同时执行多个任务或者操作,实际上是多个处理器或者计算机核心同时执行不同的任务,这样可以加速程序的执行速度。并行通常需要硬件支持,例如多核处理器或者分布式系统。在并行中,不同的任务会在不同的处理单元上同时执行,各个任务之间是独立的,彼此之间不会干扰。
  • 什么是进程?什么是线程?区别?线程安全是什么?

    • 进程(Process) 是指在计算机中运行的程序的实例。每个进程都有自己的内存空间、代码、数据和系统资源,它们之间相互独立,互不干扰。每个进程都有自己的程序计数器(Program Counter)和堆栈(Stack),用于执行和管理程序的运行。进程之间的切换会消耗较多的系统资源,因此进程通常被用来执行较为独立的任务。
    • 线程(Thread) 是在进程内部运行的轻量级的执行单元。一个进程可以包含多个线程,这些线程共享同一个进程的内存空间和系统资源。线程之间的切换相比进程更加轻量级,因为它们共享同一个地址空间。多线程的主要优势在于能够更有效地利用多核处理器,提高程序的并发性,加快程序的响应速度。
    • 区别:
      1. 进程是独立的程序执行实例,拥有自己的内存空间和系统资源;线程是进程内部的执行单元,共享同一进程的资源。
      2. 进程之间相互独立,互不干扰;线程之间共享同一进程的内存空间,可以方便地进行通信和数据共享。
      3. 进程切换需要较多的系统资源,线程切换相对较轻量级,开销较小
    • 线程安全(Thread Safety) 是指当多个线程同时访问共享数据时,不会发生不可预料的结果。如果一个代码段在多线程环境下可以安全地执行,那么就是线程安全的。线程安全通常需要采取一些同步机制,比如互斥锁(Mutex)或者信号量(Semaphore),来确保共享数据的一致性和正确性。线程安全的设计可以避免在多线程环境下出现数据竞争、死锁等问题,确保程序的稳定性和可靠性。
  • go语言协程的优点

    • 轻量级: Go语言的协程比传统的线程更加轻量级,一个协程的内存占用远远小于一个线程,因此可以在一个程序中创建大量的协程,而不会消耗过多的内存。
    • 并发性高: 由于协程的轻量级特性,Go语言可以轻松创建数以千计甚至更多的协程,实现大规模并发处理,提高程序的并发性能。
    • 简单易用: 在Go语言中,创建和管理协程非常简单,使用关键字go就可以启动一个新的协程,而不需要像传统的线程编程那样手动创建和管理线程。
    • 自动管理: Go语言的运行时系统会自动在多个线程之间调度协程的执行,开发者无需手动干预,这样就避免了传统多线程编程中的锁、死锁等问题。
    • 更好的利用多核处理器: Go语言的协程模型可以更好地利用多核处理器,通过在多个核心上并行运行协程,提高程序的处理能力。
    • 避免回调地狱: 使用协程可以避免传统异步编程中的回调地狱(Callback Hell),使得异步编程更加简洁和可读。
    • 内置的通信机制: Go语言提供了内置的通道(Channel)机制,可以方便地在协程之间进行通信和数据传递,实现协程间的同步和协作。
  • go语言channel是什么?有什么作用?

      • 通道(Channel) 是一种用来在不同协程之间传递数据的机制。它是一种类型安全的、并发安全的数据结构,可以用来在协程之间传递数据,实现协程之间的通信和同步。
      • 通道实际上是一个队列数据结构,先进先出
    • 通道的作用:

      1. 协程之间的通信: 通道允许不同的协程之间安全地传递数据。一个协程可以将数据发送到通道,另一个协程可以从通道中接收数据。通道会自动处理发送和接收的同步,确保数据的安全传递。
      2. 协程之间的同步: 通道可以用于在协程之间进行同步操作。通常在一个协程中等待另一个协程的信号,直到接收到信号后再继续执行。通道可以用来实现这种同步机制,比如等待多个协程都完成某个任务后再执行下一步操作。
      3. 避免竞态条件: 通道可以用于避免多个协程访问共享资源时的竞态条件(Race Condition)问题。通过使用通道,可以确保每次只有一个协程可以访问共享资源,避免数据的不一致性。
      4. 控制协程的并发数: 通道可以用来限制并发协程的数量。通过控制通道的缓冲区大小,可以限制同时执行的协程数量,避免系统资源被过多的协程占用。
  • 什么是阻塞?如何使用channel实现阻塞?

    • 阻塞(Blocking) 是指一个操作或者线程被暂停,直到满足某个条件才能继续执行。在Go语言中,通常在使用通道(Channel)时会涉及到阻塞的概念。

      当在一个通道上进行发送操作(channel <- value)时,如果通道已满,发送操作将被阻塞,直到有其他协程从通道中接收数据,腾出空间来,发送操作才能继续执行。同样,当在一个通道上进行接收操作(value <- channel)时,如果通道为空,接收操作将被阻塞,直到有其他协程向通道中发送数据,接收操作才能继续执行。

    • package main
      
      import (
          "fmt"
          "time"
      )
      
      func main() {
          ch := make(chan int)
      
          go func() {
              time.Sleep(time.Second * 10)
              ch <- 1
          }()
      
          fmt.Println("Waiting for data...")
          data := <-ch
          fmt.Println("Received data:", data)
      }
      //这个例子就能很好地展现管道的阻塞总能,让主程序能在打印第一个waiting for data后阻塞
      //等待有元素进入管道
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54

      - 通过goroutine模拟四个工人同时处理10个工作

      - ```go
      package main

      import (
      "fmt"
      "time"
      )

      func WorkerFunc(JobResultChan chan int, job chan int, num int, exitChan chan bool) {
      defer func() {
      if err := recover(); err != nil {
      fmt.Println("发生错误")
      }
      }()
      for {
      i, ok := <-job
      if !ok {
      break
      }
      time.Sleep(time.Second * 1)
      fmt.Printf("第%d个工人完成了第%d份工作\n", num+1, i)
      JobResultChan <- i
      }
      exitChan <- true
      fmt.Println("一个协程关闭")

      }
      func GetJob(job chan int) {
      defer func() {
      if err := recover(); err != nil {
      fmt.Println("发生错误")
      }
      }()
      for i := 1; i <= 10; i++ {
      job <- i
      }
      close(job)//工作全放入管道后关闭管道,否则会死锁
      }
      func main() {
      job := make(chan int, 10)
      WorkerNum := 4
      exitChan := make(chan bool, WorkerNum)
      JobResultChan := make(chan int, 10)
      go GetJob(job)
      for i := 0; i < WorkerNum; i++ {
      go WorkerFunc(JobResultChan, job, i, exitChan)
      }
      for i := 0; i < 4; i++ {
      <-exitChan
      }
      }
      - 这个代码未使用sync,而用管道实现线程安全。 - 代码执行顺序是初始化后启动GetJob线程和四个WorkerFunc线程,主线程阻塞在下面的for循环,其他线程执行结束后,主线程停止阻塞