- 
摘要 该视频主要讲述了指针的本质和用法。指针是用于间接控制其他变量的变量,通过存储其他变量的内存地址实现。指针类型在定义时需要在前面加上星号,表示它是一个指针类型。指针类型本质上是声明一个与变量类型相同大小的内存空间,并将这个空间的地址赋值给指针。指针类型的大小与它所指向的变量类型的大小相同,因为每个变量在内存中都有一个唯一的地址。通过指针可以间接地控制和操作它所指向的变量,例如修改其值或调用其方法。此外,视频还提到了函数传递和值传递的本质,以及指针在函数传递中的作用。总之,该视频详细解释了指针的本质、定义、使用方法和在函数传递中的作用,对于理解指针概念非常有帮助。 
- 
分段总结 折叠 00:30指针的定义和本质 1.指针是一个变量,存储的是另一个变量的地址 2.指针变量定义时前面有一个星号 * 3.指针类型可以指向不同类型的数据,如 int、float、struct 等 03:49指针的内存表示 1.指针变量存储的是一个地址,而不是直接存储数据值 2.内存中的每个空间都有唯一的地址 3.指针变量通过存储的地址来访问目标数据 09:12指针的定义和赋值 1.指针变量的定义:var pointer_name *type 2.指针变量的赋值:pointer_name = &variable 3.指针变量可以指向不同类型的数据,如 int、float、struct 等 11:22Go语言指针的特性 1.Go语言保留了指针,但限制了指针的运算 2.Go语言中的指针变量可以直接访问其指向的数据,无需显式解引用 3.Go语言提供了unsafe包,允许进行不安全的指针运算,但需要谨慎使用 
- 
重点 本视频暂不支持提取重点 
一、指针 00:00
1. 指针的本质 00:06
1)需求讲解 00:16
- 
 
- 
结构体修改需求:在函数中修改结构体字段值并反映到原变量中,需要通过指针传递而非值传递 
- 
值传递问题:当结构体作为值传递时,函数内修改的是副本,不会影响原变量 
- 
指针解决方案:使用指针传递可以修改原结构体字段值,如func changeName(p *Person) 
2)结构体定义 01:07
- 
 
- 
基本定义:type Person struct { name string } 
- 
方法定义:func (p Person) print()中Person表示指针接收器 
- 
指针优势:当结构体较大时使用指针可避免复制开销,且能修改原结构体 
3)指针变量定义 02:40
- 
 
- 
定义语法:var pi Person = &p,其中表示指针类型,&取地址 
- 
打印指针:fmt.Printf("%p",pi)打印地址,fmt.Println(pi)打印指向的值 
- 
访问方式:通过(*pi).name或直接pi.name访问字段(Go特有语法糖) 
4)指针类型的机理 03:41
- 
 
- 
存储本质:指针变量存储的是目标变量的内存地址 
- 
访问过程:先获取地址值,再通过地址访问实际数据 
- 
复制特性:复制指针变量时复制的是地址值,多个指针可指向同一内存 
2. 指针的定义 09:18
- 
 
- 
基本语法:var po *float64定义指向float64的指针 
- 
初始化方式:可直接po := &Person{name:"bobby2"}一步完成 
- 
赋值限制:不能直接将结构体赋给指针,必须使用地址符& 
3. go语言的指针 11:22
1)通过指针访问值 12:05
- 
 
- 
简化语法:Go允许直接通过指针访问字段(po.name),无需显式解引用((*po).name) 
- 
兼容特性:既支持传统指针语法,也支持类似非指针语言的简化访问 
- 
打印优化:fmt.Println会自动打印指针指向的值而非地址 
2)go语言的阉割版指针 14:36
- 
 
