当前位置: 首页 > news >正文

Go语言基础之结构体

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

类型别名和自定义类型

自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

//将MyInt定义为int类型
type MyInt int

通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性。

类型别名

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

type TypeAlias = Type

我们之前见过的rune和byte就是类型别名,他们的定义如下:

type byte = uint8
type rune = int32

类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

//类型定义
type NewInt int//类型别名
type MyInt = intfunc main() {var a NewIntvar b MyIntfmt.Printf("type of a:%T\n", a) //type of a:main.NewIntfmt.Printf("type of b:%T\n", b) //type of b:int
}

结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

结构体

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

Go语言中通过struct来实现面向对象。

结构体的定义

使用type和struct关键字来定义结构体,具体代码格式如下:

type 类型名 struct {字段名 字段类型字段名 字段类型…
}

其中:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

举个例子,我们定义一个Person(人)结构体,代码如下:

type person struct {name stringcity stringage  int8
}

同样类型的字段也可以写在一行,

type person1 struct {name, city stringage        int8
}

这样我们就拥有了一个person的自定义类型,它有name、city、age三个字段,分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。

语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型

结构体实例化

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

var 结构体实例 结构体类型

基本实例化

举个例子:

type person struct {name stringcity stringage  int8
}func main() {var p1 personp1.name = "沙河娜扎"p1.city = "北京"p1.age = 18fmt.Printf("p1=%v\n", p1)  //p1={沙河娜扎 北京 18}fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18}
}

我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。

匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

package mainimport ("fmt"
)func main() {var user struct{Name string; Age int}user.Name = "小王子"user.Age = 18fmt.Printf("%#v\n", user)
}

创建指针类型结构体

我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:

var p2 = new(person)
fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

从打印的结果中我们可以看出p2是一个结构体指针。

需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。

var p2 = new(person)
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}

取结构体的地址实例化

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

p3 := &person{}
fmt.Printf("%T\n", p3)     //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "七米"
p3.age = 30
p3.city = "成都"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"七米", city:"成都", age:30}

p3.name = "七米"其实在底层是(*p3).name = “七米”,这是Go语言帮我们实现的语法糖。

结构体初始化

没有初始化的结构体,其成员变量都是对应其类型的零值。

type person struct {name stringcity stringage  int8
}func main() {var p4 personfmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}

使用键值对初始化

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

p5 := person{name: "小王子",city: "北京",age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}

也可以对结构体指针进行键值对初始化,例如:

p6 := &person{name: "小王子",city: "北京",age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}

当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

p7 := &person{city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

使用值的列表初始化

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:

p8 := &person{"沙河娜扎","北京",28,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"沙河娜扎", city:"北京", age:28}

使用这种格式初始化时,需要注意:

  • 必须初始化结构体的所有字段。
  • 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  • 该方式不能和键值初始化方式混用。

结构体内存布局

结构体占用一块连续的内存。

type test struct {a int8b int8c int8d int8
}
n := test{1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)

输出:

n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063

空结构体

空结构体是不占用空间的。

var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0

构造函数

Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。

func newPerson(name, city string, age int8) *person {return &person{name: name,city: city,age:  age,}
}

调用构造函数

p9 := newPerson("张三", "沙河", 90)
fmt.Printf("%#v\n", p9) //&main.person{name:"张三", city:"沙河", age:90}

方法和接收者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体
}

其中,

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为
    p,Connector类型的接收者变量应该命名为c等。
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。

举个例子:

//Person 结构体
type Person struct {name stringage  int8
}//NewPerson 构造函数
func NewPerson(name string, age int8) *Person {return &Person{name: name,age:  age,}
}//Dream Person做梦的方法
func (p Person) Dream() {fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}func main() {p1 := NewPerson("小王子", 25)p1.Dream()
}

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。

// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {p.age = newAge
}

调用该方法:

func main() {p1 := NewPerson("小王子", 25)fmt.Println(p1.age) // 25p1.SetAge(30)fmt.Println(p1.age) // 30
}

值类型的接收者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {p.age = newAge
}func main() {p1 := NewPerson("小王子", 25)p1.Dream()fmt.Println(p1.age) // 25p1.SetAge2(30) // (*p1).SetAge2(30)fmt.Println(p1.age) // 25
}

