Skip to content

Latest commit

 

History

History
836 lines (510 loc) · 28.7 KB

File metadata and controls

836 lines (510 loc) · 28.7 KB

Golang 基础题

1、关于类型转化,下面语法正确的是()

A:

type MyInt int
var j int = 1
var i MyInt = i

B:

type MyInt int
var j int = 1
var i MyInt = (MyInt)i

C:

type MyInt int
var j int = 1
var i MyInt = MyInt(i)

C:

type MyInt int
var j int = 1
var i MyInt = i.(MyInt)

正确答案:C

官方解析: 在 Go 语言中,类型转换需要遵循特定的语法规则。C 选项是正确的,因为它使用了 Go 语言标准的类型转换语法 MyInt(i)。

分析各个选项:

A选项错误: 直接将 int 类型赋值给 MyInt 类型是不允许的。即使 MyInt 底层类型是 int,Go 语言也要求显式类型转换。这体现了 Go 语言的类型安全特性。

B选项错误: Go 语言不支持 C 语言风格的 (MyInt)i 类型转换语法。这种写法在 Go 中是非法的。

C选项正确: 使用 MyInt(i) 是 Go 语言中标准的类型转换语法。这种方式清晰地表明了要将 i 转换为 MyInt 类型。这是 Go 设计者推荐的类型转换写法。

D选项错误: i.(MyInt) 这种语法在 Go 中是类型断言的语法,不是类型转换。类型断言只用于接口类型向具体类型的转换,而不能用于基本类型之间的转换。

总的来说,Go 语言的类型系统严格要求显式的类型转换,并且有特定的语法规则,这有助于提高代码的可读性和类型安全性。

2、对变量x的取反操作是~x,这一说法是否正确。

A: true

B: false

正确答案:B

官方解析:

在Golang中对变量x的取反操作不是~x,而是!x。这涉及到Golang中的逻辑运算符和位运算符的区别。

!x是逻辑取反运算符,用于布尔类型(boolean)的取反操作。当x为true时,!x的结果为false;当x为false时,!x的结果为true。

~x是按位取反运算符,它对整数类型的每一个二进制位进行取反操作(0变1,1变0)。比如:

如果x = 5 (二进制:0000 0101)

~x 的结果就是 -6 (二进制:1111 1010)

所以A选项说"对变量x的取反操作是~x"是错误的,因为:

  1. 在进行逻辑运算时,应该使用!x
  2. ~x是位运算,不是逻辑取反
  3. !x和~x的作用对象和运算结果完全不同
  4. 在实际编程中混淆这两个运算符可能导致程序逻辑错误

因此B选项"false"是正确答案。

3、下面的程序的运行结果是xello,这一说法是否正确。

func main() {
	str := "hello"
	str[0] = 'x'
	fmt.Println(str)
}

A: true

B: false

正确答案为:B

GO语言中字符串是不可变的,所以不能对字符串中某个字符单独赋值。

4、定义结构体类型 Foo struct{} 和接口 Bar { M() },其中方法实现为 func (f *Foo) M() {}。现在执行代码 var b Bar = Foo{};,哪个选项描述了编译结果?

A: 编译通过,运行时正确调用 M()

B: 编译错误,因为 Foo 值类型没有实现 Bar 接口

C 编译通过,但运行时报指针接收者错误

D 编译错误,因为接口声明无效

正确答案:B

在Go语言中,接口实现基于方法集。方法func (f *Foo) M() {}使用指针接收者定义,因此只有*Foo类型实现了Bar接口,而Foo值类型没有实现Bar接口。

代码var b Bar = Foo{}试图将值类型赋值给接口变量,会触发编译错误,错误信息通常为'Foo does not implement Bar (M method has pointer receiver)'。

选项B正确描述了这一错误原因;选项A和C错误,因为编译不会通过;选项D错误,因为接口声明Bar { M() }是有效的。

5、下面关于 Go 语言中的 goroutine 说法正确的是()

A:goroutine 是轻量级的执行单元,它的创建和销毁的开销相较于传统的线程要小得多

B:当一个 goroutine 发生 panic(运行时异常),即使没有被恢复,也不会终止整个程序