- 
运算限制:Go指针不能像C语言那样进行算术运算(如p++) 
- 
安全设计:通过限制指针运算避免内存安全问题 
- 
unsafe包:特殊需求可使用unsafe包进行指针运算,但会失去安全性保障 
- 
设计理念:在灵活性和安全性之间取得平衡,日常开发推荐使用安全指针 
二、知识小结
| 知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 | 
|---|---|---|---|
| 指针的本质 | 指针是存储变量内存地址的变量,通过地址间接访问数据。修改指针指向的值会影响原变量。 | 值传递 vs 指针传递:值传递复制副本,指针传递直接修改原数据。 | ⭐⭐ | 
| 指针的定义与初始化 | 使用 var p *int 定义指针,通过 & 取地址符获取变量地址(如 &a)。 | 星号(*)的两种含义:定义时表示指针类型(*int),使用时表示解引用(*p)。 | ⭐⭐ | 
| Go指针特性 | 1. 简化访问:可直接用 p.name 代替 (*p).name2. 禁止运算:不支持指针算术(需通过 unsafe 包实现)。 | 与C/C++区别:Go指针更安全,但功能受限(如无指针运算)。 | ⭐⭐⭐ | 
| 指针的应用场景 | 结构体传参时需修改原数据(如 changeName(p *Person)),避免值传递的副本问题。 | 易错点:未取地址导致传值(changeName(p) 无效)。 | ⭐⭐ | 
| unsafe包的作用 | 提供指针运算能力,但标记为"不安全",需谨慎使用。 | 风险提示:直接操作内存可能引发不可控错误。 | ⭐⭐⭐⭐ | 
- 
摘要 该视频主要讲述了在Go语言中初始化的几种方式。对于结构体类型,会自动初始化;对于指针类型,需要使用new函数进行初始化;对于slice、map等类型,需要使用make函数进行初始化;对于channel类型,也需要使用make函数进行初始化。此外,视频还提到了在使用指针时需要注意的一些问题,如访问结构体成员时可能会出现的运行时错误,以及定义指针时需要初始化的重要性。 
- 
分段总结 折叠 00:01指针的定义和赋值 1.定义指针:通过定义一个指针变量来引用其他变量的值。 2.赋值方式:不能直接对指针进行赋值操作,需要通过指针变量来间接赋值。 3.取值方式:通过指针变量来获取其指向的值。 01:31指针的初始化 1.初始化重要性:指针在使用前必须进行初始化,以避免访问无效内存地址。 2.初始化方法:可以通过直接实例化、定义结构体指针并进行赋值、使用new方法等方式进行初始化。 3.new方法:用于创建并初始化结构体指针,返回指向新分配内存的指针。 07:22map的初始化 1.map初始化:map必须使用make方法进行初始化,不能自定义。 2.slice和person:slice和person可以不进行初始化,除非定义为指针。 
- 
重点 本视频暂不支持提取重点 
一、指针的初始化 00:00
1. 指针的定义 00:17
- 
 
- 
基本定义:通过var po *Person声明指向Person结构体的指针变量 
- 
初始化方式:使用&取地址运算符,如po := &Person{name: "bobby2"} 
2. 指针的特性
- 
运算限制:与C语言不同,Go指针不能进行算术运算(如+1操作) 
- 
安全机制:标准指针是"阉割版",如需底层操作需使用unsafe包 
- 
访问方式 :支持两种等效语法: - 
(*po).name = "bobby3" 
- 
po.name = "bobby4"(语法糖) 
 
- 
3. 结构体指针的初始化 02:02
1)未初始化问题
- 
 
- 
常见错误:访问未初始化指针会触发invalid memory address异常 
- 
默认值:未初始化的指针变量值为nil,打印显示为<nil> 
- 
对比说明:普通结构体变量会自动初始化,但指针类型不会 
2)第一种初始化方式 03:40
- 
直接实例化:ps := &Person{}创建结构体并立即取地址 
- 
特点:最直观的初始化方式,适合已知初始值的情况 
3)第二种初始化方式 03:56
- 
引用现有变量:先定义结构体变量,再取其地址 
- 
适用场景:当需要基于已有变量创建指针时使用 
4)第三种初始化方式 04:54
- 
 
- 
new函数:var pp = new(Person)自动分配内存并返回指针 
- 
底层机制 : - 
分配零值内存空间 
- 
返回该内存地址 
- 
等效于先创建后取地址的复合操作 
 
- 
4. 初始化总结 06:32
- 
关键原则:所有指针必须显式初始化后才能使用 
- 
初始化方法对比: 
- 
语言规范 : - 
指针初始化推荐使用new函数 
- 
与make的区别:make用于slice/map/channel,new用于指针 
- 
未初始化指针访问会导致nil pointer dereference错误(类似Java的NullPointerException) 
 
- 
二、知识小结
| 知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 | 
|---|---|---|---|
| 指针定义与赋值 | 通过int p = &a方式定义指针并赋值,不能直接对未初始化的指针赋值(如p = 5非法) | 指针必须指向有效内存地址才能操作 | ⭐⭐⭐ | 
| 指针初始化 | 三种方式:1. 直接实例化(&a)2. 从已存在变量取地址(p = &person{})3. new函数(p = new(person)) | 未初始化的指针访问会触发nil pointer错误(类似NullPointerException) | ⭐⭐⭐⭐ | 
| 结构体与指针差异 | 结构体变量自动初始化(默认值),指针需显式初始化 | 直接访问结构体字段安全,指针需确保非nil | ⭐⭐ | 
| new函数作用 | 分配内存并返回地址,推荐用于指针初始化 | 与make区别:new返回指针,make用于slice/map/channel | ⭐⭐⭐ | 
| 强制初始化类型 | map必须用make初始化,slice可延迟初始化,结构体指针需显式处理 | 未初始化的map操作会引发panic | ⭐⭐⭐⭐ | 
- 
摘要 该视频主要讲述了如何通过指针交换两个值。视频中强调了规范命名的重要性,以提高代码的可读性。对于包含两个单词的名称,接收者命名一般遵循两个单词首字母全部小写的规范,例如pn。此外,视频还介绍了如何使用指针交换两个值。在示例中,传入的两个参数a和b分别等于1和2,通过指针交换它们的值。然而,输出结果中a仍然等于1,这表明交换操作没有成功。这是因为指针的使用不当导致的。要成功交换两个值,需要正确使用指针。 
- 
分段总结 折叠 00:01结构体与方法接收者命名规范 1.结构体与方法接收者命名规范:接收者命名一般为p加上结构体名称,如person命名为p。 2.命名规范:使用首字母小写命名,如person命名为p。 3.随意命名:虽然可以随意命名,但遵循规范更佳。 01:45通过指针交换两个值 1.无指针情况:通过函数返回值交换两个值。 2.有指针情况:通过指针直接交换两个值,无需返回值。 3.示例代码:通过指针交换两个变量的值。 04:48指针传递与值传递 1.函数调用本质:值传递,包括指针传递。 2.指针传递:传递的是地址,可以修改底层数据。 3.值传递:传递的是值的副本,不能修改底层数据。 09:13正确交换两个变量的值 1.错误示例:直接交换两个篮子中的值,不影响外部变量。 2.正确方法:使用临时变量,交换临时变量和外部变量的值。 3.示例代码:通过临时变量正确交换两个变量的值。 
- 
重点 本视频暂不支持提取重点 
一、通过swap交换指针的值 00:02
1. 接收者命名规范 00:22
- 
 
