指针寻址总结
当我们使用一些方法时,需要传入变量的指针。go中对于指针的操作有相当明确的规定,不是任何变量和值都可以操作指针的
可寻址情况
- 一个变量: &x
- 指针引用(pointer indirection): &*x
- slice 索引操作(不管 slice 是否可寻址): &s[1]
因为 slice 底层实现了一个数组,它是可以寻址的 - 可寻址 struct 的字段: &point.X
- 可寻址数组的索引操作: &a[0]
- composite literal 类型: &struct{ X int }{1}
不可寻址的情况
-
字符串中的字节
-
map 对象中的元素
如果对象不存在,则返回零值,零值是不可变对象,所以不能寻址,如果对象存在,因为 Go 中 map 实现中元素的地址是变化的,这意味着寻址的结果是无意义的 -
接口对象的动态值(通过 type assertions 获得)
-
常数
如果可以寻址的话,我们可以通过指针修改常数的值,破坏了常数的定义。 -
literal 值(非 composite literal)
-
package 级别的函数
-
方法 method(用作函数值)
切片寻址使用案例
- 需求:
有一组不固定长度的数据。数据中的每个值都需要被传入一个方法,方法只接收指针变量。方法会根据传入顺序,修改数据的值,因为传入的是值的指针,原数据中对应的值也会发生变化。
因为原始数据的个数不是固定的,所以数组、结构体类型的变量都不能使用,切片和map可以满足需求。但是因为map中,不能针对每个值进行寻址,所以我们也无法使用,唯一可用的只有切片类型的变量
- 具体使用场景:
mysql数据库读取操作,从数据库读取值后,需要把读取的值赋值一些变量后,才能使用,
原始用法如下:
func read() map[int64]map[string]string{
rows,_ := mysql.Query("select id,name,img from user")
data := make(map[int64]map[string]string)
for rows.Next(){
var id string
var name string
rows.Scan(&id,&name)
dataIndex = strconv.ParseInt(id,10,64)
sonData := map[string]string{"id":id,"name":name}
data[id] = sonData
}
}
原始用法,在方法中写死了查询的字段,我们需要能够动态控制返回值,方法才能具有通用行,修改后如下
/**
* table 数据表名
* field 需要返回的字段名,使用逗号拼接
* index 使用哪个字段作返回map的键值
*/
func read(table string,field string,index string) map[int64]map[string]string{
rows,err := mysql.Query("select "+field+" from "+table)
checkErr(err)
data := make(map[int64]map[string]string)
for rows.Next(){
//拆分字段为字符串切片
fieldSlice := strings.Split(field,",")
//声明一个接口类型的切片,保存一个rows中的各字段值
fieldData := make([]interface{},len(fieldSlice),len(fieldSlice))
//设置一个callUserFuncArray用到的参数切片
//其实该把fieldData 中各个值的指针,插入到该变量中,因mysql的Scan方法需要传入指针类型变量
paramsSlice := make([]interface{},len(fieldSlice),len(fieldSlice))
for key := range fieldSlice{
paramsSlice[key] = &fieldData[key]
}
//callUserFuncArray方法为自定义方法,类似php中的call_user_func_array
res,resErr := callUserFuncArray(rows,"Scan",paramsSlice)
checkErr(resErr)
resSlice := res.([]reflect.Value)
if resSlice[0].Interface() == nil{
sonData := make(map[string]string)
for key2,val2 := range fieldSlice{
//scan方法读取值到接口类型变量中,值实际类型是uint8。先把接口类型转为实际类型,再转为字符串
sonData[val2] = string(fieldData[key2].([]uint8))
}
dataIndex,_ := strconv.ParseInt(sonData[index],10,64)
data[dataIndex] = sonData
}else{
panic(resSlice[0].Interface().(error))
}
}
return data
}
实现了接口的变量,作为参数传递,注意事项
//定义一个接口
type act interface {
run()
}
//定义一个结构体
type people struct {
name string
age int64
}
//结构体实现了方法run,也就是实现了接口act
func (p *people) run() {
fmt.Printf("is fast",p.name)
}
func main() {
//声明一个变量p,类型是people
p := people{"Bill", 16}
//调用run方法,可以有多种方式
//传统方式,因为是people的引用类型实现了run方法,所以需要声明一个引用类型的变量
p2 := &p
p2.run()
//简写,可以直接调用run。
p.run()
}
以上代码是一个正常的接口定义和实现的过程,可以正常运行
但是当我们加入如下代码时,就报错了
//定义一个方法,参数为实现了act接口的变量
func send(a act) {
a.run()
}
func main() {
//声明一个变量p,类型是people
p := people{"Bill", 16}
//把变量p传入send方法
send(p)
}
报错代码类似下图
意思是传入的变量没有实现接口的方法,可是我们之前的代码是可以正常运行的,为什么到了这里就不行了呢。
这里存在两种情况:
1、变量直接使用方法时
传入值是指针或者值,都可以。调用指针还是值,取决于方法定义时对接受者的声明
如下
func (alias_name reviser) A(){}//只会把接受者当做只读使用,不管传入的是指针还是值,因为指针会首先被解引用
func (alias_name *reviser) A(){}//只会把接受者当做指针,传入的是裸值也会自动转换为指针
2、变量实现了接口,以接口类型调用方法时
接收者是值的方法,可以通过值或者指针调用
接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址。
所以报错的代码修需改为
func main() {
//声明一个变量p,类型是people
p := people{"Bill", 16}
//把变量p传入send方法
send(&p)
}