C:每个 goroutine 都有独立的栈空间和寄存器,但是它们共享同一个地址空间

D:goroutine 之间可以使用 channel 来进行数据传递

正确答案:ACD

goroutine 是 Go 语言中的一个重要特性,让我们逐个分析各选项:

A选项正确:goroutine 确实是一个轻量级的执行单元。与传统的操作系统线程相比,goroutine 的创建和销毁开销非常小。每个 goroutine 初始只需要 2KB 左右的栈空间,而传统线程需要 1MB 或更多的栈空间。

C选项正确:每个 goroutine 确实拥有自己独立的栈空间和寄存器。所有的 goroutine 共享同一个进程的地址空间,这使得它们能够高效地进行内存共享,同时又保持了必要的隔离性。

D选项正确:channel 是 Go 语言提供的 goroutine 之间的通信机制。它遵循 CSP(Communicating Sequential Processes) 模型,能够安全地在不同的 goroutine 之间传递数据。

B选项错误:当一个 goroutine 发生 panic 时,如果没有被 recover 恢复,整个程序会终止执行。这是因为 Go 的设计理念是快速失败,一旦发生未处理的 panic,程序就会立即停止以防止产生不可预知的后果。

综上所述,A、C、D 三个选项准确描述了 goroutine 的特性,而 B 选项描述的情况与 Go 语言的实际行为不符。

6、context 包是 Go 中用于处理请求范围内的值、取消信号和超时的标准库。分析以下代码,它的输出最可能是什么?

func worker(ctx context.Context) {
    select {
    case <-time.After(2 * time.Second):
        fmt.Println("Worker completed successfully")
    case <-ctx.Done():
        fmt.Printf("Worker cancelled: %v\n", ctx.Err())
    }
}
func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()
    go worker(ctx)
    time.Sleep(500 * time.Millisecond)
    fmt.Println("Main function finished")
}

A:先打印 “Worker completed successfully”,再打印 “Main function finished”。

B:先打印 “Worker cancelled: context deadline exceeded”,再打印 “Main function finished”。

C:只打印 “Main function finished”。

D:程序发生死锁。

正确答案:B

代码分析:在main函数中,context.WithTimeout设置100毫秒超时。启动的worker函数内,select语句中两个case:1) time.After(2秒) 需等待2秒,2) ctx.Done()在100毫秒超时后立即触发。由于100毫秒 < 2秒,ctx.Done()会先被触发,打印"Worker cancelled: context deadline exceeded"。主函数随后休眠500毫秒后打印"Main function finished"。因此输出顺序符合选项B。题目无文字错误、逻辑矛盾或专业名称错误。

7、分析以下 Go 代码,函数 test() 的返回值是多少?

func test() (i int) {
    defer func() {
        i++
    }()
    return 5
}

A:5

B:6

C:0

D:编译错误

正确答案:B

函数使用命名返回值 i。在 Go 语言中,当函数有命名返回值时,return 语句会将值赋给该变量(本例中 return 5 将 i 赋值为 5),但 defer 语句在 return 后、函数实际返回前执行,这里 defer 执行 i++ 将 i 增加到 6。因此函数最终返回 6。选项分析:A:5 忽略 defer 影响;C:0 未考虑初始赋值;D:代码无语法错误,编译正常。

8、在一个长期运行的 for-select 事件循环中需要做超时控制。哪种写法最能避免潜在的定时器泄漏与不必要的分配?

A 在每次 select 中直接使用 time.After(d)

B 每轮创建新的 time.NewTicker,并在用完后立即 Stop

C 循环外创建一个 time.Timer,select 后根据分支 Reset(d) 复用,并在不再使用时 Stop

D 用 time.Sleep(d) 替代定时分支,避免分配

正确答案:C

在长期运行的 for-select 事件循环中,超时控制需要避免定时器泄漏(未释放资源)和不必要的内存分配。

C 选项通过在循环外创建一次 time.Timer,在 select 后根据分支执行情况重置超时时间(Reset(d)),并在不再使用时停止定时器(Stop),实现了定时器的复用,从而避免了每次循环创建新对象的内存分配,并确保资源被正确管理以防止泄漏。

