学会区分变量的值、变量的地址和指针变量,理解怎样通过函数修改调用方的数据,以及测试参数 t *testing.T 中星号的含义。
按 main.go 的 1~7 节逐段手敲。本课只使用普通指针和结构体,不涉及二级指针、unsafe 或复杂内存管理。共享根目录的 go.mod,无需第三方依赖。
number := 10
var pointer *int = &number
fmt.Println(*pointer)
| 写法 | 含义 |
|---|---|
number |
一个整数变量,当前值为 10 |
&number |
取得 number 的地址 |
*int |
类型:指向 int 的指针 |
pointer |
保存地址的指针变量 |
*pointer |
表达式:访问 pointer 指向的变量,称为解引用 |
星号的位置不同,含义不同:声明中的 *int 是类型,表达式中的 *pointer 是访问目标。*pointer = 20 修改目标整数;pointer = &other 则让指针改为指向另一个整数。
可以用 fmt.Printf("地址:%p\n", pointer) 观察地址。具体地址不固定,不要背诵某次输出,也不要依赖两次运行地址相同。示例用地址比较保证默认输出稳定。
普通参数:
func changeValue(value int) {
value = 100
}
调用 changeValue(number) 只修改整数副本,不影响 number。
指针参数:
func changeByPointer(value *int) bool {
if value == nil {
return false
}
*value = 100
return true
}
调用 changeByPointer(&number) 时,把地址值复制给参数 value。value 和调用方都能找到同一个整数,因此通过 *value 可以修改 number。
Go 仍然是按值传递,不是出现指针后就变成另一种参数传递规则。 复制的是地址值,而不是目标整数。
另外,在函数内写 value = &local 只改变参数指针的副本,不会改变调用方指针本身。示例中的 redirect 专门演示这个区别。本课不需要掌握怎样修改调用方指针的指向。
var empty *int
指针零值是 nil,表示没有指向有效变量。可以判断 empty == nil,但不能执行 *empty,否则会发生运行时 panic。
指针参数是否允许 nil,取决于函数约定。本课函数主动检查,遇到 nil 返回 false;不能因此假设所有库函数都接受 nil。
var empty *int 不会自动创建一个目标整数。与之不同:
created := new(int)
会创建一个零值 int 并返回它的指针,created != nil,*created == 0。通常 number := 0; pointer := &number 也能满足类似需求,不必强制使用 new。
func birthday(student *Student) bool {
if student == nil {
return false
}
student.Age++
return true
}
调用 birthday(&student),修改的是原学生的年龄。
Go 允许直接写 student.Age,这里等价于 (*student).Age。Go 没有 C 中的 -> 运算符;nil 结构体指针也不能直接访问字段。
func TestAdd(t *testing.T) {
// t.Fatalf(...) 等操作由 testing 包提供。
}
现在可以拆开理解:
*testing.T:指向这个类型的指针。t.Fatalf 不是结构体里的普通数据字段,而是 testing.T 提供的方法,下一课学习“方法”时再深入理解。本课不需要手动创建 testing.T。
& 取地址,用 * 声明指针类型或解引用。p++、p + 1 指针算术。& 或 new 单独决定。在仓库根目录执行:
New-Item -ItemType Directory -Force bin/09_pointers | Out-Null
go build -o ./bin/09_pointers/pointers.exe ./lessons/09_pointers
.\bin\09_pointers\pointers.exe
修改源码后需要重新编译。快速编译并运行:
go run ./lessons/09_pointers
或者从根目录进入本课:
Set-Location lessons/09_pointers
go run .
=== 取地址与解引用 ===
number 的值:10
指针类型:*int,是否指向 number:true
通过 *pointer 读取:10
通过指针修改后,number:20
=== 函数修改原变量 ===
普通参数调用后:20
指针参数调用后:100,成功:true
=== 指针复制与重新指向 ===
通过 alias 修改后,number:30
函数内重新指向后,原指针仍指向 number:true
=== nil 指针 ===
空指针为 nil:true
传入 nil,修改成功:false
=== 结构体指针 ===
小朱 过生日后,年龄:21
=== new 创建指针 ===
指针为 nil:false,初始值:0
修改后的值:42
%p 打印 &number 和 pointer,确认它们相等。*pointer = 50 修改了谁。reset(value *int) bool,检查 nil 后把目标整数设为 0。var p *int 与 p := new(int) 的 nil 状态和是否能解引用。t *testing.T,区分参数名、包名和类型。如果试验 nil 解引用,一次只保留这一处故意错误,观察 panic 后恢复注释。若另行手敲,放到本课的 practice/ 子目录,用 go run ./lessons/09_pointers/practice 运行,不要在同一个包中重复定义 Student 或 main。
下一课学习方法:理解接收者、值接收者与指针接收者,让结构体拥有自己的操作。