简介

这是我学习https://draven.co/golang的笔记。

1.数据结构

1.1 数组

问题:

1.请解释 Go 语言中数组的类型是如何定义的?比如 [3]int[4]int 是否属于同一类型?为什么?

2.Go 语言中初始化数组有两种常见方式:[3]int{1,2,3}[...]int{1,2,3},这两种方式有什么联系和区别?编译器会如何处理 [...]T 这种声明方式?

3.数组的"长度固定"是其重要特性,请问数组初始化后能否动态修改长度?如果尝试为一个长度为 3 的数组添加第 4 个元素,会发生什么?

4.在访问数组元素时,Go 语言会如何检查索引是否越界?比如 arr[4](arr 是长度为 3 的数组)和 arr[i](i 是值为 4 的变量)两种情况,检查时机和结果有什么不同?

5.当数组作为函数参数传递时,是值传递还是引用传递?这种传递方式会导致什么结果?如果希望在函数内部修改原数组的内容,应该如何处理?

6.数组初始化时,元素数量不同会影响内存分配位置:当元素数量≤4 和 >4 时,存储位置有什么区别?这对数组的使用有什么实际影响?

7.结合你对数组的理解,简要说明数组和切片的核心区别(从大小特性、类型定义等角度)。

我的回答&ai的纠错:

  1. 题目:请解释 Go 语言中数组的类型是如何定义的?比如 [3]int[4]int 是否属于同一类型?为什么?

    你的回答:数组以大小和数据类型定义,例如 [5] int。不属于。只有大小和其中元素属性相同才是同一类型。

    修正:Go 语言中数组的类型严格由「元素类型」和「长度」共同定义,例如 [3]int[4]int 是完全不同的类型,无法互相赋值或比较。只有当元素类型和长度均相同时,才属于同一类型。

  2. 题目:Go 语言中初始化数组有两种常见方式:[3]int{1,2,3}[...]int{1,2,3},这两种方式有什么联系和区别?编译器会如何处理 [...]T 这种声明方式?

    你的回答:第一种是手动输入长度,第二种是编译器自己算长度。

    修正:两种方式在运行时完全等价。[...]T 是编译器提供的语法糖,编译期间会通过遍历元素自动推导长度,最终转换为 [n]T 的形式(如 [...]int{1,2,3} 会被转换为 [3]int{1,2,3})。

  3. 题目:数组的 "长度固定" 是其重要特性,请问数组初始化后能否动态修改长度?如果尝试为一个长度为 3 的数组添加第 4 个元素,会发生什么?

    你的回答:不能。会报错。

    修正:不能动态修改长度。如果尝试通过索引添加第 4 个元素(如 arr[3] = 4,而数组长度为 3),会触发越界错误:若使用常量索引(如 arr[3]),编译时直接报错;若使用变量索引(如 i=3; arr[i]),运行时会 panic。

  4. 题目:在访问数组元素时,Go 语言会如何检查索引是否越界?比如 arr[4](arr 是长度为 3 的数组)和 arr[i](i 是值为 4 的变量)两种情况,检查时机和结果有什么不同?

    你的回答:如果是纯数字访问,编译的时候就会检查错误;如果是变量访问,就需要 go 的运行时来检查了。

    修正:Go 语言通过「编译期静态检查」和「运行时动态检查」判断索引越界:

    • 若使用常量索引(如 arr[4],数组长度为 3),编译时直接报错 “invalid array index (out of bounds)”;
    • 若使用变量索引(如 i=4; arr[i]),编译期无法判断,运行时会触发 panic: runtime error: index out of range
  5. 题目:当数组作为函数参数传递时,是值传递还是引用传递?这种传递方式会导致什么结果?如果希望在函数内部修改原数组的内容,应该如何处理?

    你的回答:值传递。导致函数内部无法修改数组。应该加上 & 变成引用传递。

    修正:数组作为函数参数时是值传递(传递数组的副本),因此函数内部修改的是副本,不会影响原数组。若要修改原数组,应传递数组的指针(如 func f(arr *[3]int)),通过指针间接操作原数组(Go 语言中没有 “引用传递”,只有值传递,指针本身也是值)。

  6. 题目:数组初始化时,元素数量不同会影响内存分配位置:当元素数量≤4 和 >4 时,存储位置有什么区别?这对数组的使用有什么实际影响?

    你的回答:<=4,会直接放在栈里面;>4 会放在静态区,在运行的时候再拿到栈上。

    修正:在不考虑逃逸分析的情况下,数组元素数量≤4 时,会直接在栈上初始化;元素数量 > 4 时,会先在静态区初始化,再拷贝到栈上。这一机制是编译器的优化,对开发者透明,但影响数组的内存分配效率(栈上分配更快,静态区分配需额外拷贝)。

  7. 题目:结合你对数组的理解,简要说明数组和切片的核心区别(从大小特性、类型定义等角度)。

    你的回答:尚未学习到切片。

    修正:如实说明未学习即可。若希望提前了解,可简单补充:数组与切片的核心区别是「大小是否固定」—— 数组大小在定义时确定且不可变,类型包含长度(如 [3]int);切片大小可动态变化,类型不包含长度(如 []int)。

