go Gorm连接数据库,并实现增删改查操作

news2024/11/25 10:55:26

Gorm

1. 准备工作

首先进入终端下载我们需要的包(确保go和mysql安装完成,并设置了环境变量)

go get -u gorm.io/driver/mysql
go get -u gorm.io/gorm

有两份官方文档有对 GORM 更详细的讲解。

  1. 创建 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.(中文)
  2. go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go’s (golang) database/sql package (github.com)

2. 连接数据库

这里以mysql为例,其他数据库请看官方文档。

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

user -> 用户名

pass -> 密码

127.0.0.1 -> mysql默认本地地址,也就是localhost

3306 -> mysql默认端口

dbname -> 数据库名称

charset -> 字符集编码

parseTime -> 把数据库datetime和date类型转换为golang的time.Time类型

loc -> 使用系统本地时区

gorm.config{} 中可以进行一些高级配置,详情请看官方文档。

例如:

我们可以在其中写入:
Logger: logger.Default.LogMode(logger.Info)

这个句子的作用是配置GORM库,告诉它使用默认的日志记录器并将日志模式设置为信息级别(logger.Info),以便在终端中输出有关数据库操作的信息。

这样我们在对使用GORM的时候就可以在终端中看到输出的对应SQL语句了。

3. 结构体与mysql table 的映射关系

go的orm框架可以让我们在用go语言实现数据库操作的时候不使用sql语言,简单易上手,提高开发效率。

下面是gorm中go与mysql的对应关系

GOmysql
结构体数据表
结构体实例数据行
结构体字段字段

例如,一个结构体:

type Student struct {
	ID                int
	Name              string
	age               int
	ExternalCharacter string
}

gorm 会将其映射为

CREATE TABLE `students` (
    `id` bigint AUTO_INCREMENT,
    `name` longtext,
    `external_character` longtext,
    PRIMARY KEY (`id`)
)

这里可以发现,结构体字段中的大写都变成了小写age完全消失Student也变成了studentsExternalCharacter变成了external_character。这里涉及到了gorm的映射规定。

具体可看模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

简单来说,就是咱要首字母大写,会按驼峰命名法给我们加 _ 符号,默认ID 为主键,表名会变成复数

再看数据类型,明显这是不知道有多大所以往大了去调

当然,这些我们也是能够一一自定义的。

3.1. 表名:

实现Tabler接口 中的TableName函数。

例如,针对上述的结构体,我们可以这样写:

func (s *Student) TableName() string {
	return "student"
}

这样表名就会被映射为student而不是students

3.2 字段名:

我们可以在字段后面打上gorm tag,tag有很多用途,比如标记主键,指定not null等等,具体请看官方文档。

这里介绍自定义字段名,还是以上述的结构体为例:

type Student struct {
	ID                int `gorm:"column:序号"`
	Name              string
	age               int
	ExternalCharacter string `gorm:"column:externalCharacter"`
}

如果我们改成这样的话,映射的表就是这样的:

CREATE TABLE `student` (
    `序号` bigint AUTO_INCREMENT,
    `name` longtext,
    `externalCharacter` longtext,
    PRIMARY KEY (`序号`)
)
3.2 数据类型

longtext改成 varchar(255) ,也是通过tag实现的。通过size:255来确定最大长度

bigint改成int只需要将int改成int32就行了,因为go中,咱64位的电脑默认intint64

例如:

type Student struct {
	ID                int32
	Name              string
	age               int
	ExternalCharacter string `gorm:"column:externalCharacter; size:255"`
}

映射出来的表就是这样的

CREATE TABLE `student` (
    `id` int AUTO_INCREMENT,
    `name` longtext,
    `externalCharacter` varchar(255),
    PRIMARY KEY (`id`)
)

tag中定义多种内容,中间打;号就行了

4. 创建table

我们可以通过 db.AutoMigratedb.Migrator().CreateTable() 方法来创建table。

AutoMigrate 方法:

使用 db.AutoMigrate 方法是一种自动创建和迁移数据库表的方式。它会根据你的 GORM 模型定义,自动检查数据库表是否存在,如果不存在则创建表,如果表结构有变化则进行迁移。这种方式适用于开发过程中的快速迭代和维护数据库结构的情况。

示例:

