Go语言中的结构体
Go语言中提供了对struct的支持,struct,中文翻译称为结构体,与数组一样,属于复合类型,并非引用类型。
Go语言的struct,与C语言中的struct或其他面向对象编程语言中的类(class)类似,可以定义字段(属性)和方法,但也有很不同的地方,需要深入学习,才能区分他们之间的区别。
别名和类型
在前面我们说到声明一个函数我们可以用关键词func,那么声明一个结构,我们可以用关键词type
type name 类型 {}
我们也可以用这种语法给一个内置的类型起别名,例如下面的代码
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
// ...
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8
这是go的源码,可以看到rune实质上是int32类型的别名,byte实质上是uint8的别名
当然,我们也可以生命自己的别名比如
package main
import "fmt"
type double = float64
func main() {
var d1 double = double(3.1415926)
fmt.Printf("value:%v, type:%T\n", d1, d1)
}
使用type 自定义名称 = 类型的方式可以给类型起别名
然后我们来来看看这段代码
package main
import "fmt"
type double float64
func main() {
var d1 double = double(3.1415926)
fmt.Printf("value:%v, type:%T\n", d1, d1)
}
在这段代码中,我们就是声明了一个double类型

定义自己的结构体
刚才我们说了可以使用type关键字来起别名和定义类型,它也可以用于定义结构体
但是我们首先得明白什么是结构体,在知道这个之前,我们得明白Go语言它并非像Java、C#这样的语言一样是一门纯面向对象的语言,那么在这种函数式语言中,我们想要表示现实生活中某样事务的多维度属性,就要用到结构体这一引用数据类型。就比如,我们现在有一个用户,他有自己的昵称、用户名、密码、手机号、年龄和爱好、,我们就可以这样表示:
package main
import "fmt"
type user struct {
nickname string
username string
password string
mobile string
age int
hobby []string
}
func main() {
var farajmujey user
farajmujey.nickname = "努力做JPG程序员的FarajMujey"
farajmujey.username = "mujeyf@gmail.com"
farajmujey.password = "111111"
farajmujey.age = 21
farajmujey.hobby = append(farajmujey.hobby, "敲代码")
farajmujey.hobby = append(farajmujey.hobby, "听音乐")
farajmujey.hobby = append(farajmujey.hobby, "汽车")
fmt.Printf("name: %s, all: %v, type: %T\n", farajmujey.nickname, farajmujey, farajmujey)
}

匿名结构体
和函数一样,结构体也可以声明成匿名的,但是这种匿名结构体只能声明在函数内部,并不能放在全局作用域中:
// 匿名结构体
var faraj struct {
name string
age int
}
faraj.name = "FarajMujey"
faraj.age = 21
fmt.Printf("value: %v, type: %T\n", faraj, faraj)

结构体初始化
上面我们通过var farajmujey user的形式初始化过结构体
初次之外,我们还可以
kv初始化
u2 := user{
name: "FarajMujey",
age: 20,
}
通过key-value的形式初始化,中间用,分开,注意🎋最后一个,也不能省略。这种初始化的形式有点类似于JavaScript声明对象
值列表初始化
u3 := user{"Faraj", 20}
通过值列表初始化是最简单的初始化步骤,有点像Java、C#实例化对象的步骤,注意🎄值的顺序要严格按照结构体字段声明的顺序来
方法操作结构体
首先我们要明确一个概念:在Go语言中方法传递参数的方式永远是复制
既然方法会复制我们传递的参数,那么我们通过方法修改一个结构体中的内容的时候就需要注意了,来看一段错误的代码:
func f(user user) {
user.name = "Mujey"
}
func main() {
var user user
user.age = 18
user.name = "Faraj"
f(user)
fmt.Println(user)
}

当我们以为我们修改了user结构体的name属性时,其实它并没有修改,就像上面所说的,我们修改的是user的副本,和实际user没有任何的关系
想要修改真正的user,就需要通过指针(pointer)来传递对象的地址值
func f(user *user) {
user.name = "Mujey"
}
func main() {
var user user
user.age = 18
user.name = "Faraj"
f(&user)
fmt.Println(user)
}
有没有发现上面的代码有个小问题,user不是个指针吗,为什么可以调用name属性,其实这是Go语言自带的一个语法糖。它会自动检查这个user是不是指针,如果是指针类型的话,会自动转换成该指针对应的类型进行操作,也就是(*user).name。但是需要注意,这个语法糖只对结构体有效,基础类型还是需要自己加*取值的!