什么时候应该使用指针类型接收者

  • 需要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
  • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

//MyInt 将int定义为自定义MyInt类型
type MyInt int//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {fmt.Println("Hello, 我是一个int。")
}
func main() {var m1 MyIntm1.SayHello() //Hello, 我是一个int。m1 = 100fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
}

注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

//Person 结构体Person类型
type Person struct {stringint
}func main() {p1 := Person{"小王子",18,}fmt.Printf("%#v\n", p1)        //main.Person{string:"北京", int:18}fmt.Println(p1.string, p1.int) //北京 18
}

注意:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针,就像下面的示例代码那样。

//Address 地址结构体
type Address struct {Province stringCity     string
}//User 用户结构体
type User struct {Name    stringGender  stringAddress Address
}func main() {user1 := User{Name:   "小王子",Gender: "男",Address: Address{Province: "山东",City:     "威海",},}fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}

嵌套匿名字段

上面user结构体中嵌套的Address结构体也可以采用匿名字段的方式,例如:

//Address 地址结构体
type Address struct {Province stringCity     string
}//User 用户结构体
type User struct {Name    stringGender  stringAddress //匿名字段
}func main() {var user2 Useruser2.Name = "小王子"user2.Gender = "男"user2.Address.Province = "山东"    // 匿名字段默认使用类型名作为字段名user2.City = "威海"                // 匿名字段可以省略fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}

当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找。

嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。

//Address 地址结构体
type Address struct {Province   stringCity       stringCreateTime string
}//Email 邮箱结构体
type Email struct {Account    stringCreateTime string
}//User 用户结构体
type User struct {Name   stringGender stringAddressEmail
}func main() {var user3 Useruser3.Name = "沙河娜扎"user3.Gender = "男"// user3.CreateTime = "2019" //ambiguous selector user3.CreateTimeuser3.Address.CreateTime = "2000" //指定Address结构体中的CreateTimeuser3.Email.CreateTime = "2000"   //指定Email结构体中的CreateTime
}

结构体的“继承”

Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

//Animal 动物
type Animal struct {name string
}func (a *Animal) move() {fmt.Printf("%s会动!\n", a.name)
}//Dog 狗
type Dog struct {Feet    int8*Animal //通过嵌套匿名结构体实现继承
}func (d *Dog) wang() {fmt.Printf("%s会汪汪汪~\n", d.name)
}func main() {d1 := &Dog{Feet: 4,Animal: &Animal{ //注意嵌套的是结构体指针name: "乐乐",},}d1.wang() //乐乐会汪汪汪~d1.move() //乐乐会动!
}

结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

结构体与JSON序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

//Student 学生
type Student struct {ID     intGender stringName   string
}//Class 班级
type Class struct {Title    stringStudents []*Student
}func main() {c := &Class{Title:    "101",Students: make([]*Student, 0, 200),}for i := 0; i < 10; i++ {stu := &Student{Name:   fmt.Sprintf("stu%02d", i),Gender: "男",ID:     i,}c.Students = append(c.Students, stu)}//JSON序列化:结构体-->JSON格式的字符串data, err := json.Marshal(c)if err != nil {fmt.Println("json marshal failed")return}fmt.Printf("json:%s\n", data)//JSON反序列化:JSON格式的字符串-->结构体str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`c1 := &Class{}err = json.Unmarshal([]byte(str), c1)if err != nil {fmt.Println("json unmarshal failed!")return}fmt.Printf("%#v\n", c1)
}

结构体标签(Tag)

Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

`key1:"value1" key2:"value2"`

结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

例如我们为Student结构体的每个字段定义json序列化时使用的Tag:

//Student 学生
type Student struct {ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的keyGender string //json序列化是默认使用字段名作为keyname   string //私有不能被json包访问
}func main() {s1 := Student{ID:     1,Gender: "男",name:   "沙河娜扎",}data, err := json.Marshal(s1)if err != nil {fmt.Println("json marshal failed!")return}fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}

结构体和方法补充知识点

因为slice和map这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。我们来看下面的例子:

type Person struct {name   stringage    int8dreams []string
}func (p *Person) SetDreams(dreams []string) {p.dreams = dreams
}func main() {p1 := Person{name: "小王子", age: 18}data := []string{"吃饭", "睡觉", "打豆豆"}p1.SetDreams(data)// 你真的想要修改 p1.dreams 吗?data[1] = "不睡觉"fmt.Println(p1.dreams)  // ?
}

正确的做法是在方法中使用传入的slice的拷贝进行结构体赋值。

func (p *Person) SetDreams(dreams []string) {p.dreams = make([]string, len(dreams))copy(p.dreams, dreams)
}

同样的问题也存在于返回值slice和map的情况,在实际编码过程中一定要注意这个问题。

相关文章:

Go语言基础之结构体

Go语言中没有“类”的概念&#xff0c;也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。 类型别名和自定义类型 自定义类型 在Go语言中有一些基本的数据类型&#xff0c;如string、整型、浮点型、布尔等数据…...

前端食堂技术周刊第 96 期:2023 CSS 状态、Nuxt 3.7、TypeScript 5.2、eBay 性能优化、贝塞尔曲线

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;冰镇黑乌龙 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看…...

一文总结Redis知识点

目录 为什么基于MySQL又出现Redis&#xff1f;Redis的优点&#xff1f;Redis支持的基本命令Redis支持的数据结构1 String2 List3 Set4 Sorted Set5 Hash6 Stream 消息队列7 Geospatial 地理空间8 Bitmap 位图9 Bitfield 位域10 HyperLogLog Redis是单线程还是多线程&#xff1f…...

ARM寄存器组

CM3 拥有通用寄存器 R0‐R15 以及一些特殊功能寄存器。 R0-R7&#xff0c;通用目的寄存器 R0-R7也被称为低组寄存器&#xff0c;所有指令可以访问它们&#xff0c;它们的字长为32位&#xff0c;复位后的初始值是不可预料的。 R8-R12&#xff0c;通用目的寄存器 R8-R12也被称…...

Windows查看当前文件夹下的所有.c文件的个数

在Windows的命令提示符&#xff08;CMD&#xff09;中&#xff0c;你可以使用for循环和dir命令结合起来&#xff0c;以计算当前文件夹下所有 .c 文件的个数。 下面是一个简单的示例&#xff0c;这个批处理脚本会计算当前目录下所有 .c 文件的个数&#xff1a; echo off setlo…...

ubuntu Qt 地图离线调用

ubuntu环境下在Qt上调用百度地图_ubuntu 百度地图_拿到金像奖上课那家店的博客-CSDN博客 【Qt初入江湖】Qt QtWebEngineWidgets 底层架构、原理详细描述_鱼弦的博客-CSDN博客 Ubuntu20.04 QT无法用Qwebengine控件的解决方案&#xff08;临时&#xff09;_cmsyq的博客-CSDN博客…...

Android Studio升级到Android API 33版本后,XML布局输入没有提示

低版本的Android Studio升级到Android API 33版本后&#xff0c;XML布局输入没有提示。查一下我目前使用的Android Studio 是2021年发布&#xff0c;而Android API 33是2022年发布的&#xff0c;这是由低版本升级到高版本造成不兼容的问题。解决方法有两种&#xff1a; 第一种…...

操作XML(带命名空间)

之前文章讲述了使用c# xpath如何操作xml文件&#xff0c;在实际开发项目中&#xff0c;遇到的很多xml文件都是带有命名空间的&#xff0c;如果还是用之前的代码获取&#xff0c;那将获取到null。 本文讲解操作代码有命名空间的Xml文件&#xff0c;以及多个命名空间的xml。 <…...

二叉搜索树(C++)

二叉搜索树 概念二叉搜索树的应用二叉搜索树的实现K模型基本结构和函数声明接口实现①find——查找关键码②Insert——插入关键码③Erase——删除关键码&#xff08;重点&#xff09;时间复杂度 源码&#xff08;整体&#xff09;非递归递归 KV模型 在使用C语言写数据结构阶段时…...

软件架构知识点

常用软件架构模型分类&#xff08;5种&#xff09; 软件架构建模方法&#xff08;模型4种&#xff09; 架构师分类&#xff08;微软4种&#xff09; 系统架构设计师的角色特质&#xff08;6种&#xff09; 计算机系统组成图谱 嵌入式操作系统的特点&#xff08;5个&#x…...

C语言日常刷题6

文章目录 题目答案与解析1234567 题目 1、以下对C语言函数的有关描述中&#xff0c;正确的有【多选】&#xff08; &#xff09; A: 在C语言中&#xff0c;一个函数一般由两个部分组成&#xff0c;它们是函数首部和函数体 B: 函数的实参和形参可以是相同的名字 C: 在main()中定…...

微信小程序使用stomp.js实现STOMP传输协议的实时聊天

简介&#xff1a; uniapp开发的小程序中使用 本来使用websocket&#xff0c;后端同事使用了stomp协议&#xff0c;导致前端也需要对应修改。 如何使用 在static/js中新建stomp.js和websocket.js&#xff0c;然后在需要使用的页面引入监听代码发送代码即可 代码如下&#x…...

基于饥饿游戏算法优化的BP神经网络(预测应用) - 附代码

基于饥饿游戏算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于饥饿游戏算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.饥饿游戏优化BP神经网络2.1 BP神经网络参数设置2.2 饥饿游戏算法应用 4.测试结果&#xff1a;5…...

[ 云计算 | AWS ] Java 应用中使用 Amazon S3 进行存储桶和对象操作完全指南

文章目录 一、前言二、所需 Maven 依赖三、先决必要的几个条件信息四、创建客户端连接五、Amazon S3 存储桶操作5.1. 创建桶5.2. 列出桶 六、Amazon S3 对象操作6.1. 上传对象6.2. 列出对象6.3. 下载对象6.4. 复制、重命名和移动对象6.5. 删除对象6.6. 删除多个对象 七、文末总…...

【Spring Boot】Spring Boot 配置 Hikari 数据库连接池

文章目录 前言配置 前言 数据库连接池是一个提高程序与数据库的连接的优化&#xff0c;连接池它主要作用是提高性能、节省资源、控制连接数、连接管理等操作&#xff1b; 程序中的线程池与之同理&#xff0c;都是为了优化、提高性能。 配置 spring:datasource:hikari:# 设置是…...

MR混合现实石油化工课堂情景实训教学演示

MR&#xff08;混合现实&#xff09;技术是一种结合了虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;优势的新型技术&#xff0c;在教育领域具有广阔的应用前景。在石油化工课堂中&#xff0c;MR混合现实情景实训教学的应用可以大大提高学生的学习效…...

php thinkphp 抖音支付,订单同步接口分享

1. 抖音支付 需要获取抖音小程序的AppID,AppSecret,需要配置回调地址&#xff0c;Token获取SALT 官方地址&#xff1a;支付&#xff0c;订单同步 以下干货仅针对于有一定开发基础的精英&#xff0c;0基础的止步。 public function DouyinPay($openId,$id,$body 抖音担保支付…...

excel功能区(ribbonx)编程笔记--2 button控件与checkbox控件

我们上一章简单先了解了ribbonx的基本内容,以及使用举例实现自己修改ribbox的内容,本章紧接上一章,先讲解一下ribbonx的button控件。 在功能区的按钮中,可以使用内置图像或提供自已的图像,可以指定大按钮或者更小的形式,添加少量的代码甚至可以同时提供标签。此外,可以利…...

JavaScript 知识点

立即执行函数 代码(function () {// ... })();创建函数的同时立即执行&#xff0c;没有绑定任何事件&#xff0c;也无需等待任何异步操作function () {} 是一个匿名函数&#xff0c;包围它的一对括号将其转换为一个表达式&#xff0c;紧跟其后的一对括号调用了这个函数。立即执…...

深入理解 JVM 之——动手编译 JDK

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 本篇为深入理解 Java 虚拟机第一章的实战内容&#xff0c;推荐在学习前先掌握基础的 Linux 操作、编译原理基础以及扎实的 C/C 功底。 该系列的 GitHub 仓库&#xff1a;https://github.com/Doge2077/lear…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...