首页 > 其他分享 >Go基础之指针和反射讲解

Go基础之指针和反射讲解

时间:2024-12-29 09:00:27浏览次数:5  
标签:int fmt reflect func 讲解 Go main 指针

目录

1 指针

学习该篇之前可以先看看 C语言的指针,点击此处了解 C语言指针 讲解

1.1 简介

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
一个指针变量指向了一个值的内存地址。

类似于变量和常量,在使用指针前需要声明指针。指针声明格式如下:

var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。

当一个指针被定义后没有分配到任何变量时,它的值为 nil,也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。

1.2 使用指针

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

指针解引用:在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

package main
import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */
   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

1.3 指针优化输出

1.3.1 优化输出复杂类型

在 Go 中,指针优化主要体现在 Go 语言对某些类型(如数组切片结构体)输出时的格式化处理。这种优化并不适用于单个基本数据类型(如 intstring)的地址输出。

number := [3]int{5, 6, 7}
fmt.Println("number数组的值的地址:", &number)
输出结果
number数组的值的地址: &[5 6 7]

Go 语言对数组类型指针(*[N]T)的格式化输出进行了优化,直接显示数组的内容(即 &[5, 6, 7])。
当指针指向一个数组时,fmt 包会解引用该指针并显示数组内容,而不是打印数组指针的原始地址。
主要是,Go 的设计者希望输出数组指针时,开发者能快速看到数组的内容,而不是内存地址。

1.3.2 去掉优化

如果不希望 &[5, 6, 7] 优化输出,可以打印指针本身:

fmt.Printf("数组的真实地址: %p\n", &number)
// 输出:数组的真实地址: 0xc000014080

1.3.3 基本类型

没有优化基本类型:

var a int = 1
fmt.Println("&a:", &a)
输出结果
&a: 0xc000014088

对于基本类型(如 int、float 等),&a 只表示 a 的地址,而没有特殊优化来显示值。
基本类型输出指针变量(如 &a)时,fmt.Println 默认不会尝试解引用并显示值,而是直接显示地址。这是 Go 语言的设计原则,确保基本数据类型的行为一致。

1.4 指针数组

如果需要保存数组,这样我们就需要使用到指针。
声明 整型指针数组:var ptr [MAX]*int;
ptr 为整型指针数组。因此每个元素都指向了一个值。以下实例的三个整数将存储在指针数组中:

package main

import "fmt"

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int;

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   }
}
以上代码执行输出结果为:

a[0] = 10
a[1] = 100
a[2] = 200

使用range遍历指针数组

const max = 3
func main(){
	number := [max]int{5, 6, 7}
    var ptrs [max]*int //指针数组
    //将number数组的值的地址赋给ptrs
	fmt.Println("number数组的值的地址:", &number)
    for i, x := range &number {
		fmt.Println(i, x)
        ptrs[i] = &x
    }
    for i, x := range ptrs {
        fmt.Printf("指针数组:索引:%d 值:%d 值的内存地址:%d\n", i, *x, x)
    }
}

结果:
number数组的值的地址: &[5 6 7]
0 5
1 6
2 7
指针数组:索引:0 值:5 值的内存地址:824633762096
指针数组:索引:1 值:6 值的内存地址:824633762104
指针数组:索引:2 值:7 值的内存地址:824633762112

1.4.1 指针数组优化

(*arr)[0] 可以替换为 arr[0]

package main

import (
	"fmt"
)

func modify(arr *[3]int) {        
	(*arr)[0] = 90
	//或者用这样的 arr[0] = 90
}

func main() {        
	a := [3]int{ 89, 90, 91}
	modify(&a)
	fmt.Println(a)
}

1.5 指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址

指向指针的指针变量声明格式如下:var ptr **int;
以上指向指针的指针变量为整型。

访问指向指针的指针变量值需要使用 两个 *号,如下所示:

package main

import "fmt"

