go array和slice的区别 golang面试题 002

本文详细探讨了Go语言中数组和切片的相同点与不同点。数组长度固定,是值类型,而切片长度可变,属于引用类型。切片通过指针、长度和容量构成,具有灵活的扩容策略。在需要扩容时,Go语言会生成新底层数组并返回指向新数组的新切片,保持原有切片不变。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

002 go array和slice的区别

相同点

都是属于集合类的类型,用来存储同一种类型的数据或者元素

不同点

数组的长度固定,数组是值类型(值类型有基本数据类型,结构体类型),数组的空余位置用0填补,不允许数组越界。
slice的值长度可变,属于引用类型(引用类型:字典类型,通道类型,函数类型)
切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。
如果传递的是引用类型,那么就是“传引用”,如果传递的是值类型,那么就是“传值”(会把以前的数据复制一遍)
数组长度在声明的时候就必须给定,并且之后不会再改变。
slice类型的字面量中只有元素类型,没有长度。切片长度可以自动随着元素数量的增长而增长,但不会随着元素数量的减少而减少。
数组的容量=长度,不可改变。

go中有没有数组

golang 中的数组

1.数组的创建
package main

import "fmt"

func main() {
	var arr1 = [...]int{1, 2, 3, 4} //[...]默认为元素的数量即为数组的长度
	fmt.Println(len(arr1))          //4
	arr1[4] = 5                     //panic 数组越界
	fmt.Println(arr1)
	var arr2 = [10]int{1, 2, 3, 4}
	fmt.Println(arr2) //[1 2 3 4 0 0 0 0 0 0]
}

2.数组是值传递
package main

import "fmt"

func main() {
	var arr = [10]int{1, 2, 5, 6, 8, 9}
	fmt.Println(arr) //[1,2,5,6,8,9,0,0,0,0]
	//验证数组是值拷贝传递
	AddOne(arr)
	fmt.Println(arr) //[1,2,5,6,8,9,0,0,0,0]
}
func AddOne(arr [10]int) {
	arr[9] = 999999
	fmt.Println(arr) //[1,2,5,6,8,9,0,0,0,999999]
}
3.数组长度固定
package main

import "fmt"

func main() {
	var arr1 = [...]int{1, 2, 3, 4} //[...]默认为元素的数量即为数组的长度
	fmt.Println(len(arr1))          //4
	arr1[4] = 5
	arr1 = append(arr1, 5)
	fmt.Println(arr1)
}

Output

# command-line-arguments
.\main.go:8:6: invalid array index 4 (out of bounds for 4-element array)
.\main.go:9:15: first argument to append must be slice; have [4]int

golang 中的切片(slice)

1.Slice 介绍

一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。通过len和cap函数分别返回slice的长度和容量。
以下是slice源码结构

type slice struct {
 array unsafe.Pointer
 len int
 cap int
}

slice是一个特殊的引用类型,但是它自身也是个结构体
属性len表示可用元素数量,读写操作不能超过这个限制,不然就会panic
属性cap表示最大扩张容量,当然这个扩张容量也不是无限的扩张,它是受到了底层数组array的长度限制,超出了底层array的长度就会panic

一旦一个slice无法容纳更多的元素,go会扩容,但不会改变原来的切片,而是会生成一个容量更大的slice,然后把原有的元素和新元素一起copy到新的slice中。一般情况下,可简单认为新slice的容量是旧slice容量的2倍。

但是当原slice的长度>= 1024,go会以原容量的1.25倍作为新容量的基准,新容量基准会不断与1.25相乘,直到结果不小于原长度与要追加的元素数量之和。最终,新容量要比新长度要大 一些。

如果一次追加的元素过多,以至于使新长度比原容量的2倍还要大,那么新容量就会以新长度为基准。

一个slice的底层数组永远不会倍替换,虽然在扩容的时候,go一定会生成新的底层数组,但同时也生成了新的 slice。只是把新slice作为了新底层数组的窗口,而没有对原slice及其底层数组做任何改动。

在无需扩容的时候,append函数返回的是指向原底层数组的新slice。而在需要扩容的时候,append函数返回的是指向新底层数组的新的slice