// 自动创建和迁移表
err := db.AutoMigrate(&Student{})
// 这里的db就是上述gorm.open的db哈
if err != nil {
    panic("创建/迁移表格失败, error = " + err.Error())
}

Migrator().CreateTable 方法:

使用 db.Migrator().CreateTable 方法是一种手动创建数据库表的方式。你需要显式地指定要创建的表,它不会检查是否已经存在,而是直接创建表。这种方式适用于在特定情况下需要手动控制表创建过程的情况。

示例:

// 手动创建表
err := db.Migrator().CreateTable(&Student{})
if err != nil {
    panic("创建表格失败, error = " + err.Error())
}

区别总结:

  • AutoMigrate 方法自动根据模型定义创建和迁移表,适用于自动化和快速迭代。
  • Migrator().CreateTable 方法手动创建表,适用于需要手动控制创建过程的情况。

在实际应用中,你可以根据项目需求选择合适的方法。通常情况下,开发和调试阶段可能更适合使用 AutoMigrate 方法,而特定场景下的手动控制可能需要使用 Migrator().CreateTable 方法。

5. 增

以下列结构体为例:

type Student struct {
	ID                int
	Name              string
	Age               int
	ExternalCharacter string `gorm:"column:externalCharacter; size:255"`
}

func (s *Student) TableName() string {
	return "student"
}
5.1. db.Create(结构体对象)
lisi := Student{2, "lisi", 18, "modest"}
result := db.Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

等于INSERT INTO student (name,age,externalCharacter,id) VALUES ('lisi',18,'modest',2)

当然也可以同时增添多个字段

例如:

students := []Student{
    {ID: 3, Name: "wangwu", Age: 20, ExternalCharacter: "generous"},
    {ID: 4, Name: "xuliu", Age: 21, ExternalCharacter: "liberal"},
}
result := db.Create(&students)
if result.Error != nil {
    panic("增加字段失败")
}
fmt.Println(result.RowsAffected) // 返回影响的行数 2

等于INSERT INTOstudent (name,age,externalCharacter,id) VALUES ('wangwu',20,'generous',3),('xuliu',21,'liberal',4)

5.2. db.Select(指定字段).Create(结构体对象)
lisi := Student{2, "lisi", 18, "modest"}
result := db.Select("ID", "Name", "Age").Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

等于INSERT INTO student (name,age,id) VALUES ('lisi',18,2)

5.3. db.Omit(忽略字段).Create(结构体对象)
lisi := Student{2, "lisi", 18, "modest"}
result := db.Omit("ExternalCharacter").Create(&lisi)
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

也等于INSERT INTO student (name,age,id) VALUES ('lisi',18,2)

