如果❤️我的文章有帮助,欢迎点赞、关注。这是对我继续技术创作最大的鼓励。更多往期文章在我的个人博客
在Golang 指针中,我们谈及过 Golang 指针的基本信息,但由于篇幅还不够详细。
今天藉由 数据库操作层封装
的实操机会,我们继续了解。
如果在之前接触过其他编程语言:Java、Python、PHP 等,在开发过程中并不会经常使用用到指针
,而在 Golang 中函数的从属
、变量的传递
都有机会用到指针。
于是乎用与不用,为什么需要使用指针?成为开发前必须明确的问题
指针的理解和使用
实践出真知,我们从例子出发,然后解读:
package main
import "fmt"
type Student struct {
age int
name string
}
// v1 版 修改年龄
func (student Student) setAgeV1(age int) {
student.age = age
}
// v2 版 修改年龄
func (student *Student) setAgeV2(age int) {
student.age = age
}
func (student Student) studentInfo(msg string) {
fmt.Printf("%s - name: %s, age: %d \n", msg, student.name, student.age)
}
func main() {
student1 := Student{age:18, name:"张三"}
student1.studentInfo("V0")
student1.setAgeV1(20)
student1.studentInfo("setAgeV1")
student1.setAgeV2(20)
student1.studentInfo("setAgeV2")
/** 输出:
V0 - name: 张三, age: 18
setAgeV1 - name: 张三, age: 18
学生的年龄 age 并没有被 方法 `student1.setAgeV1(20)` 更改成 20
setAgeV2 - name: 张三, age: 20
学生的年龄 age 被 方法 `student1.setAgeV2(20)` 更改成 20
*/
}
从上面代码中的 setAgeV1
、setAgeV2
函数的结果,我们可以看出指针
的些许端疑
Golang 只有一种变量传递方式:
值传递
、引用传递
。
一个值变量
作为参数
传递时,会被复制
出该变量的副本
,然后将副本传递给对应函数。如果你在函数内对参数&
取址操作,你会发现这个副本(参数)的地址和原来值变量的地址完全不一样
。
而一个指针变量
被作为参数
被引用时,会被复制
出该指针变量的副本(新指针)
,新指针 会和 原指针 指向相同内存地址,
结合例子分析
结合例子一起分析,我们可以得出这样的结论:
- 函数
setAgeV1()
中传递的参数为值变量
,函数修改student1
的副本,变量作用域只在 函数setAgeV1()
中; - 函数
setAgeV2()
中传递的参数为指针变量
,指向student1
地址。函数修改student1
变量本身。
函数传参使用指针与否
所以函数传参使用指针与否,我们能总结出下面的规律:
- 希望 参数 被函数修改,使用指针;相反不要使用指针;
- 大参数,传参时不希望发生内存拷贝(内存开销),使用指针;相反不要使用指针;
函数返回值使用指针与否
返回值使用值传递
、还是引用传递
标准同上
- 希望 返回值 被函数修改,使用指针;相反不要使用指针;
- 大struct/大数组,返回时不希望发生内存拷贝(内存开销),使用指针;相反不要使用指针;
- 函数作用域内的参数,使用
值传递(不使用指针)
Go编译器会尽量将对象分配到栈上;而引用传递(使用指针)
很可能会分配到对象上,这对垃圾回收会有影响
跳出指针,结合实际分析
我们把上面的例子稍微修改一下,你会发现两种方式都能修改变量:
// v1 版 修改年龄
func (student Student) setAgeV1(age int) Student {
student.age = age
return student
}
// v2 版 修改年龄
func (student *Student) setAgeV2(age int) {
student.age = age
}
函数setAgeV1()
是值传递,参数 student 复制参数副本(整个student发生内存拷贝) 进入函数 setAgeV1();修改 student.age
属性;返回修改后的 student ,复制副本(修改后的 student),返回给函数外面
函数setAgeV2()
是引用传递,参数 student 复制参数指针的副本进入函数 setAgeV1();修改 原来的student.age
属性;执行结束
见微知著: 不使用指针的是值传递,基本就不会走gc(原理:释放堆中不再使用/引用的对象所占用的空间),缺点是整个struct都发生内存拷贝,而被编译器识别为inline函数就什么都不会发生,性能极好。所以函数输入/返回参数就几个int,float,struct 建议使用值传递
指针就是类似引用传递,出作用域会走gc,当然也不是绝对,比如inline函数返回指针就不一定会导致堆分配,而Golang内置的new和make,map,slice等本身就分配在堆上就必然走gc。
gc对于密集型计算服务的后果就是大量cpu计算都消耗在gc上,严重影响性能;另外栈内存的分配花销时间可能比堆好。而且在实际开发维护中,大范围使用指针会因为不知道数据哪里被更改,而导致代码极难维护。
有了以上内容,看下面 dao
数据库操作层,就不会有太大问题
数据库操作 dao 层
dao/playerinfo_dao.go
package dao
// play_info 表数据操作层
import (
"log"
"superstar/models"
"xorm.io/xorm"
)
type PlayerinfoDao struct {
engine *xorm.Engine
}
func NewPlayerinfoDao(engine *xorm.Engine) *PlayerinfoDao {
return &PlayerinfoDao{
engine: engine,
}
}
// 获取单个
func (p PlayerinfoDao)Get(id int) *models.PlayerInfo {
data := &models.PlayerInfo{Id:id}
ok, err := p.engine.Get(data)
if ok && err == nil {
return data
} else {
data.Id = 0
return data
}
}
// 获取多个
func (p PlayerinfoDao)GetAll() []models.PlayerInfo {
datalist := []models.PlayerInfo{}
err := p.engine.Desc("id").Find(&datalist)
if err != nil {
log.Println(err)
return nil
} else {
return datalist
}
}
// 关键字查询
func (d PlayerinfoDao) Search(country string) []models.PlayerInfo {
// datalist := make([]models.StarInfo, 0)
datalist := []models.PlayerInfo{}
err := d.engine.Where("country=?", country).Desc("id").Find(&datalist)
if err != nil {
log.Println(err)
return nil
} else {
return datalist
}
}
// 软删除
func (d PlayerinfoDao) DeleteByID(id int) error {
data := &models.PlayerInfo{Id: id, PlayStatus: 1}
_, err := d.engine.ID(id).Update(data)
return err
}
// 修改
func (d PlayerinfoDao) Update(data *models.PlayerInfo, columns []string) error {
_, err := d.engine.MustCols(columns...).ID(data.Id).Update(data)
return err
}
// 新增
func (d PlayerinfoDao) Create(data *models.PlayerInfo) error {
_, err := d.engine.Insert(data)
return err
}