相比之下:A 选项使用 time.After(d) 会在每次循环中创建新的定时器通道,导致旧定时器未被及时回收,引发潜在泄漏和持续分配;

B 选项虽在创建后立即停止 time.NewTicker,但每轮创建新 ticker 导致不必要的分配,且 ticker 适用于周期性任务,而非一次性超时;

D 选项使用 time.Sleep(d) 会阻塞整个事件循环,使程序无法及时响应其他事件,违背了 for-select 非阻塞设计的初衷。

因此,C 是最佳写法。题目文字与专业术语均无误,无逻辑矛盾或考试规则破坏。

9、如下代码的执行结果为?

package main
import (
    "fmt"
)
func main() {
    testMap := make(map[string]string)
    testMap["a"] = "a"
    testMap["b"] = "b"
    testMap["c"] = "c"
    for _, v := range testMap {
        fmt.Print(v)
    }
}

A abc

B 报 panic

C ccc

D 随机打印

正确答案:D

在 Go 语言中,map 在遍历时的顺序是随机的,这是语言本身的特性设计。即使是相同的 map,每次遍历的顺序都可能不同。这是因为 Go 在遍历 map 时会随机选择一个起始位置开始遍历,以防止程序员依赖遍历顺序编写代码。

代码中使用 range 遍历 testMap,每次运行程序时打印的 "a"、"b"、"c" 的顺序都可能不同。这就是为什么 D 选项"随机打印"是正确答案。

分析其他选项: A错误:虽然"abc"是这些值的一种可能输出顺序,但不是固定的顺序,所以不能保证每次都是这个顺序。

B错误:这段代码完全合法,不会出现 panic。map 的遍历是一个正常的操作。

C错误:"ccc"完全不符合逻辑,因为 map 中存储的是三个不同的值("a"、"b"、"c"),不可能输出三个相同的字符。

这种随机性设计是有意义的:它可以避免开发者在业务逻辑中依赖 map 的遍历顺序,因为这种依赖可能导致难以发现的 bug。如果需要按照特定顺序遍历 map,应该先将 key 取出并排序,然后按照排序后的 key 来访问 map。

10、如下代码的输出是什么?

package main
import (
    "fmt"
)
func main() {
    var nums = make([]int, 0, 4)
    for i := 1; i <= 4; i++ {
        Append(nums, i)
    }
    fmt.Println(nums)
}
func Append(nums []int, num int) {
    nums = append(nums, num)
}

A []

B [0,0,0,0]

C [1,2,3,4]

D [0,0,0,0,1,2,3,4]

正确答案:A

这道题目考察了Go语言中切片参数传递的机制和append函数的特性。

为什么A选项[]是正确答案:

  1. 在Go中,切片作为参数传递时是按值传递的,传递的是切片的副本
  2. Append函数中的append操作作用在了副本上,而不是原始切片nums上
  3. 虽然在Append函数内部完成了追加操作,但这个修改对原始的nums没有影响
  4. 所以main函数中的nums始终保持为空切片,最终打印出[]

分析其他选项的错误原因: B选项[0,0,0,0]错误:

  • make([]int, 0, 4)创建的是一个长度为0、容量为4的切片,而不是长度为4的切片
  • 如果是make([]int, 4)才会得到[0,0,0,0]

C选项[1,2,3,4]错误:

  • 如果想得到这个结果,需要在main函数中直接使用nums = append(nums, i)
  • 或者让Append函数返回新的切片并在main中接收

D选项[0,0,0,0,1,2,3,4]错误:

  • 这个结果完全不符合程序逻辑
  • 切片既没有被初始化为[0,0,0,0],也没有成功追加元素

这个题目很好地展示了Go语言中的一个常见陷阱:如果需要在函数中修改切片,应该返回新的切片或使用切片指针作为参数。

11、关于Go中的panic和recover机制,以下哪项是recover函数正确使用的必要条件?

A 在main函数中调用

B 在非defer函数中调用

C 在defer函数的内部调用

D 在goroutine起始时调用

正确答案:C

