Golang 指针的理解使用及数据库操作层封装

Golang 指针的理解使用及数据库操作层封装

Posted by 锐玩道 on April 26, 2021

如果❤️我的文章有帮助,欢迎点赞、关注。这是对我继续技术创作最大的鼓励。更多往期文章在我的个人博客

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
	*/
}

从上面代码中的 setAgeV1setAgeV2 函数的结果,我们可以看出指针的些许端疑

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
}