# Golang 基础题 ## 1、关于类型转化,下面语法正确的是() **A**: ```go type MyInt int var j int = 1 var i MyInt = i ``` **B**: ```go type MyInt int var j int = 1 var i MyInt = (MyInt)i ``` **C**: ```go type MyInt int var j int = 1 var i MyInt = MyInt(i) ``` **C**: ```go 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,这一说法是否正确。 ```go 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 中用于处理请求范围内的值、取消信号和超时的标准库。分析以下代码,它的输出最可能是什么? ```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()` 的返回值是多少? ```go 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、如下代码的执行结果为? ```go 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、如下代码的输出是什么? ```go 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`语句的执行顺序及对返回值的影响,正确的是: ```go 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、如下代码的打印内容是什么? ```go 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、对于以下代码,描述正确的是: ```go 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 ```go func main() { var x [2]int x[2] = 10 fmt.Println(x[2]) } ``` B ```go func main() { x := 42 x, y := 10, "go" fmt.Println(x, y) } ``` C ```go func main() { x := []int{} x = append(x, 10) fmt.Println(x[0]) } ``` D ```go func main() { var x map[string]int x["key"] = 10 fmt.Println(x["key"]) } ``` ## 正确答案: AD **A** ```go var x [2]int x[2] = 10 fmt.Println(x[2]) ``` 数组 `x` 的长度是 2,可访问的下标是 `0` 和 `1`。 `x[2]` 越界,会在**编译期**直接报错: ``` invalid array index 2 (out of bounds for 2-element array) ``` 所以 **A 无法编译通过**。 --- **B** ```go x := 42 x, y := 10, "go" fmt.Println(x, y) ``` 短变量声明 `:=` 的规则是: 只要左侧有至少一个 **新的变量**,就允许重复声明已有变量。 这里 `x` 已存在,`y` 为新变量,所以合法。 运行输出: ``` 10 go ``` 所以 **B 能正常编译运行**。 --- **C** ```go x := []int{} x = append(x, 10) fmt.Println(x[0]) ``` 空切片 `[]int{}` 是可用的,`append` 会自动扩容。 程序合法,运行输出: ``` 10 ``` 所以 **C 能正常编译运行**。 --- **D** ```go 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)。