在Go语言中,recover函数必须在一个通过defer调用的函数内部执行,才能捕获panic并恢复程序执行。例如,正确用法如:defer func() { if r := recover(); r != nil { ... } }()。选项A错误,recover不一定在main函数调用,可在任意函数中;选项B错误,在非defer函数中调用recover无效;选项D错误,在goroutine起始时调用不是必要条件,关键在于是否在defer中。

12、以下代码段中,关于defer语句的执行顺序及对返回值的影响,正确的是:

func f() (x int) {
    defer func() { x++ }()
    return 5
}

A 函数返回 5,defer 中对 x 的修改不影响返回值

B 函数返回 6,defer 在 return 后执行但能修改命名返回值

C 函数返回 5,defer 中对 x 的修改在函数结束后生效

D 函数返回 0,defer 中的逻辑会覆盖 return 的值

正确答案:B

该函数使用命名返回值x。执行顺序如下:

  1. return 5将5赋值给x;
  2. defer语句在return后执行,但函数返回前,x++将x递增为6;
  3. 函数最终返回x的值6。选项B正确描述了此行为。

选项A错误,因为defer修改了命名返回值;选项C错误,因为修改在返回前生效;选项D错误,因为返回值是6而非0,defer未覆盖return值。

13、给定如下代码片段:s := make([]int, 0, 2); s = append(s, 1, 2); s2 := s[:1]; s = append(s, 3)。以下哪项描述是正确的?

A 追加 3 时触发扩容,s 与 s2 不再共享底层数组;之后修改 s2[0] 不会影响 s[0]

B s 与 s2 始终共享底层数组,任何一方修改都会影响另一方

C 追加 3 不会触发扩容,因为 cap 足够

D 对 s2 执行 append 一定会修改 s 的底层数组

正确答案:A

题目无文字错误(无错别字或术语拼写错误)、无逻辑矛盾、无专业名称错误、无规则破坏错误(傻瓜错误)。

分析代码:s初始长度为0、容量为2;append(s,1,2)后,s长度为2、容量为2;s2 := s[:1] 创建新切片,共享底层数组;s = append(s,3) 时,s的len=2、cap=2,追加新元素触发扩容(cap不足),s指向新底层数组,而s2仍指向旧数组,因此s与s2不再共享。

随后修改s2[0]只会影响旧数组,不影响新数组中的s[0]。选项A描述正确:追加3触发扩容且不共享,修改s2[0]不影响s[0]。选项B错误,因扩容后不共享;选项C错误,因cap=2不足,追加触发扩容;选项D错误,因s扩容后,s2的append可能修改旧数组,不影响s的新数组。

14、关于通道的关闭与使用,以下哪项是正确的?

A 从已关闭且已被耗尽的通道接收会引发 panic

B 向已关闭的通道发送会返回 ok=false,而非 panic

C 对一个已经关闭的通道再次调用 close 会导致 panic

D 已关闭的通道不能被 range 遍历

正确答案:C

在 Go 语言中,通道的关闭与使用规则如下:

  • 选项 A 错误:从已关闭且已被耗尽的通道接收不会引发 panic,而是立即返回通道元素类型的零值(例如,int 通道返回 0)。
  • 选项 B 错误:向已关闭的通道发送会引发 panic,而非返回 ok=false;ok=false 仅用于接收操作中以检查通道状态。
  • 选项 C 正确:对一个已经关闭的通道再次调用 close 函数会导致 panic,这是 Go 语言的标准行为。
  • 选项 D 错误:已关闭的通道可以被 range 遍历,但遍历会立即结束,因为通道无数据,不会引发错误;只是循环体不会执行。

15、下面关于 Go 语言中字符串的说法错误的是()

A 字符串的数据结构,只包含一个成员,即字符串的首地址

B 字符串可以使用字符“+”拼接

C 可以通过指定下标修改字符串中的某一个值

D 字符串不支持取地址操作

正确答案:AC

字符串不支持取地址(指的是字面量地址如: &"abc")操作,也就无法修改字符串的值。

string 的数据结构包含两个成员:str-字符串的首地址,len-字符串的长度

16、在Go中,当两个slice共享底层数组时(如s1 := []int{1, 2, 3}; s2 := s1[0:2]),修改s2的第一个元素会:

A 影响s1的对应元素

B 不影响s1,因为新slice独立

C 仅在append操作后才影响s1