func main() {
   var a int
   var ptr *int
   var pptr **int

   a = 3000

   /* 指针 ptr 地址 */
   ptr = &a

   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 a = %d\n", a )
   fmt.Printf("指针变量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}

还可以多级递增,比如:指向指针的指针的指针

func main(){
  var a int = 1
  var ptr1 *int = &a
  var ptr2 **int = &ptr1
  var ptr3 **(*int) = &ptr2 // 也可以写作:var ptr3 ***int = &ptr2
  // 依次类推
  fmt.Println("a:", a)
  fmt.Println("ptr1", ptr1)
  fmt.Println("ptr2", ptr2)
  fmt.Println("ptr3", ptr3)
  fmt.Println("*ptr1", *ptr1)
  fmt.Println("**ptr2", **ptr2)
  fmt.Println("**(*ptr3)", **(*ptr3)) // 也可以写作:***ptr3
}

1.6 向函数传递指针参数

Go 允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。
以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的值,:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前 a 的值 : %d\n", a )
   fmt.Printf("交换前 b 的值 : %d\n", b )

   /* 调用函数用于交换值
   * &a 指向 a 变量的地址
   * &b 指向 b 变量的地址
   */
   swap(&a, &b);

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 将 y 赋值给 x */
   *y = temp    /* 将 temp 赋值给 y */
}

2 反射

Golang语言实现的反射机制就是指在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的。
go的变量包括type, value两部分,type 包括static typeconcrete type(static type是在编码是看见的类型(如int、string),concrete typeruntime系统看见的类型)。
类型断言能否成功,取决于变量的concrete type,而不是static type。因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

反射是建立在类型之上的,Golang指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的typestatic type),在创建变量的时候就已经确定,反射主要与Golanginterface类型相关(它的type是concrete type只有interface类型才有反射一说
反射就是用来检测存储在接口变量内部(值value;类型concrete type) 的一种机制。

2.1 reflect

主要方法:

  • reflect.TypeOf:获取变量类型,返回reflect.Type类型;如果接口为空则返回nil
  • reflect.ValueOf:获取变量的值,返回reflect.Value类型;如果接口为空则返回0
  • reflect.Value.Kind:获取变量的类别,返回一个常量;表示该类型的特定种类
  • reflect.Value.Interface():转换成 interface{} 类型;
  • NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法返回字段 i 的 reflect.Value,其中 i 是字段的 索引。在 Go 的反射(reflect)机制中,结构体的字段是按定义顺序分配索引的,索引从 0 开始

2.1.1 示例

获取变量类型

package main

import (
    "fmt"
    "reflect"
)
func test(i interface{}) { 
    // 反射获取值类型
    t := reflect.TypeOf(i)
    fmt.Println(t)
    // 反射获取值
    v := reflect.ValueOf(i)
    fmt.Println(v)
}
func main() {     
    var a int = 10
    test(a)
}
/*
int
10
*/

类型和类别

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string
    Age int
    Score float32
}
func test(i interface{}) {   
    // 反射获取类型
    t := reflect.TypeOf(i)
    fmt.Println("类型:",t)
    // 反射获取类别
    v := reflect.ValueOf(i)
    k := v.Kind()
    fmt.Println("类别:",k)
}
func main() {    
    var stu Student = Student {   
        Name: "zhangsan",
        Age: 19,
        Score: 88,
    }
    test(stu)
}

/*
类型: main.Student
类别: struct
*/ 

示例:断言处理类型转化

package main

import (
    "fmt"
    "reflect"
)

type Student struct {     
    Name string
    Age int
    Score float32
}
func test(i interface{}) { 
    // 反射获取类型
    t := reflect.TypeOf(i)
    fmt.Println("类型:",t)
    // 反射获取类别
    v := reflect.ValueOf(i)
    k := v.Kind()
    fmt.Println("类别:",k) 
    // 转换成接口类型
    iv := v.Interface()
    // 断言
    stu,ok := iv.(Student)
    if ok {
        fmt.Printf("结构:%v 类型:%T\n",stu,stu)
    }
}
func main() {   
    var stu Student = Student {   
        Name: "zhangsan",
        Age: 19,
        Score: 88,
    }
    test(stu)
}
/*
类型: main.Student
类别: struct
结构:{zhangsan 19 88} 类型:main.Student
*/

2.2 获取变量值 ValueOf

获取变量值:

  • reflect.ValueOf(x).Float()
  • reflect.ValueOf(x).Int()
  • reflect.ValueOf(x).String()
  • reflect.ValueOf(x).Bool()

示例:类型转换

package main

import (
    "fmt"
    "reflect"
)

func testInt(b interface{}) {    
    val := reflect.ValueOf(b)
    fmt.Printf("val 类型:%T\n",val)
    // 转换成 int 类型,其他类型都可以转化成 string
    a := val.Int()
    fmt.Printf("a 类型:%T\n",a)
}
func main() {   
    testInt(100)
}

/*
val 类型:reflect.Value
a 类型:int64
*/

2.3 修改变量值 Value.Set

设置变量值:

  • reflect.Value.SetFloat() #设置浮点数
  • reflect.Value.SetInt() #设置整数
  • reflect.Value.SetString() #设置字符串

2.3.1 Elem 方法

