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会创建一个栈,把语句放入其中,当函数结束后会执行这个栈的语句,能方便释放资源
切片和数组是什么
数组是一种顺序储存的数据结构,它有固定长度,在声明的时候就要指定
切片是对底层数组的一个引用,是一个动态长度的数据结构,可以增删切片元素
函数返回值是什么,有名和匿名区别,return和c区别
返回值就是函数执行完成后所返回的变量值
有名返回值可以在函数内使用而不用声明,且return是可以不用再写返回的变量名称,而匿名的不行
golang的return可返回多个值,返回值也可分为匿名返回值和有名(具名)返回值,还可以额外返回error来判断函数是否执行成功
c语言指针和go指针区别
c语言指针常初始化为null,而go是nil
c语言指针可以进行一定算数操作,但也易形成野指针,而go没有
go有逃逸分析来确定变量生命周期和内存分配位置,c没有
变量生命周期
在Go语言中,变量的生命周期由它的作用域(scope)决定。变量的作用域是指变量在程序中可以被访问的范围。在Go语言中,变量的生命周期从它被声明的地方开始,直到它不再被引用为止。当一个变量不再被引用时,它会被Go的垃圾回收机制自动回收,释放相关的内存空间。
例如,在一个函数内部声明的局部变量的生命周期在函数执行期间,当函数执行完毕时,这些局部变量就会被销毁。全局变量的生命周期是整个程序的运行期间,它在程序启动时被创建,在程序结束时被销毁。
在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
42package 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
32package 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
84package 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) 是在进程内部运行的轻量级的执行单元。一个进程可以包含多个线程,这些线程共享同一个进程的内存空间和系统资源。线程之间的切换相比进程更加轻量级,因为它们共享同一个地址空间。多线程的主要优势在于能够更有效地利用多核处理器,提高程序的并发性,加快程序的响应速度。
- 区别:
- 进程是独立的程序执行实例,拥有自己的内存空间和系统资源;线程是进程内部的执行单元,共享同一进程的资源。
- 进程之间相互独立,互不干扰;线程之间共享同一进程的内存空间,可以方便地进行通信和数据共享。
- 进程切换需要较多的系统资源,线程切换相对较轻量级,开销较小
- 线程安全(Thread Safety) 是指当多个线程同时访问共享数据时,不会发生不可预料的结果。如果一个代码段在多线程环境下可以安全地执行,那么就是线程安全的。线程安全通常需要采取一些同步机制,比如
互斥锁(Mutex)或者信号量(Semaphore),来确保共享数据的一致性和正确性。线程安全的设计可以避免在多线程环境下出现数据竞争、死锁等问题,确保程序的稳定性和可靠性。
go语言协程的优点
- 轻量级: Go语言的协程比传统的线程更加轻量级,一个协程的内存占用远远小于一个线程,因此可以在一个程序中创建大量的协程,而不会消耗过多的内存。
- 并发性高: 由于协程的轻量级特性,Go语言可以轻松创建数以千计甚至更多的协程,实现大规模并发处理,提高程序的并发性能。
- 简单易用: 在Go语言中,创建和管理协程非常简单,使用关键字
go就可以启动一个新的协程,而不需要像传统的线程编程那样手动创建和管理线程。 - 自动管理: Go语言的运行时系统会自动在多个线程之间调度协程的执行,开发者无需手动干预,这样就避免了传统多线程编程中的锁、死锁等问题。
- 更好的利用多核处理器: Go语言的协程模型可以更好地利用多核处理器,通过在多个核心上并行运行协程,提高程序的处理能力。
- 避免回调地狱: 使用协程可以避免传统异步编程中的回调地狱(Callback Hell),使得异步编程更加简洁和可读。
- 内置的通信机制: Go语言提供了内置的通道(Channel)机制,可以方便地在协程之间进行通信和数据传递,实现协程间的同步和协作。
go语言channel是什么?有什么作用?
- 通道(Channel) 是一种用来在不同协程之间传递数据的机制。它是一种类型安全的、并发安全的数据结构,可以用来在协程之间传递数据,实现协程之间的通信和同步。
- 通道实际上是一个队列数据结构,先进先出
通道的作用:
- 协程之间的通信: 通道允许不同的协程之间安全地传递数据。一个协程可以将数据发送到通道,另一个协程可以从通道中接收数据。通道会自动处理发送和接收的同步,确保数据的安全传递。
- 协程之间的同步: 通道可以用于在协程之间进行同步操作。通常在一个协程中等待另一个协程的信号,直到接收到信号后再继续执行。通道可以用来实现这种同步机制,比如等待多个协程都完成某个任务后再执行下一步操作。
- 避免竞态条件: 通道可以用于避免多个协程访问共享资源时的竞态条件(Race Condition)问题。通过使用通道,可以确保每次只有一个协程可以访问共享资源,避免数据的不一致性。
- 控制协程的并发数: 通道可以用来限制并发协程的数量。通过控制通道的缓冲区大小,可以限制同时执行的协程数量,避免系统资源被过多的协程占用。
什么是阻塞?如何使用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后阻塞 //等待有元素进入管道- 这个代码未使用sync,而用管道实现线程安全。 - 代码执行顺序是初始化后启动GetJob线程和四个WorkerFunc线程,主线程阻塞在下面的for循环,其他线程执行结束后,主线程停止阻塞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
}
}