5.4. 原生sql语句
lisi := Student{2, "lisi", 18, "modest"}
result := db.Exec("INSERT INTO `student` (`name`,`age`,`externalCharacter`,`id`) VALUES ('lisi',18,'modest',2)")
if result.Error != nil {
    panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1

6. 查

之后所有的操作都是基于我的这份表实现的。

我的表

我现在的表如下:

在这里插入图片描述

6.1 where

我们这里使用where 函数 设置条件

func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

参数说明:

参数名说明
querysql语句的where子句, where子句中使用问号(?)代替参数值,则表示通过args参数绑定参数
argswhere子句绑定的参数,可以绑定多个参数

例如db.Where("id in (?)", []int{1,2,3,4})

后面的 select、having 语句这些 query 的也同样适用

6.2 Take

取走第一条查询信息

s1 := Student{}
db.Where("age = 18").Take(&s1)
fmt.Println(s1)

这里我们是先创建了一个结构体对象,查询的结果直接赋予了该对象

输出:

{1 zhangsan 18 humble}

等于:

 SELECT * FROM `student` WHERE age = 18 LIMIT 1

注意这个limit 1,我这里有两个age = 18的,但是take只取一个,一般是第一个

6.3 First

根据主键正序排序后,查询的第一条数据

s1 := Student{}
db.Where("age = 18").First(&s1)
fmt.Println(s1)

输出:

{1 zhangsan 18 humble}

等于:

SELECT * FROM `student` WHERE age = 18 ORDER BY `student`.`id` LIMIT 1
6.4 Last

根据主键倒序排序后,查询最后一条记录

s1 := Student{}
db.Where("age = 18").Last(&s1)
fmt.Println(s1)

输出:

{5 feixin 18 frantic}

等于:

 SELECT * FROM `student` WHERE age = 18 ORDER BY `student`.`id` DESC LIMIT 1
6.5 Find

这里就不是查询一条记录了,可以查询多条记录

这里因为多条记录,一个结构体对象咱也装不完,所以需要改成使用结构体切片(咱也不知道会查出多少记录)

还有一点就是find如果没有找到记录是不会报err的,但是take、first、last会,因为他们都要取走一条数据。

s1 := []Student{}
db.Where("age = 18").Find(&s1)
fmt.Println(s1)

输出:

[{1 zhangsan 18 humble} {5 feixin 18 frantic}]

等于:

SELECT * FROM `student` WHERE age = 18

再举一个使用where args的例子

s1 := []Student{}
db.Where("id in (?)", []int{1, 2, 3, 4}).Find(&s1)
fmt.Println(s1)

输出:

[{1 zhangsan 18 humble} {2 lisi 19 modest} {3 wangwu 20 generous} {4 xuliu 21 liberal}]

等于:

SELECT * FROM `student` WHERE id in (1,2,3,4)
6.6 Pluck

查询一列的值

这个函数返回切片类型,同时需要一个模型Model,就是我们之前定义的结构体模型

col := []string{}
db.Model(&Student{}).Pluck("ExternalCharacter", &col)
for _, i := range col {
    fmt.Println(i)
}

输出:

humble
modest
generous
liberal
frantic

等于:

SELECT `externalCharacter` FROM `student`

关于这个model其实之前的所有操作都可以去写成这个model形式,不过可以省略,在这里就不行了因为你这里没有任何有关student结构体的信息,它甚至都不知道你是哪一个表,那肯定就查找不了咯,所以这里要写这个model。

比如之前的db.Where("age = 18").Find(&s1) 也可以写成

db.Model(&Student{}).Where("age = 18").Find(&s1)

然后这里如果不使用字符串切片,使用结构体student切片的话,就可以不使用model,每一个student对象中没在查找范围的值会变成默认值,也就是int 变 0, string 变 空值。

例如:

col := []Student{}
db.Pluck("ExternalCharacter", &col)
fmt.Println(col)

输出:

[{0  0 humble} {0  0 modest} {0  0 generous} {0  0 liberal} {0  0 frantic}]

等于:

 SELECT `ExternalCharacter` FROM `student`

所以,model的作用 ok 了吗 😝

6.7 Select

Pluck是 一列,那如果我需要多列信息呢?select来帮忙

例如,我想要查询 nameage 两列

result := []Student{}
err = db.Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

之的代码没有加err,汗,懒得加了,反正err是这样加😉

输出:

zhangsan 18
lisi 19
wangwu 20
xuliu 21
feixin 18

等于:

SELECT `age`,`name` FROM `student`

select 中也可以使用mysql中的聚集函数

avg 为例:

var averAge float32
err = db.Model(&Student{}).Select("avg(age)").Pluck("age", &averAge).Error
if err != nil{
    panic("查询失败, error = " + err.Error())
}
fmt.Println(averAge)

输出:

19.2

等于:

SELECT avg(age) FROM `student`

使用方法和mysql中差不多,具体看官方文档哈

6.8 Order

看单词儿也大概知道了,是排序用的,可以将搜索结果进行排序

例如我按年龄排序:

result := []Student{}
err = db.Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

这其实就是之前那个加了一个order

输出:

zhangsan 18
feixin 18
lisi 19
wangwu 20
xuliu 21

等于:

 SELECT `age`,`name` FROM `student` ORDER BY age asc
6.9 Limit

可以限制查找条数,比如上面这个,如果想要只找出前三条的话,可以这样写:

result := []Student{}
err = db.Limit(3).Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

输出:

zhangsan 18
feixin 18
lisi 19

等于:

SELECT `age`,`name` FROM `student` ORDER BY age asc LIMIT 3
6.10 Offset

这个可以过滤掉前几个,还是以上面的为例,如果我想不要zhangsan 和 feixin,取后面三个,那可以这样写:

result := []Student{}
err = db.Offset(2).Limit(3).Order("age asc").Select("Age", "Name").Find(&result).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
for _, i := range result {
    fmt.Println(i.Name, i.Age)
}

输出:

lisi 19
wangwu 20
xuliu 21

等于:

SELECT `age`,`name` FROM `student` ORDER BY age asc LIMIT 3 OFFSET 2

有没有一种反复套娃的赶脚,是这样的没错。🤔

6.11 Count

这个就是返回查询相对应了多少条行数,比如我的表里有两个18岁的小伙子,查查他们的

var count int64
err = db.Model(&Student{}).Where("age = 18").Count(&count).Error
// 这里记得where要写在count前面
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(count)

输出:

2

等于:

SELECT count(*) FROM `student` WHERE age = 18
6.12 Group 与 Having

GroupHaving 一般联合使用,和sql中也是一样的,因为where 没法对聚合函数进行操作

在 GORM 中,使用 Group 函数时,通常需要搭配 Select 函数一起使用,以指定你希望在分组操作中选择的字段。这是因为在 SQL 查询中,使用 GROUP BY 进行分组时,通常需要明确指定分组后所需的字段。

所以Group, Having , Select 三者一般一起使用

简而言之,就是Select 用了聚集函数,然后Group 分组,再用Having 进行筛选

比如我现在要对我的表中的各个 age 的人数进行统计,首先我的表中18的有两人,19,20,21的各一人

type Result struct {
    Age     int
    Numbers int
}
var results []Result
err = db.Model(&Student{}).Select("age, count(*) as numbers").
    Group("age").Having("numbers > 0").Find(&results).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(results)

这里我是定义了一个Result 结构体存储结果, count(*) as numbers 是计算这一列的数目,并取一个别名为numbers, 这里所有这些都要用引号括起来,因为实际上他就是要转化为sql语句(可以仔细对照一下 下文中的等于啥sql语句)。最后老方法用Find 取结果并赋值给了results, 注意这里赋值也是映射赋值,你结构体里面的字段名一定要大写

输出:

[{18 2} {19 1} {20 1} {21 1}]

等于:

SELECT age, count(*) as numbers FROM `student` GROUP BY `age` HAVING numbers > 0
6.13 原生sql语句

就是上面的例子,如果使用sql语句的话,也可以这样写

type Result struct {
    Age     int
    Numbers int
}
var results []Result
sql := " SELECT age, count(*) as numbers FROM `student` GROUP BY `age` HAVING numbers > 0"
err = db.Raw(sql).Find(&results).Error // 这个Find 应该要换成 Scan
if err != nil {
    panic("查询失败, error = " + err.Error())
}
fmt.Println(results)

之前在中 使用的 Exec 方法是只返回语句执行情况,不会返回结果集,这里需要查询结果,我们改用Raw

结果是一样的,就不表了,这里说一下 FindScan

  1. Find 方法: Find 方法用于从数据库中查询数据并将结果映射到指定的结构体切片中。它适用于正常的 GORM 查询操作,根据条件从数据库中检索数据并进行映射。
  2. Scan 方法: Scan 方法用于将结果扫描到指定的结构体切片中,而不需要进行查询操作。它通常用于执行原生 SQL 查询,并将查询结果映射到结构体中。

意思就是FindScan差不多,但是这里我在使用原生sql语句,所以应该使用Scan

7. 删

删就很简单了,我们可以先查出来再删,也可以根据主键删除

还是这张表

在这里插入图片描述

现在我要把这个frantic的家伙给删除

可以先找到他

s := Student{}
err = db.Where("externalCharacter = 'frantic'").Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Delete(s).Error
if err != nil {
    panic("删除失败, error = " + err.Error())
}

如果知道主键的话也可以直接

err = db.Delete(&Student{}, 5).Error
if err != nil {
    panic("删除失败, error = " + err.Error())
}

等于:

DELETE FROM `student` WHERE `student`.`id` = 5

8. 改

还是这张表

在这里插入图片描述

8.1 Save

我们可以直接对结构体的字段进行修改然后进行save 操作映射到mysql中同步更改

比如我这里把 feixin 的 age 改为 19

s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
s.Age = 19
err = db.Save(&s).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

UPDATE `student` SET `name`='feixin',`age`=19,`externalCharacter`='frantic' WHERE `id` = 5
8.2 Update

上面的也可以这样写

s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Model(&s).Update("age", 19).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

UPDATE `student` SET `age`=19 WHERE `id` = 5
8.3 Updates

可以更新多列值

s := Student{}
err = db.Where("id = ?", 5).Find(&s).Error
if err != nil {
    panic("查询失败, error = " + err.Error())
}
err = db.Model(&s).Updates(Student{
    Name: "feixin",
    Age:  18,
}).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

UPDATE `student` SET `name`='feixin',`age`=18 WHERE `id` = 5
8.4 表达式

比如我把所有人的年龄都加1

err = db.Model(&Student{}).Where("age > ?", 0).Update("age", gorm.Expr("age + ?", 1)).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

UPDATE `student` SET `age`=age + 1 WHERE age > 0

在 GORM 中,使用 Update 方法时,需要提供一个条件来指定要更新哪些记录。所以这里需要写where 语句,不然会报err,之前的没有没有使用是因为Model 里填的是结构体对象,实际上就算已经指定了条件。、

当然,如果不想使用where的话也是有办法的,我们可以启用 AllowGlobalUpdate 模式

err = db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&Student{}).Update("age", gorm.Expr("age + ?", 1)).Error
if err != nil {
    panic("更新失败, error = " + err.Error())
}

