1、go语言指针概述
go语言指针,其实就是Java中引用类型(区别于基本类型)的底层原理,涉及到内存结构,Java中的内存有堆栈方法区,而go语言中因为逃逸分析的存在,所以值类型和引用类型存放于堆栈是不一定的,编译器会自动优化,有利于减少垃圾产生,减少GC,同时减少程序员的负担。
2、go语言指针入门
理解go语言指针,只需要记住两个符号:*和&。
&很简单,就是取地址。
*的意思的取值,注意,这里有四种使用场景。
场景一:用在类名前面,构成新的引用类型的类。
场景二:用在变量名前面,同时变量位于赋值号左边。
场景三:用在变量名前面,同时变量位于赋值号右边。
场景四:用在变量名前面,同时变量作为参数或接收体。
场景二是给变量赋值,这个值一定是某个变量的地址。
场景三是取出变量的值参与运算。
场景四能够让变量本身的值发生改变。
试着实现以下需求:
1、取出值类型的地址。
2、把值类型的地址存在一个引用类型变量中。
3、取出引用类型的值的地址。
4、取出引用类型的值和地址。
5、把引用类型的变量的值进行更改。
6、两个不同引用类型变量指向同一个内存地址的值。
7、把引用类型变量的值取出来参与运算。
package main
import "fmt"
func main(){
//1、取出值类型的地址。
var a int = 13
fmt.Println("0、定义变量并赋值:a = ",a)
fmt.Println("1、取出值类型的地址: &a = ",&a)
//2、把值类型的地址存在一个引用类型变量中。
var b *int = &a
fmt.Println("2、把值类型的地址存在一个引用类型变量中:","var b *int = &a")
//3、取出引用类型的值的地址。
fmt.Println("3、取出引用类型的值的地址: b = ",b)//值的地址
//4、取出引用类型的值和地址。
fmt.Println("4.1、取出引用类型的值: *b = ",*b)//值
fmt.Println("4.2、取出引用类型的地址: &b = ",&b)//地址
//5、把引用类型的变量的值进行更改。
*b = 16
fmt.Println("5、把引用类型的变量的值进行更改: "," a = ",a,"; *b = ",*b)
//6、两个不同引用类型变量指向同一个内存地址的值。
var c *int = b
var d *int = &a
if(c==d&&b==d){
fmt.Println("6、两个不同引用类型变量指向同一个内存地址的值:","b、c、d他们都一样。")
}
//7、把引用类型变量的值取出来参与运算。
var e int = *b*3
fmt.Println("7、把引用类型变量的值取出来参与运算: e = *b*3 = ",e)
}
以上代码打印结果为:
0、定义变量并赋值:a = 13
1、取出值类型的地址: &a = 0xc00000a0a8
2、把值类型的地址存在一个引用类型变量中: var b *int = &a
3、取出引用类型的值的地址: b = 0xc00000a0a8
4.1、取出引用类型的值: *b = 13
4.2、取出引用类型的地址: &b = 0xc000006030
5、把引用类型的变量的值进行更改: a = 16 ; *b = 16
6、两个不同引用类型变量指向同一个内存地址的值: b、c、d他们都一样。
7、把引用类型变量的值取出来参与运算: e = b3 = 48
3、函数中的指针
以下示例可帮助理解函数中的指针:
package main
import "fmt"
func changeValue(p int){
p = 10
}
func main(){
var a int = 2
changeValue(a)
fmt.Println("a = ",a)
}
//打印出来a = 2
//这是因为函数调用时是值传递,
//changeValue函数调用完毕没有返回值,
//所以把2传进去自己玩了一把寂寞,然而并没有什么卵用。
如果想实现:把a传进去,修改的就是a本身的值,怎么做?
用指针,引用传递,见如下代码:
package main
import "fmt"
func changeValue(p *int){
//*int是一种区别于int的类型,
//用以存int值的内存地址
*p = 10//如果p = 10,表示把p的值修改为10
//*p = 10 *是取值符,表示找到p表示的地址指向的内存的值修改为10
}
func main(){
var a int = 2
changeValue(&a)
fmt.Println("a = ",a)//打印出来 a = 10
}
4、复杂类型对象如何取地址?
var r1 *Rect//Rect是自定义的结构体
r1 = &Rect{100,200}
fmt.Println("值的地址:r1 = ",r1)
fmt.Println("值:*r1 = ", *r1)
fmt.Println("引用的地址:&r1 = ", &r1)
这段我打印出来是:
值的地址:r1 = &{100 200}
值:*r1 = {100 200}
引用的地址:&r1 = 0xc000006030
问题:这个 &{100 200}到底是什么地址?
答:可以用Printf("%p",r1)拿到r1的值的地址。
问题:引用的地址是什么鬼?
答:引用就是指针,就是存地址的变量,他本身也有地址。
var r1 *Rect
r1 = &Rect{100,200}
fmt.Printf("r1值的地址:%p\n",r1)
//打印结果:r1值的地址:0xc00000a0a8
5、指针与面向对象
实现以下需求:
1、创建学生类、老师类
2、给每个类写一个构造方法,一个成员方法。
3、实例化一个学生,一个老师。
tip:学生类中有一个引用类型属性;
学生类构造方法创造引用类型,老师类构造方法创造值类型;
package main
import "fmt"
func main(){
var college *string
var str string
str = "北京大学"
college = &str
//实例化一个学生
stu := newStu("张楚岚", 16, 10086, college)
stu.selfIntro()
//实例化一个老师
t1 := newTeacher("冯宝宝",200,999999999)
t1.speak()
}
type student struct{
name string
age int
stuId int
college *string
}
type teacher struct{
name string
age int
salary float32
}
/*学生类构造方法*/
func newStu(name string,age int,stuId int,college *string) *student{
//因为返回值类型*student是引用类型,所以下面要用&把地址取出来赋值返回
s := &student{
name,
age,
stuId,
college,
}
return s
}
/*老师类构造方法*/
func newTeacher(name string,age int,salary float32) teacher{
//因为返回值类型teacher是值类型,所以下面直接赋值返回
t :=teacher{
name,
age,
salary,
}
return t
}
/*学生类成员方法*/
func(stu *student)selfIntro(){
fmt.Println("My name is",stu.name,",I graduated from",*stu.college)
}
/*老师类成员方法*/
func(t *teacher)speak(){
fmt.Println("I am",t.name)
}
6、项目中的指针示例
理解如下代码:
1、把 *类放在接收体中。(类成员方法)
2、把 *类放在函数形参中。
3、把 *变量放在函数体中,赋值号左边。(真的改变传的变量)
例如:lotus源码中:\extern\sector-storage\manager.go
func (m *Manager) readPiece(sink io.Writer, sector storage.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, rok *bool) func(ctx context.Context, w Worker) error {
return func(ctx context.Context, w Worker) error {
r, err := m.waitSimpleCall(ctx)(w.ReadPiece(ctx, sink, sector, offset, size))
if err != nil {
return err
}
if r != nil {
*rok = r.(bool)
}
return nil
}
}