D 导致运行时panic

正确答案:A

在Go中,slice是引用类型,当s2通过s1[0:2]创建时,s1和s2共享相同的底层数组。修改s2的第一个元素(如s2[0] = 10)会直接修改底层数组,因此会影响s1的对应元素。选项B错误,因为新slice并非独立;选项C错误,影响发生在修改时,无需append操作;选项D错误,该操作不会导致panic。

17、在 Go 的错误处理中,使用 errors.Is(err, target) 函数时,它主要用于检查什么?

A 比较两个错误值是否完全相等

B 判断错误类型是否与 target 类型相同

C 检查错误链中是否存在特定错误实例

D 提取错误消息并匹配字符串

正确答案:C

在 Go 语言中,errors.Is(err, target) 函数主要用于检查错误链中是否存在特定的错误实例,它会遍历错误链(通过 Unwrap 方法)以判断是否有任何错误等于目标错误或满足自定义 Is 方法的相等性条件。

选项 C 准确描述了这个行为。选项 A 错误,因为它仅比较错误值是否完全相等,忽略了错误链的遍历; 选项 B 错误,因为它强调类型是否相同,但实际上 errors.Is 可能通过自定义 Is 方法处理相等性; 选项 D 错误,因为它涉及字符串匹配,而 errors.Is 是基于错误值或类型的比较,与消息无关。

18、如下代码的打印内容是什么?

package main