- 
单单词命名:当接收者为单单词结构体(如Person)时,使用首字母小写(如p)作为接收者名称 
- 
多单词命名:当结构体名由多个单词组成(如PersonName)时,使用各单词首字母小写组合(如pn)作为接收者名称 
- 
规范本质:命名规范没有特殊原因,主要是为了保持代码风格统一,便于团队协作开发 
2. 通过指针交换两个值的方法 01:45
1)无指针的情况 02:03
- 
 
- 
实现方式:需要定义返回值的函数,通过返回值重新赋值给原变量 
- 
示例代码: 
- 
缺点:需要额外的返回值处理,代码不够简洁 
2)有指针的情况 02:22
- 
 
- 
直接交换误区: 
- 
正确实现: 
- 
调用示例: 
3)原理分析 04:48
- 
 
- 
内存模型 : - 
每个变量都有自己的内存地址(如a=0x001指向值1,b=0x002指向值2) 
- 
函数参数传递时创建新的指针变量(值传递) 
 
- 
- 
错误原因:直接交换指针变量只是交换了函数内部局部变量的指向,不影响外部变量 
- 
正确逻辑:需要通过指针解引用(*操作符)修改指针指向的实际值 
4)值传递分析 09:08
- 
 
- 
本质特性:Go语言中所有参数传递都是值传递,包括指针传递 
- 
指针传递本质:传递的是指针值的副本(内存地址的拷贝) 
- 
关键区别 : - 
修改指针本身(改变指向):只影响函数内部 
- 
修改指针指向的值:会影响外部变量 
 
- 
- 
初始化注意 : - 
指针必须初始化(使用new或取地址&) 
- 
未初始化指针会导致nil pointer错误 
 
- 
二、知识小结
| 知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 | 
|---|---|---|---|
| 结构体方法接收者 | 接收者可以是值类型或指针类型,指针类型能修改内部值且避免复制 | 指针接收者与值接收者的本质区别 | ⭐⭐ | 
| 命名规范 | 单单词接收者首字母小写(p),多单词首字母全小写(pn) | 规范虽非强制但建议统一遵循 | ⭐ | 
| 指针交换值案例 | 错误示范:仅交换指针地址;正确做法:通过*操作符修改指针指向的值 | 值传递本质导致地址交换无效,必须操作指针指向的内存 | ⭐⭐⭐ | 
| 指针底层原理 | 函数调用时参数是值传递,修改指针地址不影响原变量,需通过*操作符修改目标值 | 临时变量在值交换中的必要性 | ⭐⭐⭐⭐ | 
- 
摘要 该视频主要讲述了在Go语言中,不同类型的默认值以及在结构体中的使用,以及slice和map的特性和使用注意事项。首先介绍了不同类型的默认值,包括布尔类型、字符串类型、切片类型等。接着讲述了自定义的结构体默认值是其字段的默认值,而不是整个结构体的默认值。此外还提到了在判断结构体中的属性是否相等时,需要每一个属性都相等才最终相等。对于slice和map的使用注意事项,强调了slice底层对应一个数组,其值由指针指向数组中的元素,而map分为near map和mmap两种类型,操作上有所不同。在进行map赋值时,需要进行mac初始化,以避免潜在的问题。 
- 
分段总结 折叠 00:01nil在Go中的含义 1.nil在Go语言中代表某些数据类型的零值。 2.不同数据类型的零值不同,如布尔型的零值为false,数值型的零值为0,字符串的零值为空字符串。 3.指针和slice的默认值也是nil。 4.struct的默认值是其每个字段的默认值。 04:39nil在判断中的应用 1.判断一个值是否为nil,可以直接使用等号。 2.判断error是否为nil的常用手段。 3.slice的默认值问题,未初始化的slice与使用make初始化的slice的区别。 4.map的默认值也是nil,但nil map和非nil map在赋值操作上有区别。 
- 
重点 本视频暂不支持提取重点 
一、nil 00:09
1. 数据零值 00:56
- 
 