Elem() 方法可以用来获取反射值指向的对象的 元素,通常在要通过反射修改一个值时,需要获取指向该值的指针,并且需要通过 Elem() 来获取其底层值。

那么什么时候使用 Elem()

Elem() 方法用于从 指针 类型的反射值获取它指向的对象的值。也就是说,如果通过反射获取的是一个指向某个对象的指针类型(比如 *int),然后想修改这个对象的值,就需要使用 Elem() 来获取该指针指向的实际值。

package main

import (
	"fmt"
	"reflect"
)

func main() {
    a := 10
    fmt.Println("Before:", a)
    // 获取 a 的反射值
    v := reflect.ValueOf(&a)
    // 通过 Elem() 获取指针指向的值并修改
    v.Elem().SetInt(20)
    fmt.Println("After:", a) // 输出 20
}

示例:修改值

package main

import (
    "fmt"
    "reflect"
)

func testInt(b interface{}) {
    val := reflect.ValueOf(b)
    // 更改值需要value的地址,否则会崩溃,Elem() 表示 *
    val.Elem().SetInt(100)
    c := val.Elem().Int()
    fmt.Printf("类型:%T,值:%d\n",c,c)
}

func main() {    
    var i = 10
    testInt(&i)
    fmt.Println(i)
}
/*
类型:int64,值:100
100
*/

2.3.2 修改结构体反射

对于结构体中的字段,如果你想修改它们,也需要使用 Elem() 来获取该结构体指针的底层值。

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
}

func main() {
    p := Person{"Alice", 30}
    fmt.Println("Before:", p)

    v := reflect.ValueOf(&p) // 获取结构体指针的反射值

    // 修改结构体字段
    v.Elem().FieldByName("Name").SetString("Bob")
    v.Elem().FieldByName("Age").SetInt(40)

    fmt.Println("After:", p) // 输出 {Bob 40}
}

解释:

  • reflect.ValueOf(&p):获取的是 Person 结构体指针的反射值。
  • 使用 v.Elem():获取该结构体指针指向的实际结构体值。
  • FieldByName("Name")FieldByName("Age"):获取到结构体字段的反射值,然后使用 SetString()SetInt() 来修改字段的值。

示例:反射出结构体属性和方法数量

package main

import (
    "fmt"
    "reflect"
)

type Student struct {  
    Name string
    Age int
    Score float32
}

func (s Student) Sleep() {  
    fmt.Println("正在睡觉")
}

func (s Student) Run(min interface{ }) {  
    fmt.Printf("跑步%d分钟",min)
}

// 断言结构体
func Test(object interface{ }) {  
    // 获取值
    v := reflect.ValueOf(object)
    // 获取类别
    c := v.Kind()
    // 判断类别是否为结构体
    if c != reflect.Struct {
     fmt.Println("expect struct")
        return
    }
    // 获取结构体字段数量
    num := v.NumField()
    fmt.Println("字段数量:",num)
    // 获取结构体方法数量
    numOfMethod := v.NumMethod()
    fmt.Println("方法数量:",numOfMethod)
}

func main() {     
    var stu Student = Student {  
        Name: "zhangsan",
        Age: 18,
        Score: 90,
    }
    Test(stu)
}

/*
字段数量: 3
方法数量: 2
*/

2.4 函数反射

2.4.1 简介

示例:Go 中函数可以赋值给变量。

package main

import "fmt"

func Hello() {  
    fmt.Println("hello world")
}
func main() {
   // 函数可以赋值给变量
    a := Hello
    a()
}
/*
hello world
*/

2.4.2 无参调用

函数可以像普通的类型变量一样,在反射机制中就和不同的变量是一样,在反射中函数和方法的类型 (Type) 都是 reflect.Func,如果要调用函数,通过 ValueCall() 方法

package main

import (
    "fmt"
    "reflect"
)

func Hello() {     
    fmt.Println("hello world")
}

func main() {    
    // 反射获取 hello
    v := reflect.ValueOf(Hello)
    fmt.Printf("类型:%T\n",v)
    // 判断 hello 的类型
    if v.Kind() == reflect.Func {
      fmt.Println("OK")
    }
    // 反射调用函数
    v.Call(nil)
}

/*
类型:reflect.Value
OK
hello world
*/

2.4.3 有参有返回调用

ValueCall() 方法的参数是一个 Valueslice,对应的反射函数类型的参数,返回值也是一个 Valueslice,同样对应反射函数类型的返回值。

package main