1.2 切片

问题:

  1. 题目:Go 语言中切片的类型是如何定义的?与数组的类型定义有什么本质区别?请举例说明。
  2. 题目:切片在运行时的数据结构包含哪几个部分?各部分分别表示什么含义?
  3. 题目:Go 语言中初始化切片有哪些常见方式?请分别举例说明。其中,通过 arr[0:3](arr 是数组)创建的切片与原数组有什么关系?
  4. 题目:切片的 len()cap() 方法分别返回什么?两者之间有什么约束关系(比如谁必须大于等于谁)?
  5. 题目:使用 append 向切片追加元素时,如果切片当前容量足够(len < cap),会发生什么?如果容量不足(len == cap),又会触发什么操作?
  6. 题目:切片扩容时,新容量的计算规则是什么?请分情况说明(比如当前长度小于 1024 和大于等于 1024 时的区别)。
  7. 题目:切片扩容后,新切片与原切片的底层数组一定不同吗?为什么?这对使用切片有什么影响?
  8. 题目:使用 copy(a, b) 函数拷贝切片时,实际拷贝的元素数量由什么决定?拷贝完成后,修改切片 a 的元素会影响切片 b 吗?
  9. 题目:请从「长度特性」「内存共享」「类型定义」三个角度,总结切片与数组的核心区别。