- 
布尔类型: 默认值为false 
- 
数值类型: 包括float和int等,默认值为0 
- 
字符串类型: 默认值为空字符串"" 
- 
指针类型: 默认值为nil 
- 
slice类型: 默认值为nil 
- 
map类型: 默认值为nil 
- 
channel类型: 默认值为nil 
- 
interface类型: 默认值为nil 
- 
function类型: 默认值为nil 
2. struct默认值 02:15
- 
 
- 
非nil特性: struct的默认值不是nil,而是其字段的默认值组合 
- 
字段继承: 每个字段继承各自类型的零值 
- 
判等特性: 可以直接用==比较两个struct实例 
- 
示例说明 : - 
name默认为空字符串 
- 
age默认为0 
- 
f默认为nil指针 
 
- 
3. error判断 04:46
- 
本质: error是interface类型,默认值为nil 
- 
判断方法: 直接使用== nil判断是否有错误 
- 
常见用法: 
4. slice默认值 05:11
- 
 
- 
nil slice : - 
通过var声明:var ps []Person 
- 
底层结构体各字段为零值 
- 
ptr为nil,len为0,cap为0 
- 
可以与nil直接比较 
 
- 
- 
empty slice : - 
通过make创建:make([]Person, 0) 
- 
ptr指向空数组,len为0,cap为0 
- 
不与nil相等 
 
- 
- 
底层结构: 
5. map默认值 10:26
- 
 
- 
nil map : - 
通过var声明:var m map[string]string 
- 
可以进行range操作(无效果) 
- 
可以取值(返回零值) 
- 
不能赋值(会panic) 
 
- 
- 
empty map : - 
通过make创建:make(map[string]string, 0) 
- 
可以进行所有map操作 
 
- 
- 
安全建议 : - 
通常使用make初始化map 
- 
只有在需要区分nil和empty的特殊场景才使用var声明 
 
- 
二、知识小结
| 知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 | 
|---|---|---|---|
| nil关键字 | Go语言中代表数据类型的零值,不同数据类型零值不同 | 布尔类型零值为false,数值类型为零,字符串为空串,指针/slice/map/channel/interface/function为零值nil | ⭐⭐⭐⭐ | 
| struct零值 | 自定义struct的零值是各字段的零值组合,不是nil | struct可以直接用==判等,需所有字段相等才返回true | ⭐⭐⭐ | 
| slice的nil判断 | 未初始化的slice为nil,make创建的slice即使长度为0也不等于nil | nil slice和empty slice底层结构不同,判等结果不同 | ⭐⭐⭐⭐⭐ | 
| map的nil特性 | nil map可以安全进行读取操作,但写入会panic | nil map和empty map在range遍历时表现相同,但赋值操作不同 | ⭐⭐⭐⭐ | 
| 指针与interface | 指针和interface类型的零值为nil | error本质是interface,常用err == nil判断错误 | ⭐⭐⭐ | 
| 零值设计哲学 | Go通过零值机制确保变量总有默认值,减少未初始化错误 | 需特别注意slice和map在nil状态下的操作差异 | ⭐⭐⭐⭐ | 
以下是关于 Go 指针的 20 道八股文题(难度递增)和 25 道场景题(涵盖基础到高级应用),系统覆盖指针的核心概念、底层原理及实践技巧。
一、八股文题(20 题)
基础篇(1-8 题)
- 
问题 :Go 中指针的定义格式是什么?指针变量存储的是什么? 答案 : 定义格式: var 指针变量名 *类型(如var p *int)。 指针变量存储的是另一个变量的内存地址。
- 
问题 :如何获取一个变量的地址?如何通过指针访问目标变量的值? 答案: - 
获取地址:使用 &运算符(如p := &x,p是x的地址)。
- 
访问值:使用 *运算符(如*p表示p指向的变量值)。
 
- 
- 
问题 :指针的零值是什么?对 nil指针解引用会发生什么? 答案:- 
指针的零值是 nil(表示不指向任何变量)。
- 
对 nil指针解引用(*nilPtr)会触发panic: runtime error: invalid memory address or nil pointer dereference。
 
- 
- 
问题 : *运算符在指针定义和指针解引用时有什么区别? 答案:- 
定义时(如 *int):表示 "指向 int 类型的指针",*是类型的一部分。
- 
解引用时(如 *p):表示 "获取指针指向的变量值",*是运算符。
 
- 
- 
问题 :值类型变量和指针类型变量作为函数参数的根本区别是什么? 答案: - 
值类型参数:函数接收变量副本,修改副本不影响原变量。 
- 
指针类型参数:函数接收指针副本(仍指向原变量地址),修改 *指针会影响原变量。
 
- 
- 
问题 :数组指针和指针数组的区别是什么?分别如何定义? 答案: - 
数组指针:指向数组的指针( *[n]T),如var p *[3]int。
- 
指针数组:元素为指针的数组( [n]*T),如var arr [3]*int。 核心区别:数组指针是单个指针,指针数组是多个指针的集合。
 