slice扩容策略

  • 首先判断,如果新申请容量(cap)大于二倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap的两倍),即(newcap=doublecap)
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的四分之一,即(newcap=old.cap,for{newcap+=newcap/4})知道最终容量(newcap)大于等于新申请的容量(cap),即(newcap >=cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
    确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。
    它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。
    请记住,在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,函数返回的是指向新底层数组的新切片。所以,严格来讲,“扩容”这个词用在这里虽然形象但并不合适。
1.slice的声明和创建

菜鸟教程

2.切片作为参数传递
package main

import (
	"fmt"
)

func main() {
	var slice = make([]int, 3, 5) //len=3,cap=5
	fmt.Println(slice)            //[0,0,0]
	slice2 := slice[:5]           //slice实现了对slice的扩容,切片长度变为5
	fmt.Println(slice2)           //[0,0,0,0,0]
	slice[0] = 999                //这里slice和slice的index=0位置都是999 因为他们引用的底层数组的index=0位置都是999
	fmt.Println(slice)
	fmt.Println(slice2)
	AddOne(slice)       //[8888,0,0]
	fmt.Println(slice)  //[8888,0,0]
	fmt.Println(slice2) //[8888,0,0,0]
}
func AddOne(s []int) {
	s[0] = 8888
	fmt.Println(s)
}

总结

(1)go是有数组的,只是平时用切片比较多。数组大小一旦创建就不能改变,数组长度大于元素个数的时候会用0补位,这跟其他语言是相通的。
(2)切片slice可以看作是对数组的一切操作,它是一个引用数据类型,其数据结构包括底层数组的地址,以及元素可操作长度len或可扩容长度cap。
(3)要想突破slice的扩容cap限制进行无限扩容就需要使用append()函数进行操作。如果append追加的元素后slice的总长度不超过底层数组的总长度,那么slice引用的地址不会发生改变,反之引用地址会 变成新的数组的地址。
(4)slice是一个抽象的概念,它存在的意义在于方便对一个顺序结构进行一些方便操作,例如查找,排序,追加等等,这个类似于python的list。

<think>好的,用户想了解Golang面试题,我需要整理一些常见的题目,同时确保回答结构清晰,分点说明。首先,我应该回忆一下Golang的核心知识点,比如并发、垃圾回收、接口、GoroutineChannel这些。这些通常是面试中经常被问到的话题。 接下来,我需要考虑用户可能的背景。可能是准备面试的开发者,或者刚学Golang想检验自己知识的人。因此,题目需要覆盖基础进阶内容,既包括语法特性,也有底层原理。例如,Goroutine线程的区别、Channel的使用、接口的实现机制等。 然后,要确保每个问题都有简明扼要的解答,帮助用户快速理解。例如,在解释Goroutine时,需要提到它们由Go运行时管理,相比线程更轻量。对于垃圾回收,可以介绍三色标记法的基本原理流程。 同时,用户提到使用中文回答,所以要注意术语的准确性,必要时用括号补充英文术语。还要注意LaTeX格式的正确使用,比如行内公式用$包裹,独立公式用$$。比如在提到复杂度时,可能需要用到Big O表示法,如$O(1)$。 另外,结构方面,可能需要分大类,比如基础概念、并发编程、内存管理、进阶特性等,每个类别下列举几个典型问题,这样用户看起来更清晰。每个问题后给出解答,保持段落简短,避免大段文字。 还需要检查是否有遗漏的重要主题,比如错误处理、defer关键字、context包的使用、sync包中的锁机制等。这些都是面试中常见的问题点。 最后,要确保回答真实可靠,可能需要参考官方文档或权威资料,比如Go语言规范、Effective Go等,确保信息准确。同时,避免过于深入复杂的内容,保持答案适合面试准备的层次。</think>以下是一些常见的Golang面试题及核心知识点解析,按知识模块分类整理: --- ### 一、基础概念 1. **Go语言特性** Q: Go语言有哪些核心特性? A: 并发编程(Goroutine/Channel)、垃圾回收、编译型语言、静态类型、接口非侵入式设计、丰富的标准库、跨平台编译 2. **值类型 vs 引用类型** Q: 哪些类型是值类型?哪些是引用类型? A: - 值类型:基本类型(int/float/bool等)、数组(array)、结构体(struct) - 引用类型:切片(slice)、映射(map)、通道(channel)、函数(func)、指针(*T) --- ### 二、并发编程 3. **Goroutine原理** Q: Goroutine线程有什么区别? A: - 线程由OS调度(MB级栈),Goroutine由Go运行时调度(KB级栈,可动态扩容) - 线程上下文切换成本高,Goroutine切换成本低(用户态调度) 4. **Channel机制** Q: 无缓冲有缓冲Channel的区别? A: - 无缓冲:发送接收操作必须同时就绪(同步阻塞) - 有缓冲:缓冲区未满时可发送,非空时可接收(异步操作) 5. **Sync包** Q: `sync.WaitGroup`的作用? A: 用于等待一组Goroutine完成,通过`Add()`、`Done()`、`Wait()`实现协同 --- ### 三、内存管理 6. **GC算法** Q: Go的垃圾回收采用什么算法? A: 三色标记法(Tri-color Mark & Sweep) - 阶段1: 标记阶段(Mark Termination + Marking) - 阶段2: 清理阶段(Sweeping) 7. **逃逸分析** Q: 什么是变量逃逸? A: 编译器决定变量分配在栈还是堆的过程。若变量在函数外部被引用,则会逃逸到堆上 --- ### 四、进阶特性 8. **接口实现** Q: Go接口的隐式实现机制? A: 类型不需要显式声明实现接口,只要方法签名匹配即视为实现(Duck Typing) 9. **反射原理** Q: `reflect`包如何获取类型信息? A: 通过`reflect.TypeOf()`获取类型元数据,`reflect.ValueOf()`获取运行时值 --- ### 五、典型代码题 10. **Goroutine执行顺序** ```go func main() { for i := 0; i < 5; i++ { go func() { fmt.Print(i) }() } time.Sleep(time.Second) } ``` Q: 输出可能是什么?如何修正? A: 可能输出乱序的55555。应改为传递参数: ```go go func(n int) { fmt.Print(n) }(i) ``` --- ### 六、高频考点总结 | 知识点 | 常见问题 | |----------------------|----------------------------------------| | 并发模型 | Channel死锁场景、select用法 | | 内存分配 | 内存对齐、内存泄漏排查 | | 标准库 | context包作用、json序列化优化 | | 性能调优 | pprof工具使用、GC调优参数 | 建议结合《Go语言设计与实现》等专业书籍深入理解底层机制,并通过实际项目加深对并发模式的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值