import (
    "fmt"
    "sync"
)
func main() {
    var (
        wg    = &sync.WaitGroup{}
        count int
    )
    wg.Add(100)
    for i := 1; i <= 100; i++ {
        defer wg.Done()
        go func() {
            count += i
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

A 0

B 5050

C 不可预期

D 程序崩溃

正确答案:D

这道题目考察了 Go 语言中并发编程和闭包的知识点。程序会崩溃(D选项正确),原因如下:

主要问题出在闭包捕获循环变量 i 上。在这段代码中:

  1. defer wg.Done() 是在循环中直接调用的,而不是在 goroutine 中调用
  2. 由于 defer 语句的特点,它会在函数返回时按照 LIFO(后进先出)的顺序执行
  3. 当循环结束后,wg.Done() 会被立即调用 100 次,而此时部分 goroutine 可能还未执行完成
  4. 这会导致 WaitGroup 的计数器变为负数,触发 panic

分析其他选项:

A(0)错误: 即使程序不崩溃,由于存在并发访问,结果也不会是 0 B(5050)错误: 5050 是 1+2+...+100 的理想结果,但由于并发访问没有同步机制,实际无法得到这个结果 C(不可预期)错误: 程序根本无法正常运行到输出结果的阶段

要修复这个问题,应该:

  1. 将 defer wg.Done() 移入 goroutine 内部
  2. 使用互斥锁保护共享变量 count
  3. 通过传参方式传入 i 的副本,避免闭包捕获循环变量

19、在Goroutine中使用无缓冲channel进行通信时,以下哪种情况会导致发送操作永久阻塞?

A 接收端因panic退出,发送端继续发送

B 接收端调用了close(ch)后,发送端继续发送

C 发送端在select中同时监听多个channel

D 发送端通过time.After设置超时机制

正确答案:A

在无缓冲channel中,发送操作阻塞直到有接收操作。选项A:接收端panic退出后,channel仍存在但无接收者,发送操作会永久阻塞;

选项B:向已关闭的channel发送数据会立即panic,不会阻塞;

选项C:select中监听多个channel可以避免永久阻塞,因select可能选择其他可操作channel;

选项D:time.After设置的超时机制会防止永久阻塞。

因此,只有A会导致发送操作永久阻塞。

20、关于 Go 的内存模型与通道的同步关系,以下哪项说法是正确的?

A 只要两个 goroutine 使用了同一个缓冲通道,即使没有发生发送或接收,也能建立内存可见性的顺序。

B 关闭一个通道后,另一个 goroutine 成功执行一次从该通道的接收(该接收返回零值并表明通道已关闭),该接收在内存模型上发生在 close 之后,因此能观察到 close 之前完成的写入。

C 使用 len(ch) 判断通道是否为空即可作为可靠的同步屏障,从而保证内存可见性。

D 在单核 CPU 上,对共享变量的普通读写天然有序,无需任何同步原语也不会产生数据竞态。

正确答案:B

根据Go内存模型:

  • A错误,因为仅共享通道而不进行实际发送或接收操作无法建立happens-before关系,内存可见性无法保证。
  • B正确,因为关闭通道时,接收操作(返回零值和关闭状态)在内存模型上发生在close之后,因此能观察到close前完成的写入。
  • C错误,len(ch)仅返回通道元素数量,并非同步点,无法作为同步屏障保证内存可见性。
  • D错误,在单核CPU上,由于Goroutine调度和编译器优化,共享变量的读写仍可能产生数据竞态,需要同步原语。

21、以下Go函数中的变量,不会逃逸到堆上的是?

A 函数返回一个结构体的指针,该结构体在函数内创建

B 函数内创建的切片,作为参数传递给另一个函数,该函数将切片存储到全局变量中

C 函数内创建的变量,被闭包引用且闭包被返回

D 函数内创建的int变量,作为值类型直接返回

正确答案:D

在Go语言的逃逸分析中:

  • A选项:函数返回指向局部结构体的指针,该结构体在函数返回后可能被外部引用,因此会逃逸到堆上。
  • B选项:函数内创建的切片作为参数传递,并最终存储到全局变量中,全局变量的生命周期长于函数,因此切片的底层数组会逃逸到堆上。
  • C选项:函数内创建的变量被闭包引用且闭包被返回,闭包捕获的变量在函数返回后仍可能被调用,因此会逃逸到堆上。
  • D选项:函数内创建的int变量作为值类型直接返回,返回值是副本,原始变量在函数返回后不再被引用,因此不会逃逸到堆上,可能分配在栈上。

22、一个高并发服务需要维护一个热表(map),读请求远多于写请求,键集合相对稳定。为保证线程安全和性能,最推荐的做法是?

A 直接使用内置 map,读多写少时是并发安全的

B 使用 map 搭配 sync.RWMutex 读写锁

C 使用 sync.Map,总是优于普通 map

D 使用全局 sync.Mutex 包住所有读写以避免死锁

正确答案:B

在Go语言中,内置map非并发安全,即使读多写少也可能因并发读写导致panic,因此选项A错误。

sync.Map针对特定场景优化,如键频繁变化或写极少且读极多,但并非总是优于普通map(尤其键集合稳定时普通map加锁可能更高效),选项C的'总是'说法错误。

全局sync.Mutex会让所有读写串行化,在读多写少场景下性能极差,不推荐,选项D错误。

使用map搭配sync.RWMutex读写锁,允许多个读操作并发执行,写操作互斥,适合读请求远多于写请求、键集合稳定的高并发场景,性能较好,因此选项B最推荐。

题目中'热表'虽非标准术语(应为'map'或'哈希表'),但括号内标注'map'已明确含义,不影响理解,无需视为错误。

23、下面关于 Go 语言中 map 的说法正确的是()

A map 操作不是原子的,多个协程同时操作 map 时有可能产生读写冲突

B 如果需要并发读写,可以使用锁来保护 map,也可以使用标准库 sync 包中的 sync.Map

C 查询或者删除未初始化的 map 会报错

D 使用 make() 函数初始化 map 时可以不指定 map 容量

正确答案:ABD

Go语言中map的特性题目,需要从并发安全性、初始化方式和基本操作等方面来分析。

A选项正确:Go语言的map不是并发安全的数据结构,当多个goroutine同时对同一个map进行读写操作时,可能会发生竞态条件,导致程序panic。

B选项正确:Go提供了两种方式来实现map的并发安全:

  1. 使用互斥锁(sync.Mutex)来保护map的读写操作
  2. 使用sync.Map,这是Go标准库提供的一个并发安全的map实现

D选项正确:使用make()函数初始化map时,容量参数是可选的。即可以写作make(map[KeyType]ValueType)make(map[KeyType]ValueType, capacity)

C选项错误:对未初始化的map(值为nil)进行查询操作是允许的,会返回该类型的零值;但对nil map进行写入或删除操作才会导致panic。具体来说:

  • 读取nil map返回零值
  • 删除nil map中的元素是一个空操作,不会报错
  • 只有向nil map写入数据时才会panic

因此ABD是正确答案。这体现了Go语言在map设计上的特点:既保持了使用的便利性,又为并发安全提供了完整的解决方案。

24、在 Go 中,当使用 range 遍历切片时,如果在循环体中修改了切片的结构(如通过 append 增加长度),会对遍历过程产生什么影响?

A 遍历索引会立即更新以反映新长度

B 不会影响遍历过程,因为 range 使用原始切片的快照

C 导致切片重新分配内存

D 可能引发索引越界错误

正确答案:B

在 Go 语言中,range 在遍历开始时对切片的长度进行求值并固定,不会在迭代过程中更新。因此,在循环体中通过 append 等操作修改切片结构(如增加长度)不会影响当前遍历过程,遍历只会基于初始长度进行。

选项 B 正确描述了这一行为,但'快照'一词虽不完全精确(range 实际使用切片头的副本,而非完整深拷贝),整体含义符合预期。

选项 A 错误,因为索引不会更新以反映新长度;选项 C 描述的是 append 可能导致的结果,并非对遍历过程的直接影响;

选项 D 错误,因为 range 迭代索引在初始长度范围内,不会引发索引越界错误。

25、对于以下代码,描述正确的是:

package main
 
import (
    "fmt"
    "sync"
)
 
func main() {
    var wg sync.WaitGroup
    intSlice := []int{1, 2, 3, 4, 5}
    wg.Add(len(intSlice))
    ans1, ans2 := 0, 0
    for _, v := range intSlice {
        vv := v
        go func() {
            defer wg.Done()
            ans1 += v
            ans2 += vv
        }()
    }
    wg.Wait()
    fmt.Printf("ans1:%v,ans2:%v", ans1, ans2)
    return
}

A ans1是 15 , ans2 是 15.

B ans1是 15 , ans2 不一定是 15.

C ans1不一定是 15, ans2 不一定是 15.

D ans1 不一定是 15 , ans2 是 15。

正确答案:C

在Go语言中,循环变量v在闭包中被多个goroutine共享,最终可能全部引用最后一次循环的值(5),而vv在每次循环中创建新变量,保存当前值。

但由于ans1和ans2的累加操作未加锁,存在数据竞争,导致结果不确定。ans1的最终值不可能是15(实际可能为25或更小),ans2的值也可能因竞争而不等于15。

因此,A(断言两者均为15)、B(ans1固定为15)、D(ans2固定为15)的说法均错误,只有C正确。

26、下面选项中,哪些 Go 程序代码不能正常编译运行()

A

func main() {
    var x [2]int
    x[2] = 10
    fmt.Println(x[2])
}

B

func main() {
    x := 42
    x, y := 10, "go"
    fmt.Println(x, y)
}

C

func main() {
    x := []int{}
    x = append(x, 10)
    fmt.Println(x[0])
}

D

func main() {
    var x map[string]int
    x["key"] = 10
    fmt.Println(x["key"])
}

正确答案: AD

A

var x [2]int
x[2] = 10
fmt.Println(x[2])

数组 x 的长度是 2,可访问的下标是 01x[2] 越界,会在编译期直接报错:

invalid array index 2 (out of bounds for 2-element array)

所以 A 无法编译通过


B

x := 42
x, y := 10, "go"
fmt.Println(x, y)

短变量声明 := 的规则是: 只要左侧有至少一个 新的变量,就允许重复声明已有变量。 这里 x 已存在,y 为新变量,所以合法。

运行输出:

10 go

所以 B 能正常编译运行


C

x := []int{}
x = append(x, 10)
fmt.Println(x[0])

空切片 []int{} 是可用的,append 会自动扩容。 程序合法,运行输出:

10

所以 C 能正常编译运行


D

var x map[string]int
x["key"] = 10
fmt.Println(x["key"])

var x map[string]int 声明但没有初始化, 默认值是 nil map。 对 nil map 进行写操作会运行时 panic

panic: assignment to entry in nil map

但它能编译,只是运行时报错。

所以 D 无法正常运行(运行时 panic)。