- 
- 
问题 :Go 支持指针运算吗?例如 p++或p+1这样的操作是否合法? 答案 : 不支持。Go 刻意简化了指针功能,禁止指针运算(如增减、比较地址大小),仅允许通过&取地址和*解引用。
- 
问题 :结构体指针如何访问结构体的字段?是否需要先解引用? 答案 : 结构体指针访问字段时可直接使用 .运算符(无需显式解引用),如p.Name等价于(*p).Name(编译器自动优化)。
中级篇(9-15 题)
- 
问题 :什么是 "指针的指针"(双重指针)?在什么场景下会用到? 答案 : 指针的指针是指向指针变量的指针(如 **int),存储的是指针变量的地址。 场景:函数中修改外部指针变量本身(如为指针分配内存),例如:go 运行 func initPtr(p **int) { *p = new(int) // 修改外部指针变量,使其指向新内存 ** p = 10 }
- 
问题 : new(T)和&T{}都能创建指针,它们的区别是什么? 答案:- 
new(T):分配 T 类型的零值内存,返回*T(仅初始化零值)。
- 
&T{}:通过结构体字面量初始化,可指定字段值,返回*T(适用于结构体)。 对于非结构体类型(如int),new(int)与&int{}等价。
 
- 
- 
问题 :指针作为函数返回值时,需要注意什么?为什么不能返回函数内局部变量的指针? 答案: - 
注意:返回的指针指向的变量生命周期必须长于函数调用(否则可能指向无效内存)。 
- 
可以返回局部变量的指针:Go 的逃逸分析会将被外部引用的局部变量分配到堆上,避免悬垂指针。 
 
- 
- 
问题 :接口类型变量存储指针类型时,接口的动态类型和动态值分别是什么? 答案: - 
动态类型:指针指向的类型(如 *int)。
- 
动态值:指针本身(存储的地址)。 例如: var i int; var iface interface{} = &i中,动态类型是*int,动态值是&i。
 
- 
- 
问题 :切片的底层结构包含指针,传递切片时是否需要传递切片指针?为什么? 答案 : 通常不需要。切片本身包含指向底层数组的指针,值传递切片时,副本的指针仍指向原数组,修改元素会影响原切片。仅当需要修改切片本身(如 len、cap或重新分配底层数组)时,才需传递切片指针。
- 
问题 : map是引用类型,传递map指针和直接传递map有区别吗? 答案 : 区别不大。map本身是指针包装,传递map和map指针都是传递地址(前者是*hmap,后者是**hmap)。修改map内容时两者效果相同,但修改map指针(如重新分配*m = make(map...))时,只有传递指针才能影响外部。
- 
问题 :指针类型如何实现接口?与值类型实现接口有何区别? 答案: - 
指针类型实现接口:需为指针类型定义接口方法( func (p *T) Method()),仅指针实例可赋值给接口变量。
- 
值类型实现接口:需为值类型定义接口方法( func (t T) Method()),值实例和指针实例均可赋值给接口变量(指针会自动解引用)。
 
- 
高级篇(16-20 题)
- 
问题 :什么是 "悬垂指针"(dangling pointer)?Go 如何避免悬垂指针? 答案 : 悬垂指针是指向已释放内存的指针。 Go 通过垃圾回收(GC) 和逃逸分析避免: - 
逃逸分析将被指针引用的局部变量分配到堆上,延长生命周期。 
- 
GC 自动回收不再被引用的内存,避免指针指向无效内存。 
 
- 
- 
问题 :指针的内存布局是什么?32 位和 64 位系统中指针占用多少字节? 答案: 指针存储的是内存地址,布局为一个纯地址值。 - 
32 位系统:指针占用 4 字节(可寻址 2³² 字节内存)。 
- 
64 位系统:指针占用 8 字节(可寻址 2⁶⁴字节内存)。 
 
- 
- 
问题 : unsafe.Pointer的作用是什么?使用时需要注意哪些风险? 答案:- 
作用: unsafe.Pointer是 "通用指针",可转换为任意类型指针,用于绕过 Go 的类型安全检查(如*int与*float64互转)。
- 
风险:破坏类型安全,可能导致内存访问错误;依赖具体内存布局,可移植性差;可能被 GC 错误处理。 
 
- 
- 
问题 :如何通过指针实现两个变量的值交换?与值传递方式相比有何优势? 答案: 指针实现: go 运行 func swap(a, b *int) { *a, *b = *b, *a }优势:无需复制变量值(尤其适合大结构体),直接操作原变量,性能更优。 
- 
问题 :Go 的指针与 C 的指针最主要的区别是什么? 答案: - 
安全性:Go 禁止指针运算,无悬垂指针(GC 管理);C 允许指针运算,需手动管理内存,易产生悬垂指针。 
- 
功能:Go 指针仅支持取地址( &)和解引用(*);C 指针支持算术运算、转换为整数等。
- 
类型严格性:Go 指针类型严格( *int与*float64不可隐式转换);C 指针转换更灵活(需显式类型转换)。
 
- 
二、场景题(25 题)
基础应用(1-8 题)
- 
场景 :编写函数,通过指针交换两个整数的值。 答案: go func swap(a, b *int) { *a, *b = *b, *a }  // 使用: // x, y := 10, 20 // swap(&x, &y) // x=20, y=10
- 
场景 :定义一个结构体 Person,通过结构体指针修改其字段值,并比较直接修改与指针修改的区别。 答案:go type Person struct { Name string }  // 直接修改(值传递,不影响原结构体) func modifyValue(p Person, newName string) { p.Name = newName }  // 指针修改(影响原结构体) func modifyPointer(p *Person, newName string) { p.Name = newName }  // 使用: // p := Person{Name: "Alice"} // modifyValue(p, "Bob") // p.Name仍为"Alice" // modifyPointer(&p, "Bob") // p.Name变为"Bob"
- 
场景 :创建一个指针数组,存储多个整数的地址,遍历数组并修改每个整数的值。 答案: go func main() { a, b, c := 1, 2, 3 // 指针数组:元素是*int ptrs := []*int{&a, &b, &c}  // 遍历并修改值 for _, p := range ptrs { *p *= 2 // 每个整数翻倍 }  // 输出:2 4 6 fmt.Println(a, b, c) }
- 
场景 :编写函数,接收 *int类型参数,若指针为nil则初始化它,否则将其值加 1。 答案:go func initOrIncrement(p *int) { if p == nil { return // 无法修改nil指针(需双重指针) } if *p == 0 { // 假设0为未初始化 *p = 1 } else { *p++ } }  // 使用: // var x int // initOrIncrement(&x) // x=1 // initOrIncrement(&x) // x=2
- 
场景 :通过指针实现一个函数,将字符串反转(原地修改,不创建新字符串)。 答案: go // 字符串不可变,需转换为[]byte func reverseString(s *[]byte) { for i, j := 0, len(*s)-1; i < j; i, j = i+1, j-1 { (*s)[i], (*s)[j] = (*s)[j], (*s)[i] } }  // 使用: // s := []byte("hello") // reverseString(&s) // fmt.Println(string(s)) // "olleh"
- 
场景 :创建一个数组指针,通过指针修改数组中的元素,并比较数组指针与指针数组的使用差异。 答案: go func main() { // 数组指针:指向数组的指针 arr := [3]int{1, 2, 3} pArr := &arr pArr[0] = 10 // 等价于(*pArr)[0] = 10  // 指针数组:元素为指针 a, b, c := 1, 2, 3 arrPtrs := [3]*int{&a, &b, &c} *arrPtrs[0] = 10  fmt.Println(arr) // [10 2 3] fmt.Println(a, b, c) // 10 2 3 }
- 
场景 :编写函数,接收指针类型参数,判断指针是否为 nil,并处理nil情况避免 panic。 答案:go func safeDereference(p *int) int { if p == nil { return 0 // 处理nil指针,返回默认值 } return *p }  // 使用: // var x *int // fmt.Println(safeDereference(x)) // 0(无panic) // y := 5 // fmt.Println(safeDereference(&y)) // 5
- 
场景 :通过指针传递大结构体,比较其与值传递的性能差异(使用 time包)。 答案:go import "time"  // 大结构体 type BigStruct struct { Data [1024 * 1024]int // 4MB(每个int4字节) }  // 值传递 func passByValue(b BigStruct) {}  // 指针传递 func passByPointer(b *BigStruct) {}  func main() { b := BigStruct{}  // 测试值传递耗时 start := time.Now() for i := 0; i < 100; i++ { passByValue(b) } fmt.Println("值传递耗时:", time.Since(start))  // 测试指针传递耗时 start = time.Now() for i := 0; i < 100; i++ { passByPointer(&b) } fmt.Println("指针传递耗时:", time.Since(start)) // 明显快于值传递 }
中级应用(9-18 题)
- 
场景 :使用双重指针( **int)在函数内部为外部指针分配内存并赋值。 答案:go func allocate(p **int, value int) { *p = new(int) // 为外部指针分配内存 **p = value // 赋值 }  // 使用: // var x *int // allocate(&x, 10) // fmt.Println(*x) // 10(x已被正确初始化)
- 
场景 :实现一个函数,接收切片指针,向切片中添加元素并修改切片的长度(对比直接传递切片的区别)。 答案: go // 直接传递切片:修改len不影响外部 func appendValue(s []int, val int) { s = append(s, val) }  // 传递切片指针:修改影响外部 func appendPointer(s *[]int, val int) { *s = append(*s, val) }  // 使用: // s := []int{1,2} // appendValue(s, 3) // s仍为[1,2] // appendPointer(&s, 3) // s变为[1,2,3]
- 
场景 :定义接口 Logger,分别用结构体和结构体指针实现该接口,观察赋值差异。 答案:go type Logger interface { Log(msg string) }  type ConsoleLogger struct{}  // 值类型实现接口 func (c ConsoleLogger) Log(msg string) { fmt.Println("Log:", msg) }  type FileLogger struct{}  // 指针类型实现接口 func (f *FileLogger) Log(msg string) { fmt.Println("FileLog:", msg) }  func main() { var l Logger  // 结构体实现:值和指针均可赋值 c := ConsoleLogger{} l = c // 合法 l = &c // 合法  // 指针实现:仅指针可赋值 f := FileLogger{} // l = f // 编译错误(值类型未实现接口) l = &f // 合法 }
- 
场景 :通过指针实现链表的插入操作(在指定节点后插入新节点)。 答案: go type ListNode struct { Val int Next *ListNode }  // 在node后插入新节点 func insertAfter(node *ListNode, val int) { if node == nil { return } newNode := &ListNode{Val: val} newNode.Next = node.Next node.Next = newNode }  // 使用: // head := &ListNode{Val: 1} // insertAfter(head, 2) // head.Next.Val=2
- 
场景 :编写函数,接收 map指针,在函数内重新初始化map(对比直接传递map的区别)。 答案:go // 直接传递map:重新初始化不影响外部 func resetMap(m map[int]string) { m = make(map[int]string) }  // 传递map指针:重新初始化影响外部 func resetMapPointer(m *map[int]string) { *m = make(map[int]string) }  // 使用: // m := map[int]string{1: "a"} // resetMap(m) // m仍为{1: "a"} // resetMapPointer(&m) // m变为空map
- 
场景 :使用指针实现结构体的深拷贝(对比浅拷贝)。 答案: go type Data struct { Num int List []int // 引用类型字段 }  // 浅拷贝:仅复制指针,共享底层切片 func shallowCopy(d Data) Data { return d }  // 深拷贝:通过指针手动复制引用类型 func deepCopy(d *Data) *Data { newList := make([]int, len(d.List)) copy(newList, d.List) return &Data{ Num: d.Num, List: newList, } }  // 使用: // d1 := Data{Num: 1, List: []int{1,2}} // d2 := shallowCopy(d1) // d2.List[0] = 100 // d1.List[0]也变为100  // d3 := deepCopy(&d1) // d3.List[0] = 200 // d1.List不受影响
- 
场景 :通过指针传递实现并发安全的计数器(使用 sync.Mutex)。 答案:go import "sync"  type Counter struct { mu sync.Mutex value int }  // 指针接收者确保锁和值被正确共享 func (c *Counter) Incr() { c.mu.Lock() defer c.mu.Unlock() c.value++ }  func (c *Counter) Get() int { c.mu.Lock() defer c.mu.Unlock() return c.value }  // 使用: // c := &Counter{} // for i := 0; i < 1000; i++ { // go c.Incr() // } // time.Sleep(time.Second) // fmt.Println(c.Get()) // 1000(并发安全)
- 
场景 :使用 unsafe.Pointer将int指针转换为float64指针,修改其值(演示类型转换)。 答案:go import "unsafe"  func main() { var x int = 0x4048000000000000 // float64(3.5)的二进制表示 pInt := &x  // 转换为unsafe.Pointer,再转换为*float64 pFloat := (*float64)(unsafe.Pointer(pInt)) fmt.Println(*pFloat) // 输出3.5  *pFloat = 2.5 fmt.Println(x) // 输出0x4000000000000000(float64(2.5)的整数表示) }
- 
场景 :编写函数,通过指针判断两个变量是否存储在同一块内存地址。 答案: go func isSameAddress(a, b *int) bool { return a == b // 比较指针地址 }  // 使用: // x := 5 // y := 5 // fmt.Println(isSameAddress(&x, &y)) // false(不同地址) // z := &x // fmt.Println(isSameAddress(&x, z)) // true(相同地址)
- 
场景 :实现一个函数,接收指针类型的参数,返回该指针指向的值的副本(适用于任意类型)。 答案: go import "reflect"  // 使用反射实现通用副本函数 func copyValue(p interface{}) interface{} { val := reflect.ValueOf(p).Elem() // 解引用指针 copyVal := reflect.New(val.Type()).Elem() copyVal.Set(val) return copyVal.Interface() }  // 使用: // x := 10 // y := copyValue(&x).(int) // y是x的副本 // s := "hello" // t := copyValue(&s).(string) // t是s的副本
高级应用(19-25 题)
- 
场景 :使用指针实现二叉树的前序遍历(递归和非递归方式)。 答案: go type TreeNode struct { Val int Left *TreeNode Right *TreeNode }  // 递归前序遍历 func preorderRecursive(root *TreeNode, result *[]int) { if root == nil { return } *result = append(*result, root.Val) preorderRecursive(root.Left, result) preorderRecursive(root.Right, result) }  // 非递归前序遍历 func preorderIterative(root *TreeNode) []int { var result []int if root == nil { return result } stack := []*TreeNode{root} for len(stack) > 0 { node := stack[len(stack)-1] stack = stack[:len(stack)-1] result = append(result, node.Val) // 右子树先入栈(栈是LIFO) if node.Right != nil { stack = append(stack, node.Right) } if node.Left != nil { stack = append(stack, node.Left) } } return result }
- 
场景 :通过指针实现一个简单的内存池(复用对象,减少 GC 压力)。 答案: go import "sync"  type Object struct { Data int // 其他字段... }  type Pool struct { mu sync.Mutex items []*Object // 存储可用对象指针 }  func NewPool() *Pool { return &Pool{ items: make([]*Object, 0), } }  // 获取对象(从池或新建) func (p *Pool) Get() *Object { p.mu.Lock() defer p.mu.Unlock() if len(p.items) > 0 { // 从池尾取对象 obj := p.items[len(p.items)-1] p.items = p.items[:len(p.items)-1] return obj } // 新建对象 return &Object{} }  // 归还对象(重置并放入池) func (p *Pool) Put(obj *Object) { p.mu.Lock() defer p.mu.Unlock() // 重置对象状态 obj.Data = 0 p.items = append(p.items, obj) }
- 
场景 :使用指针和接口实现策略模式(根据不同策略动态切换算法)。 答案: go // 策略接口 type PaymentStrategy interface { Pay(amount float64) string }  // 具体策略:信用卡支付 type CreditCard struct { CardNo string }  func (c *CreditCard) Pay(amount float64) string { return fmt.Sprintf("用信用卡%s支付%.2f元", c.CardNo, amount) }  // 具体策略:支付宝支付 type Alipay struct { UserID string }  func (a *Alipay) Pay(amount float64) string { return fmt.Sprintf("支付宝用户%s支付%.2f元", a.UserID, amount) }  // 上下文:订单 type Order struct { strategy PaymentStrategy // 策略指针 }  func (o *Order) SetStrategy(s PaymentStrategy) { o.strategy = s }  func (o *Order) Pay(amount float64) string { return o.strategy.Pay(amount) }  // 使用: // order := &Order{} // order.SetStrategy(&CreditCard{CardNo: "1234"}) // fmt.Println(order.Pay(100)) // 信用卡支付 // order.SetStrategy(&Alipay{UserID: "user1"}) // fmt.Println(order.Pay(200)) // 支付宝支付
- 
场景 :通过指针操作二进制数据(使用 encoding/binary包解析字节流)。 答案:go import ( "encoding/binary" "fmt" )  // 解析字节流为结构体(小端序) func parseBytes(data []byte) (*struct { ID uint16 Len uint32 Flag bool }, error) { if len(data) < 2+4+1 { // ID(2) + Len(4) + Flag(1) return nil, fmt.Errorf("数据长度不足") }  result := &struct { ID uint16 Len uint32 Flag bool }{}  // 通过指针解析 result.ID = binary.LittleEndian.Uint16(data[0:2]) result.Len = binary.LittleEndian.Uint32(data[2:6]) result.Flag = data[6] != 0  return result, nil }
- 
场景 :实现一个泛型函数,接收任意类型的指针,将其值设置为该类型的零值。 答案: go import "reflect"  func setZero[T any](p *T) { // 通过反射获取零值并设置 zero := reflect.Zero(reflect.TypeOf(*p)).Interface().(T) *p = zero }  // 使用: // x := 5 // setZero(&x) // x=0 // s := "hello" // setZero(&s) // s="" // m := map[int]int{1: 2} // setZero(&m) // m=nil
- 
场景 :使用指针实现一个单例模式(确保全局只有一个实例)。 答案: go import "sync"  type Singleton struct { // 单例字段 }  var ( instance *Singleton once sync.Once // 确保只初始化一次 )  // 获取单例实例 func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} // 只执行一次 }) return instance }  // 使用: // a := GetInstance() // b := GetInstance() // fmt.Println(a == b) // true(同一实例)
- 
场景 :通过指针和 sync.Cond实现多个 goroutine 的同步(等待某个条件满足)。 答案:go import ( "sync" "time" )  type Queue struct { mu sync.Mutex cond *sync.Cond items []int closed bool }  func NewQueue() *Queue { q := &Queue{} q.cond = sync.NewCond(&q.mu) // 基于队列的锁创建条件变量 return q }  // 入队 func (q *Queue) Push(item int) { q.mu.Lock() defer q.mu.Unlock() if q.closed { return } q.items = append(q.items, item) q.cond.Signal() // 唤醒一个等待的goroutine }  // 出队(阻塞直到有元素或关闭) func (q *Queue) Pop() (int, bool) { q.mu.Lock() defer q.mu.Unlock()  // 等待条件:队列非空或已关闭 for len(q.items) == 0 && !q.closed { q.cond.Wait() // 释放锁并等待 }  if q.closed { return 0, false }  item := q.items[0] q.items = q.items[1:] return item, true }  // 关闭队列 func (q *Queue) Close() { q.mu.Lock() defer q.mu.Unlock() q.closed = true q.cond.Broadcast() // 唤醒所有等待的goroutine } 
总结
以上题目从基础语法到高级应用,全面覆盖了 Go 指针的核心知识点:
- 
八股文题聚焦指针的定义、特性、安全机制及与其他类型的区别。 
- 
场景题结合实际开发需求,涵盖数据结构(链表、树)、并发控制、设计模式等,体现指针在性能优化和内存管理中的关键作用。 
通过练习这些题目,可深入理解指针在 Go 中的设计哲学(安全与简洁并重)及最佳实践。