可以通过new函数来创建一个指定结构体类型的指针
结构体内存布局
一个结构体内部的字段的内存都是连续的

但是不同的数据类型所占的内存字节大小是不同的,int8占一个字节所以的步长是1,但是其他的数据类型就涉及到一个内存对齐的机制,但是他们都是连续的
构造函数
Go语言是一种面向接口的函数,它没有内置的构造函数,需要完成类面向对象编程的特性,需要自己写构造函数返回一个结构体
package main
import "fmt"
type user struct {
username string
password string
}
type student struct {
id int
name string
specialty string
score int
awards []string
}
func newUser(username, password string) user {
return user{
username: username,
password: password,
}
}
func newStudent(id int, name, specialty string, score int, awards []string) *student {
return &student{id, name, specialty, score, awards}
}
func main() {
u1 := newUser("FarajMujey", "111111")
fmt.Printf("%v\n", u1)
stu1 := *newStudent(1, "Mujey", "E-commerce", 100, nil)
fmt.Printf("%v\n", stu1)
}

需要注意的是,当结构体比较大的时候,也就是字段较多的时候建议使用返回值为结构体类型的指针,这样可以减少不必要的内存开销。
方法
在Go语言中的方法时作用于特别类型的函数
func (接收者) 函数名(参数) 返回值类型{}
方法接收者的类型名一般都用结构体的第一个字母小写
package main
import "fmt"
type student struct {
name string
}
// student的构造方法
func newStudent(name string) student {
return student{name}
}
// 用于student结构体的方法
func (s student) sayHi() {
fmt.Printf("%s say Hi!\n", s.name)
}
func main() {
// 构建一个student类型的结构体
stu := newStudent("Faraj")
stu.sayHi()
}
和其他的函数一样,方法的接收者也是按值传递的,也就是拷贝一份新的,所以在方法体内修改的结构体,方法体外的结构体将不受改变,所以涉及到修改值得操作,就需要用到指针了。
package main
import "fmt"
type student struct {
name string
age int
}
// student的构造方法
func newStudent(name string, age int) student {
return student{name, age}
}
// 操作结构体内部字段的方法
func (s *student) happyNewYear() {
s.age++
}
func main() {
// 构建一个student类型的结构体
stu := newStudent("Faraj", 20)
//(&stu).happyNewYear()
stu.happyNewYear()
fmt.Println(stu)
}