import (
    "fmt"
    "reflect"
    "strconv"
)
func prints(i int) string {  
    fmt.Println("i=",i)
    return strconv.Itoa(i)
}
func main() {     
    fv := reflect.ValueOf(prints)
    // 定义参数切片
    params := make([]reflect.Value,1)
    // 参数为 20
    params[0] = reflect.ValueOf(20)
    // 反射函数,获取处理结果
    result := fv.Call(params)
    fmt.Printf("result 类型:%T\n",result)
    fmt.Printf("result 转换后类型:%T,值是:%s\n",result[0].Interface().(string),result[0].Interface().(string))
}

/*
i= 20
result 类型:[]reflect.Value
result 转换后类型:string,值是:20
*/

2.5 方法反射

函数和方法可以说其实本质上是相同的,只不过方法与一个对象进行了绑定,方法是对象的一种行为,这种行为是对于这个对象的一系列操作,例如修改对象的某个属性。

2.5.1 MethodByName

MethodByName 是用来通过方法的 名称 来查找并获取该方法的反射对象。是通过字符串名称来获取方法的。

语法:func (v Value) MethodByName(name string) (Method, bool)

  • v: 是一个反射值,表示一个结构体类型的对象。
  • name: 方法的名称,是一个字符串。

返回值:

  • Method: 表示方法的反射对象。
  • bool: 表示方法是否存在。如果方法存在,返回 true,否则返回 false。

示例

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct{}

func (m MyStruct) Greet(name string) {
	fmt.Println("Hello", name)
}

func main() {
	m := MyStruct{}
	// 获取反射值
	v := reflect.ValueOf(m)
	// 获取方法名为 "Greet" 的反射对象
	method, exists := v.MethodByName("Greet")
	if exists {
		// 调用方法
		method.Call([]reflect.Value{reflect.ValueOf("Alice")}) // 输出: Hello Alice
	} else {
		fmt.Println("Method not found")
	}
}
package main

import (
    "fmt"
    "reflect"
    "strconv"
)

type Student struct {     
    age int
    name string
}

func (s *Student) SetAge(i int) {     
    s.age = i
}

func (s *Student) SetName(name string) {     
    s.name = name
}

func (s *Student) String() string {     
    return fmt.Sprintf("%p",s) + "--name:" + s.name + " age:" + strconv.Itoa(s.age)
}

func main() {     
    // 实例化
    stu := &Student{22,"zhangsan"}
    // 反射获取值,指针方式
    stuV := reflect.ValueOf(&stu).Elem()
    // 也可以使用非指针(值类型) stuV := reflect.ValueOf(stu)
    fmt.Println("Before:",stuV.MethodByName("String").Call(nil)[0])    
    // 修改值
    params := make([]reflect.Value,1)
    params[0] = reflect.ValueOf(18)
    stuV.MethodByName("SetAge").Call(params)  
    params[0] = reflect.ValueOf("lisi")
    stuV.MethodByName("SetName").Call(params)
    
    fmt.Println("After:",stuV.MethodByName("String").Call(nil)[0])
}

2.5.2 Method

Method 是用来通过反射值获取某个方法的反射对象,它需要一个方法的 索引 来定位目标方法。返回的是一个 reflect.Method,其中包含了方法的信息(如方法类型、方法值等)。

语法:func (v Value) Method(i int) Method

  • v: 是一个反射值,表示一个结构体类型的对象。
  • i: 是方法的索引,方法的索引是从 0 开始的。它是由方法在类型中的定义顺序来决定的,方法的索引值是 按声明顺序 分配的。

例如,如果你有一个结构体 MyStruct,它有三个方法,那么 i = 0 会返回第一个方法。

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct{}

func (m MyStruct) Greet(name string) {
	fmt.Println("Hello", name)
}

func main() {
	m := MyStruct{}
	// 获取反射值
	v := reflect.ValueOf(m)
	// 获取第一个方法的反射对象
	method := v.Method(0)
	// 调用方法
	method.Call([]reflect.Value{reflect.ValueOf("Alice")})  // 输出: Hello Alice
}

2.6 reflect性能

Golang的反射很慢,Golang reflect慢主要有两个原因:

  • 涉及到内存分配以及后续的GC;
  • reflect实现里面有大量的枚举,也就是for循环,比如类型之类的;

这里取出来的 field 对象是 reflect.StructField 类型,但是它没有办法用来取得对应对象上的值。

type_ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")

如果要取值,得用另外一套对object,而不是type的反射,fieldValue类型是 reflect.Value,它是一个具体的值,而不是一个可复用的反射对象了,每次反射都需要malloc这个reflect.Value结构体,并且还涉及到GC。

type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")

标签:int,fmt,reflect,func,讲解,Go,main,指针
From: https://www.cnblogs.com/jingzh/p/18638387

相关文章