等于:

UPDATE `student` SET `age`=age + 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EndNote21 | 账户同步问题

问题:无法同步,提示如下图所示。 原因:网络问题。 解决方法:国内网络无法实现同步,解决上网问题即可。

【数据结构】结构实现:顺序存储模式实现堆的相关操作

🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:数据结构 🔥该文章着重讲解了使用顺序结构实现堆的插入和删除等操作。 目录: 🌍二叉树的顺序结构&#x…

什么触控笔好用又便宜?ipad2022手写笔推荐

随着无纸化的广泛使用,和Apple pencil的出现,电容笔逐渐成为生产力的主要部分,像中性笔一样的电容笔,它不止具有小巧的身材,续航和功能都很在线,无论是在学习上还是工作上,电容笔逐渐成为人们缺…

Jenkis 配置钉钉通知

1、安装插件Ding Talk 2、钉钉上的配置 打开钉钉创建机器人,勾选加签,后面jenkins要用到 2.1 webhook -jenins界面要配置的地址:https://。。。。。。 2.2 jenkins 界面的加密地址 3、jenkins界面上的配置 在系统管理中找到安装好的插件&a…

部署docker image到AWS

1. 在AWS里创建ECR 仓库 2. 进入自己的Repository. 然后点击View push commands 3. 记一下这些命令 4. 登陆AWS cloud9 , 利用git clone 你的代码 5. 依次执行步骤3里的所有命令。最后你发现你的ECR里有了一个docker镜像 aws ecr-public get-login-password --region us-eas…