什么时候使用指针接收者
- 需要修改接收者中的值
- 接收者拷贝代价比较大的结构体
- 保证一致性,如果有某一个方法使用了指针接收者,那么其他的方法最好也都使用指针接收者
使用自定义类型可以定义方法
只能为自己包中的结构体定义方法,如果我们想定义int类型为接收者的方法,这时候就可以定义一个自定义的int类型的结构体
package main
import "fmt"
type d int
func (d d) value() string {
return fmt.Sprintf("类型是%T,值是%v\n", d, d)
}
func main() {
aInt := d(10)
dStr := aInt.value()
fmt.Printf("%v\n", dStr)
}
小Demo
函数版学生管理系统
能够列表显示,新增,删除,修改学生
package main
import (
"fmt"
"os"
)
/*
函数版学生管理系统
!能够列表显示,新增,删除,修改学生
*/
var students []student
type student struct {
id int
name string
score int
}
func listStudent() {
if len(students) == 0 {
fmt.Println("暂无学生")
} else {
fmt.Printf("%-4s\t%-10s\t%s\n", "id", "姓名", "分数")
for _, v := range students {
fmt.Printf("%-4d\t%-10s\t%d\n", v.id, v.name, v.score)
}
}
}
func newStudent(id int, name string, score int) *student {
return &student{id, name, score}
}
func addStudent(name string, score int) {
var id int
if len(students) > 0 {
id = students[len(students)-1].id + 1
} else {
id = 1
}
obj := *newStudent(id, name, score)
students = append(students, obj)
}
func deleteStudent(id int) {
var index int
for i := 0; i < len(students); i++ {
if id == students[i].id {
index = i
}
}
students = append(students[:index], students[index+1:]...)
}
func updateStudentScore(id, newScore int) {
for i := 0; i < len(students); i++ {
if id == students[i].id {
students[i].score = newScore
}
}
}
func console() {
for {
fmt.Println("1.查看所有学生\t2.新增一个学生\t3.删除一个学生\t4.修改学生分数\t5.退出")
var key int
fmt.Scanln(&key)
switch key {
case 1:
listStudent()
case 2:
var (
name string
score int
)
fmt.Println("请输入学生名称")
fmt.Scanln(&name)
fmt.Println("请输入学生分数")
fmt.Scanln(&score)
addStudent(name, score)
case 3:
var id int
fmt.Scanln(&id)
deleteStudent(id)
case 4:
var (
id, newScore int
)
fmt.Println("请输入学生id")
fmt.Scanln(&id)
fmt.Println("请输入学生新分数")
fmt.Scanln(&newScore)
updateStudentScore(id, newScore)
case 5:
os.Exit(0)
default:
fmt.Println("???")
}
}
}
func main() {
console()
}
匿名结构体
匿名结构体就是结构体中的字段没有名字,适合字段比较少的结构体使用,但是不推荐且不常用,了解即可
调用匿名结构体中的字段时,用结构体.字段类型的方式调用,每个类型只能有一个字段
type user struct {
name string
age string
}
func main() {
u := user{"ABC", 1}
fmt.Println(u.string)
}
嵌套结构体
在一个结构体中,可以声明另一个结构体类型的字段:
package main
import "fmt"
type address struct {
country string
province string
city string
region string
}
type company struct {
id int64
name string
addr address
}
func main() {
obj := company{
id: 1,
name: "上海极梦",
addr: address{
country: "中国",
province: "上海",
city: "上海",
region: "浦东新区",
},
}
fmt.Printf("region=%s\n", obj.addr.region)
}
匿名嵌套结构体
如果结构体中的嵌套结构体中的字段没有重复的话,可以使用匿名嵌套结构体的方式来简写,在调用它的时候直接可以用结构体.出匿名嵌套结构体中的字段
type company struct {
id int64
name string
address // 匿名嵌套结构体
}
func main() {
obj := company{
id: 1,
name: "上海极梦",
address: address{
country: "中国",
province: "上海",
city: "上海",
region: "浦东新区",
},
}
fmt.Printf("region=%s\n", obj.region) // obj.region == obj.address.region
}
Go语言中的模拟继承
Go语言是没有继承这一概念的,但是可以模拟其他面向对象语言完成继承
package main
import "fmt"
type animal struct {
name string
age int
}
type dog struct {
feet int
animal
}
func (a animal) move() {
fmt.Printf("%s can move\n", a.name)
}
func (d dog) shut() {
fmt.Printf("%s can shut: wolf~\n", d.name)
}
func main() {
d := dog{
animal: animal{
name: "Cola",
age: 1,
},
feet: 4,
}
d.move()
d.shut()
}

结构体和json转换
在前后端分离项目中,数据通常是用json传递的,在go语言的标准库中包含有结构体和json互相转换的包
结构体转换成json
使用json.Marshal()函数序列化结构体为字符串
package main
import (
"encoding/json"
"fmt"
)
type user struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
user := user{"Faraj", 18}
b, err := json.Marshal(user)
if err != nil {
fmt.Printf("JSON Marshal err %v\n", err)
return
}
fmt.Printf("%v\n", string(b))
}

{%span red, 注意🧨:在声明一个结构体的时候字段要大写,这样在encoding包下才能访问到该字段,如果想让序列化出的json小写,需要加上 %} {% inlineimage https://faraj-picgo.oss-cn-shanghai.aliyuncs.com/20200821160439.png %}
json解析成结构体
使用json.Unmarshal()函数反序列化(解析)json为结构体
package main
import (
"encoding/json"
"fmt"
)
type user struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
obj := user{"Faraj", 18}
b, err := json.Marshal(obj)
if err != nil {
fmt.Printf("JSON Marshal err %v\n", err)
return
}
str := string(b)
var userObj user
json.Unmarshal([]byte(str), &userObj)
fmt.Printf("%v\n", userObj)
}


浙公网安备 33010602011771号