指针是存储另一个变量的内存地址的变量。区别于C/C++中的指针,在Go中的指针不能进行偏移和运算,是安全指针。
1、指针地址、指针类型、指针取值
&*
• 默认值 nil,没有 NULL 常量。
• 操作符 "&" (取地址符) 取变量地址,"*" (取值符)透过指针访问目标对象。
• 不支持指针运算,不支持 "->" 运算符,直接用 "." 访问目标成员。
1.1 指针地址和指针类型
(int、float、bool、string、array、struct)*int、*int64、*string
var ip *int /* 指向整型*/ //声明一个int值得指针变量
var fp *float32 /* 指向浮点型 */
var sp *string /* 指向字符串类型 */
a := 1001
ptr := &a
1.2 指针取值
*&*
- 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
/*
指针:pointer
存储了另一个变量内存地址的变量
*/
// 1. 定义 int 类型变量 a
a := 10
fmt.Printf("a的数值:%v,类型:%T\n", a, a) // a的数值:10,类型:int
// & 符 取变量地址, %p 指针占位符
fmt.Printf("a的内存地址:%p\n", &a) // a的内存地址:0xc00000a098
// 2、声明 *int 类型指针变量 p1,用于存储 a 的内存地址
var p1 *int
fmt.Println(p1) // <nil> 空指针
// p1 指向了 a 的内存地址
p1 = &a
fmt.Printf("p1的数值:%v\n", p1) // p1的数值:0xc00000a098
fmt.Println("p1自己的地址:", p1) // p1自己的地址: 0xc00000a098
// 获取 指针p1 指向变量的数值:*p1
fmt.Printf("p1的数值,是a的地址,该地址存储的数值为:%v\n", *p1)
// p1的数值,是a的地址,该地址存储的数值为:10
// 3、操作变量:修改变量a的数值,并不会改变a的地址
fmt.Printf("a的地址:%p,a的值为:%v\n", &a, a)
// a的地址:0xc00000a098,a的值为:10
a = 100
fmt.Printf("a的地址:%p,a的值为:%v\n", &a, a)
// a的地址:0xc00000a098,a的值为:100
// 4、通过指针,修改变量的值
fmt.Printf("a的地址:%p,a的值为:%v\n", &a, a)
// a的地址:0xc00000a098,a的值为:100
*p1 = 200
fmt.Printf("a的地址:%p,a的值为:%v\n", &a, a)
// a的地址:0xc00000a098,a的值为:200
// 5、指针的指针
// 声明指针p1的指针p2,用于存储p1的内存地址
var p2 **int // p2 的值类型:只能为 *int
fmt.Println(p2) // <nil>
p2 = &p1
fmt.Printf("类型,a: %T, p1: %T,p2: %T\n", a, p1, p2)
// 类型,a: int, p1: *int,p2: **int
fmt.Printf("p2的数值:%v\n", p2) // p2的数值:0xc000006030
fmt.Printf("p2自己的地址:%v\n", &p2) // p2自己的地址:0xc000006038
fmt.Printf("p2的数值,是p1的内存地址,该地址存储的数值为:%v\n", *p2)
// p2的数值,是p1的内存地址,该地址存储的数值为:0xc00000a098
fmt.Printf("根据指针的指针p2也可以直接获取数据:%v", **p2)
// 根据指针的指针p2也可以直接获取数据:200
1.3 空指针
- 当一个指针被定义后没有分配到任何变量时,它的值为 nil
var ptr *string
fmt.Printf("ptr值:%v,ptr类型:%T\n", ptr, ptr)
// ptr值:<nil>,ptr类型:*string
- 空指针的判断
var ptr *int
if ptr != nil{
fmt.Println("ptr is not nil")
}else {
fmt.Println("ptr is nil")
}
2、数组指针和指针数组
2.1 定义
*[len]Type
// *[5]float64,指针,存储了5个浮点数据类型数组的内存地址
// *[5]int,指针,存储了5个整型数据类型数组的内存地址
[len]*Type
// [4] *int 数组,存储了4个整型的指针地址
// [4] *string 数组,存储了4个字符串的指针地址
*[5]*float64 // 指针,存储了5个字符串的指针地址的数组
*[3]*int // 指针,存储了3个整型的指针地址的数组
**[5]string // 指针的指针,存储了5个字符串数据的数组
**[5]*string // 指针的指针,存储了5个字符串的指针地址的数组
2.1 数组指针
// 1、创建 数值 arr1
arr1 := [4]int{1,2,3,4}
fmt.Println(arr1) // [1 2 3 4]
// 2、创建一个指针,存储数值的指针--->数值指针
var p1 *[4]int
p1 = &arr1
fmt.Println(arr1) // [1 2 3 4]
fmt.Println(p1) // &[1 2 3 4]
// 3、根据数组指针,操作数组
fmt.Println(*p1) // [1 2 3 4]
(*p1)[0] = 100 // 注意 () 圆括弧
fmt.Println(arr1) // [100 2 3 4]
p1[0] = 200 // 简化写法:可简写为 (*p1)[0] --> p1[0]
fmt.Println(arr1) // [200 2 3 4]
2.2 指针数组
// 指针数组
a := 1
b := 2
c := 3
d := 4
arr2 := [4]int{a,b,c,d}
arr3 := [4]*int{&a, &b, &c, &d}
fmt.Println(arr2) // [1 2 3 4]
fmt.Println(arr3) // [0xc00000a0f0 0xc00000a0f8 0xc00000a100 0xc00000a108]
- 修改指针数组,根据内存地址修改原地址存储的值
fmt.Println(arr2) // [1 2 3 4]
arr2[0] = 100 // arr2[0]赋新值,但a的值并无变化
fmt.Println(arr2) // [100 2 3 4]
fmt.Println(a) // 1
// 仅仅修改原&arr2[0]元素内存地址存储的值,即原a的值修改
*arr3[0] = 200
fmt.Println(arr2) // [100 2 3 4]
fmt.Println(a) // 200
3、函数指针和指针函数
- 函数指针:一个指针,指向了一个函数的指针。go语言中 函数名默认即为函数指针,可以被赋值及调用。
go中,function默认看成一个指针,没有*;常见的引用类型有:map、slice、function
func func1 (){
fmt.Println("func1...")
}
func main() {
var a func() // 声明 a 为 func() 类型
a = func1
a() // func1...
}
- 指针函数:一个函数,该函数的返回值是一个指针。
func main(){
arr1 := func2()
fmt.Printf("arr1的数值类型为:%T,地址:%p,数值:%v\n", arr1, &arr1,arr1)
// arr1的数值类型为:[4]int,地址:0xc0000aa060,数值:[1 2 3 4]
arr2 := func3()
fmt.Printf("arr2的数值类型为:%T,地址:%p,数值:%v\n", arr2, arr2,arr2)
// arr2的数值类型为:*[4]int,地址:0xc000010240,数值:&[1 2 3 4]
}
// 普通函数,返回数值
func func2() [4] int {
arr := [4]int{1,2,3,4}
return arr
}
// 指针函数,返回数值的指针
func func3() *[4]int{
arr := [4]int{1,2,3,4}
fmt.Printf("函数中arr的地址:%p\n", &arr)
// 函数中arr的地址:0xc000010240
return &arr
}
4、指针作为参数
指针作为参数,而参数的传递分为:值传递、引用传递
- 值传递:传递数据的副本,开辟新的内存空间,把数据复制一份传递过去
- 引用传递:传递的是地址,导致多个变量指向同一个内存地址
1)值传递,不会修改原内存地址所存储的数值
func main(){
a := 10
fmt.Println("func1()函数调用前a的值:", a)
func1(a)
fmt.Println("func1()函数调用后a的值:",a)
}
func func1(num int){
fmt.Println("func1()函数中,num的值:", num)
num = 100
fmt.Println("func1()函数中修改num的值:",num)
}
输出:可以看到a的值并未被修改
func1()函数调用前a的值: 10
func1()函数中,num的值: 10
func1()函数中修改num的值: 100
func1()函数调用后a的值: 10
2)引用传递,则会修改原内存地址所存储的数值
func main(){
a := 10
fmt.Println("func2()函数调用前a的值:", a)
func2(&a)
fmt.Println("func2()函数调用后a的值:",a)
}
func func2(ptr *int){
fmt.Println("func2()函数中,num的值:", *ptr)
*ptr = 200
fmt.Println("func2()函数中修改num的值:", *ptr)
}
输出:可以看到a的值 已经被修改
func2()函数调用前a的值: 10
func2()函数中,num的值: 10
func2()函数中修改num的值: 200
func2()函数调用后a的值: 200
5、结构体指针
5.1 结构体初识
结构体:
- 由一系列具有不同类型或相同类型的数据构成的数据集合
- 其中每一个变量称为结构体成员,也被称为“字段”,字段名唯一
- 字段类型也可以为结构体,即结构体的嵌套
- 结构体放弃了python中面向对象的继承等class类的特性
定义结构体:首字母大写表示导包时可以对外访问,其中的元素也是一样
package main
import "fmt"
func main(){
// 初始化结构体
// 1、方法一
var p1 Person
fmt.Println(p1) // { 0 },默认类型的零值
p1.Name = "王二狗子"
p1.Age = 30
p1.Sex = "男"
p1.Address = "北京"
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\n",p1.Name,p1.Age,p1.Sex,p1.Address)
// 姓名:王二狗子,年龄:30,性别:男,地址:北京
// 2、方法二
p2 := Person{}
p2.Name = "Pony"
p2.Age = 28
p2.Sex = "男"
p2.Address = "南京"
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\n",p2.Name,p2.Age,p2.Sex,p2.Address)
// 姓名:Pony,年龄:28,性别:男,地址:南京
// 3、方法三
p3 := Person{Name:"李小花", Age:25, Sex:"女", Address:"成都"}
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\n",p3.Name,p3.Age,p3.Sex,p3.Address)
// 姓名:李小花,年龄:25,性别:女,地址:成都
p4 := Person{
Name: "alex",
Age: 88,
Sex: "男",
Address: "邯郸",
}
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\n",p4.Name,p4.Age,p4.Sex,p4.Address)
// 姓名:alex,年龄:88,性别:男,地址:邯郸
// 方法四:注意位置参数,务必位置对齐
p5 := Person{"eva", 26, "女", "海淀"}
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\n",p5.Name,p5.Age,p5.Sex,p5.Address)
// 姓名:eva,年龄:26,性别:女,地址:海淀
}
// 1、定义结构体
type Person struct {
Name string
Age int
Sex string
Address string
}
5.2 结构体指针
常见数据类型:
- 值类型:int、float、bool、string、array、struct,值类型默认为深拷贝
- 引用类型:slice、map、function、pointer
5.2.1 证明结构体为值类型,默认为深拷贝
默认的结构体传值仅仅是值传递,新开辟了内存空间
package main
import "fmt"
func main() {
p1 := Person{"Pony", 22}
fmt.Printf("%p,%T,%v\n",&p1,p1,p1)
// 0xc000004078,main.Person,{Pony 22}
p2 := p1
p2.Name = "robbin"
fmt.Printf("%p,%T,%v\n",&p2,p2,p2)
// 0xc0000040c0,main.Person,{robbin 22}
fmt.Printf("%p,%T,%v\n",&p1,p1,p1)
// 0xc000004078,main.Person,{Pony 22}
}
type Person struct {
Name string
Age int
}
5.2.1 设置结构体为浅拷贝,即结构体指针
设置结构体指针,多个变量指向同一个内存地址
package main
import "fmt"
func main() {
p1 := Person{"Pony", 22}
fmt.Printf("%p,%T,%v\n",&p1,p1,p1)
// 0xc000004078,main.Person,{Pony 22}
// 定义结构体指针
var ptr *Person
ptr = &p1
fmt.Printf("%p,%T,%v,%v\n",&ptr,ptr,ptr, *ptr)
// 0xc000006030,*main.Person,&{Pony 22},{Pony 22}
// (*ptr).Name = "风清扬"
ptr.Name = "风清扬"
fmt.Printf("%p,%T,%v,%v\n",&ptr,ptr,ptr, *ptr)
// 0xc000006030,*main.Person,&{风清扬 22},{风清扬 22}
}
type Person struct {
Name string
Age int
}
5.3 内置new()函数 创建引用类指针结构体
只能
new()函数,在go中专门用于创建某种类型的指针的函数
注意:new()初始化返回的数据 ,默认不是nil,而是空指针,指向了新分配的类型T的内存空间,里边的零值!
import "fmt"
func main(){
ptr := new(int)
fmt.Printf("%T,%v,%v\n",ptr,ptr,*ptr)
// *int,0xc00000a098,0
*ptr = 10
fmt.Printf("%T,%v,%v\n",ptr,ptr,*ptr)
// *int,0xc00000a098,10
}
1)通过内置new()函数,创建结构体指针,实现浅拷贝
package main
import "fmt"
func main(){
ptr := new(Person)
//(*ptr).Name = "alex"
ptr.Name = "alex" // 简写
ptr.Age = 88
fmt.Printf("%T,%v\n",ptr,*ptr)
// *main.Person,{alex 88}
ptr2 := ptr
ptr2.Name = "Pony"
fmt.Printf("%T,%v\n",ptr2,*ptr2)
// *main.Person,{Pony 88}
fmt.Printf("%T,%v\n",ptr,*ptr)
// *main.Person,{Pony 88}
}
type Person struct{
Name string
Age int
}
6、unsafe.Pointer
待补充…