ai的反馈:

  1. 题目:Go 语言中切片的类型是如何定义的?与数组的类型定义有什么本质区别?请举例说明。

    你的回答:[] int {} 类似于这样定义的,[]+ 数据类型。数组需要在 [] 中填写大小或者...,而切片啥都不需要填写。例如 [] int {1,2,3} 和 [3] int {1,2,3},前者属于切片,后者属于数组。

    纠正 / 答案:切片类型定义为[]T(T 为元素类型),仅由元素类型决定;数组类型定义为[n]T[...]T,由元素类型和长度共同决定(长度不同则类型不同)。例如:[]int是切片类型,[3]int[4]int是不同的数组类型,而[]int与元素类型相同的其他切片(如[]int{1,2})属于同一类型。

  2. 题目:切片在运行时的数据结构包含哪几个部分?各部分分别表示什么含义?

    你的回答:包含 len,caps,data,分别代表长度,容量和数据区域。

    纠正 / 答案:切片在运行时的数据结构包含三部分:Len(当前切片的长度,即元素个数)、Cap(当前切片的容量,即底层数组的大小)、Data(指向底层数组的指针)。其中 “caps” 表述不准确,应为 “Cap”;“数据区域” 更准确的描述是 “指向底层数组的指针”。

  3. 题目:Go 语言中初始化切片有哪些常见方式?请分别举例说明。其中,通过 arr[0:3](arr 是数组)创建的切片与原数组有什么关系?

    你的回答:arr [0:3] [] int {} [] interface {} 这三种方式。是原数组的子数组。

    纠正 / 答案:初始化切片的三种常见方式为:

    • 通过数组 / 切片下标:arr[0:3](arr 为数组或切片);
    • 字面量:slice := []int{1, 2, 3}
    • make 函数:slice := make([]int, 3, 5)(长度 3,容量 5)。其中[]interface{}是切片类型,并非初始化方式。通过arr[0:3]创建的切片与原数组

      共享底层内存,修改切片元素会同时影响原数组。

  4. 题目:切片的 len()cap() 方法分别返回什么?两者之间有什么约束关系(比如谁必须大于等于谁)?

    你的回答:len () 返回长度,cap()返回容量。cap>=len。

    纠正 / 答案len()返回切片当前的元素个数(长度),cap()返回切片底层数组的大小(容量)。两者的约束关系是容量必须大于等于长度cap >= len),这是切片的基本特性。

  5. 题目:使用 append 向切片追加元素时,如果切片当前容量足够(len < cap),会发生什么?如果容量不足(len == cap),又会触发什么操作?

    你的回答:会正常添加元素。如果不足,会触发扩容的操作。

    纠正 / 答案:若容量足够(len < cap),会直接在底层数组的剩余空间添加元素,切片长度增加,不分配新内存;若容量不足(len == cap),会触发扩容:分配新的底层数组,将原数组元素拷贝到新数组,新切片指向新数组,同时更新长度和容量。

  6. 题目:切片扩容时,新容量的计算规则是什么?请分情况说明(比如当前长度小于 1024 和大于等于 1024 时的区别)。

    你的回答:如果当前长度小于 1024,会直接翻倍;如果大于 1024,会每次增加 25% 的长度,直到新容量大于期望容量。如果期望容量大于当前容量的两倍就会使用期望容量。

    纠正 / 答案:扩容时新容量的计算基于当前容量(非长度):

    • 若期望容量 > 当前容量的 2 倍,则新容量 = 期望容量;
    • 若当前容量 < 1024,则新容量 = 当前容量 × 2;
    • 若当前容量 ≥ 1024,则新容量每次增加当前容量的 25%,直到 ≥ 期望容量。
  7. 题目:切片扩容后,新切片与原切片的底层数组一定不同吗?为什么?这对使用切片有什么影响?

    你的回答:我不清楚。

    纠正 / 答案:不一定。若扩容时触发了新内存分配(容量不足),新切片会指向新的底层数组,与原切片数组不同;若未触发扩容(容量足够),则共享原数组。影响:若共享底层数组,修改新切片会影响原切片;若已扩容(数组不同),则两者修改互不影响。

  8. 题目:使用 copy(a, b) 函数拷贝切片时,实际拷贝的元素数量由什么决定?拷贝完成后,修改切片 a 的元素会影响切片 b 吗?

    你的回答:由 b 的长度决定。不会。

    纠正 / 答案:实际拷贝的元素数量是切片 a 和 b 长度的较小值(即min(len(a), len(b)))。拷贝完成后,修改切片a的元素不会影响切片b,因为copy会将b的元素值拷贝到a的底层内存,两者底层数组独立(除非原本就共享且未发生扩容)。

  9. 题目:请从「长度特性」「内存共享」「类型定义」三个角度,总结切片与数组的核心区别。

    你的回答:长度特性:数组长度固定,切片长度可变。内存共享:数组分配固定内存大小,切片会动态分配新内存。类型定义:数组在定义时就需要声明大小,或者写... 让编译器自己推断,而切片不需要。

    纠正 / 答案

    • 长度特性:数组长度固定(初始化后不可变);切片长度可变(可通过append动态调整)。
    • 内存共享:数组内存独立(每个数组有自己的内存);切片可能与原数组或其他切片共享底层内存(通过下标创建时),动态扩容时会分配新内存。
    • 类型定义:数组类型由元素类型和长度共同决定(如[3]int[4]int不同);切片类型仅由元素类型决定(如[]int统一为一种类型)。
最后修改:2025 年 09 月 24 日
如果觉得我的文章对你有用,请随意赞赏