指针是存储另一个变量的内存地址的变量。区别于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

待补充…