目录
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 语言对某些类型(如数组
、切片
、结构体
)输出时的格式化处理。这种优化并不适用于单个基本数据类型(如 int
、string
)的地址输出。
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 type
和concrete type
(static type
是在编码是看见的类型(如int、string),concrete type
是runtime
系统看见的类型)。
类型断言能否成功,取决于变量的concrete type
,而不是static type
。因此,一个 reader
变量如果它的concrete type
也实现了write方法的话,它也可以被类型断言为writer.
反射是建立在类型之上的,Golang
指定类型的变量的类型是静态的(也就是指定int、string
这些的变量,它的type
是static type
),在创建变量的时候就已经确定,反射主要与Golang
的interface
类型相关(它的type是concrete type
) 只有interface类型才有反射一说
反射就是用来检测存储在接口变量内部(值value;类型concrete type) 的一种机制。
2.1 reflect
主要方法:
reflect.TypeOf
:获取变量类型,返回reflect.Type
类型;如果接口为空则返回nil
reflect.ValueOf
:获取变量的值,返回reflect.Value
类型;如果接口为空则返回0reflect.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
,如果要调用函数,通过 Value
的 Call()
方法
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 有参有返回调用
Value
的 Call()
方法的参数是一个 Value
的 slice
,对应的反射函数类型的参数,返回值也是一个 Value
的 slice
,同样对应反射函数类型的返回值。
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