(CVPR-2023)InternImage:利用可变形卷积探索大规模视觉基础模型

InternImage:利用可变形卷积探索大规模视觉基础模型 Title:InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions paper是上海AI Lab发表在CVPR 2023的工作 paper链接 Abstract 与近年来大规模视觉变换器&#x…

【Vue】入门及生命周期(前后端分离)

目录 一、Vue简介 1、Vue.js是什么 2、库和框架的区别 2.1 库(Library) 2.2 框架(Framework) 3、MVVM的介绍 二、Vue入门 1、Vue快速入门 2、Vue的优势 三、Vue事件 四、Vue生命周期 1、实例 一、Vue简介 1、Vue.js是什么 Vue是一款流行的构建用户界面(UI)的[渐进式…

vue3-admin-element安装

1、直接点此链接进入下载 https://gitee.com/hu-snail/vue3-admin-element-template 2、下载完毕之后是个压缩包,解压 然后直接可以运行到vscode或HBuilder中 3、下载依赖:我使用的npm下载 npm i npm i serve启动 运行:npm run serve …

【力扣】三角形最小路径和

目录 题目 例子 示例 1: 示例 2: 前言 思路 思想 代码 调用的函数 主函数 所有代码 力扣提交的代码 运行结果 小结 题目 给定一个三角形 triangle ,找出自顶向下的最小路径和。 每一步只能移动到下一行中相邻的结点上。相邻的结…

分布式事务基础理论

基础概念 什么是事务 什么是事务?举个生活中的例子:你去小卖铺买东西,“一手交钱,一手交货”就是一个事务的例子,交钱和交货必 须全部成功,事务才算成功,任一个活动失败,事务将撤销…

BUU 加固题 AWDP Fix 持续更新中

BUU Ezsql 首先连接上ssh。输入账号密码。 到/var/www/html目录下&#xff0c;源码在里面。 主要是看index.php文件。 <?php error_reporting(0); include dbConnect.php; $username $_GET[username]; $password $_GET[password]; if (isset($_GET[username]) &&am…

shared library

原文、 shared library compatible vs incompatible compatible library 函数的工作场景没有变化 所有的函数对全局变量和返回参数产生相同的影响所有的函数继续返回相同的结果值提升性能 fix bugs 没有api 被删除可以有新的api加入 export 的结构体没有变化 违反以上各条的…

c++八股day2-虚函数表和虚函数表指针的创建时机

虚函数表和虚函数表指针的创建时机。 背景&#xff1a;用来实现多态&#xff08;包括静态多态和动态多态&#xff09;&#xff0c;多态的原理就是虚函数表和虚函数表指针 虚函数表的创建时机&#xff1a; a.什么时候生成的&#xff1f;编译器编译的时候声生成的&#xff0c;…

筑梦未来,与“EYE”同行——蔡司镜片X肇庆爱尔眼科医院走进石群小学

不知不觉&#xff0c;蔡司光学这项传递护眼理念、营造公益氛围的助童活动已步入第十个年头。从一开始的“孤军奋战”&#xff0c;到现如今拥有汇聚眼视光领域同行的社会号召力&#xff0c;品牌为青少年近视管理方案绘就了清晰的发展蓝图&#xff0c;旨在用蔡司镜片帮助广大青少…

Java(运算符+循环)万字超详细介绍 (囊括了按位,异或,for,while等基础和疑难知识)

【本节目标1】熟练掌握运算符 【本章目标2】熟练掌握循环 万字讲解&#xff0c;十分详细&#xff0c;有大量&#xff08;简单&#xff09;代码帮助理解和大量的&#xff08;简单&#xff09;举例与总结。 1.运算符 1.什么是运算符 计算机最基本的用途之一就是执行数学运算…

shopee——排序模型AUC还能涨吗?

文章目录 CBMRMultiCBMRSample Weight Assignment多任务推荐模型 CBMR MultiCBMR Sample Weight Assignment Click-aware Structure Transfer with Sample Weight Assignment for Post-Click Conversion Rate Estimation 每个用户的top-k 邻居每个商品的top-k 邻居平滑处理并构…

Windows【工具 04】WinSW官网使用说明及实例分享(将exe和jar注册成服务)实现服务器重启后的服务自动重启

官方Github&#xff1b;官方下载地址。没有Git加速的话很难下载&#xff0c;分享一下发布日期为2023.01.29的当前最新稳定版v2.12.0网盘连接。 包含文件&#xff1a; WinSW-x64.exesample-minimal.xmlsample-allOptions.xml 链接&#xff1a;https://pan.baidu.com/s/1sN3hL5H…

02_elasticsearch 核心概念

02_elasticsearch 核心概念 1、lucene和elasticsearch的前世今生2、elasticsearch的核心概念 1、lucene和elasticsearch的前世今生 1、lucene和elasticsearch的前世今生 lucene&#xff1a;最先进、功能最强大的搜索库。但是直接基于lucene开发&#xff0c;非常复杂&#xff…

Idea安装webservice插件

打开Idea的settings菜单&#xff0c;选择Plugins&#xff0c;模糊搜索"Web Ser"&#xff0c;安装以下3个红框内插件&#xff1a; 安装好以上3个插件后&#xff0c;就可以根据需求生成webservice客户端或者webservice服务端了。

【C语言】进阶——指针

目录 ①(●◡●)前言 1.字符指针 ✌字符指针和数组笔试题 2.指针数组 和数组指针 &#x1f44a;指针数组 &#x1f44a;数组指针 &#x1f44a;&数组名和数组名 3.数组传参和指针传参 &#x1f44a;一维数组传参 &#x1f44a;二维数组传参 &#x1f44a;一级…