go语言struct
一、基本说明
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
二、结构体声明
type 结构体名称 struct{
field type
field type
}
例子:
type Dog struct {
Name string
Age int
Color string
}
注:在创建一个结构体变量后,如果没有给字段赋值,都应该对应一个零值(默认值),布尔类型为false,数值类型为0,字符串为"",指针、slice和map的零值时nil,即没有分配空间。
三、结构体变量和访问结构体字段
- 方式一:直接声明
var dog Dog
- 方式二:{}
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Color string
}
func main() {
dog := Dog{"来福", 3, "白色"}
fmt.Printf("dog=%v\n", dog)
}
输出结果:
dog={来福 3 白色}
- 方式三:new()
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Color string
}
func main() {
dog := new(Dog)
// dog是一个指针,因此标准的给字段赋值应该是(*dog).Name = "来福" (*dog).Age = 3 (*dog).Color = "白色"
// go设计者为了程序员使用方便,底层会对dog.Name = "来福"进行处理,会给dog加上取值运算(*dog).Name = "来福"
dog.Name = "来福" // 等价于(*dog).Name = "来福"
dog.Age = 3 // 等价于(*dog).Age = 3
dog.Color = "白色" // 等价于(*dog).Color = "白色"
fmt.Printf("dog=%v\n", *dog)
}
输出结果:
dog={来福 3 白色}
- 方式四:
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Color string
}
func main() {
dog := &Dog{"来福", 3, "白色"}
fmt.Printf("dog=%v\n", *dog)
}
输出结果:
dog={来福 3 白色}
- 方式五:创建struct实例指定字段值
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func main() {
person := Person{
Name: "Tom",
Age: 18,
}
personPor := &Person{
Name: "Jack",
Age: 20,
}
fmt.Println("person=", person)
fmt.Println("personPor=", *personPor)
}
输出结果:
person= {Tom 18}
personPor= {Jack 20}
说明:
- 第三种方式和第四种方式返回的是结构体指针
- 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*dog).Name = "来福"
- 但是go做了一个简化,也支持结构体指针.字段名,比如:dog.Name = "来福",更加符合程序员使用习惯,go编译器底层对dog.Name做了转化(*dog).Name
四、结构体和结构体变量的区别
- 结构体是自定义的数据类型,代表一类事物
- 结构体变量(实例)是具体的,实际的,代表一个具体变量
五、结构体在内存中的示意图
六、注意细节
- 结构体所有字段在内存中是连续分布的
package main
import (
"fmt"
)
type Point struct {
x, y int
}
type Rect struct {
LeftUp, RightDown Point
}
func main() {
rect := Rect{Point{10, 20}, Point{30, 40}}
fmt.Printf("rect.LeftUp.x地址=%v\n,rect.LeftUp.y地址=%v\n,rect.RightDown.x地址=%v\n,rect.RightDown.y地址=%v\n",
&rect.LeftUp.x, &rect.LeftUp.y, &rect.RightDown.x, &rect.RightDown.y)
}
输出结果:
rect.LeftUp.x地址=0xc0000124e0
,rect.LeftUp.y地址=0xc0000124e8
,rect.RightDown.x地址=0xc0000124f0
,rect.RightDown.y地址=0xc0000124f8
- 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数、顺序和类型)
package main
import (
"fmt"
)
type A struct {
Num int
Num2 int
}
type B struct {
Num int
Num2 int
}
func main() {
var a A
var b B
a = A(b) // 可以转换,但是有要求,就是结构体字段要完全一样(包括:名字,类型,个数,顺序)
fmt.Println("a=", a, "b=", b)
}
输出结果:
a= {0 0} b= {0 0}
- 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
package main
import (
"fmt"
)
type integer int
func main() {
var i integer
var j int
j = int(i)
fmt.Println("i=", i, "j=", j)
}
输出结果:
i= 0 j= 0
- struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
package main
import (
"encoding/json"
"fmt"
)
type Dog struct {
Name string `json:"name"` // `json:"name"`就是struct tag
Age int `json:"age"`
Color string `json:"color"`
}
func main() {
dog := Dog{"来福", 3, "白色"}
// 将dog变量序列化为json字符串
// json.Marshal函数中使用反射
jsonStr, err := json.Marshal(dog)
if err != nil {
fmt.Println("json转换错误")
}
fmt.Println("dog=", string(jsonStr))
}
输出结果:
dog= {"name":"来福","age":3,"color":"白色"}
七、方法
golang中的方法是作用在指定的数据类型上的(即和指定的数据类型绑定)因此自定义类型,都可以有方法,而不仅仅是struct。
1.方法的声明(定义)
func (recevier type) methodName(参数列表)(返回值列表){
方法体
return 返回值
}
- 参数列表,表示方法输入
- recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
- recevier type:可以是结构体,也可以说是自定义类型。
- recevier就是type类型的一个变量(实例)。
- 参数列表表示方法的输入
- 返回值列表,表示返回的值,可以是多个。
- 方法主体,表示为了实现某一功能代码块
- return语句不是必须的
例子:
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Color string
}
// 给Dog类型绑定一个方法
func (d Dog) Eat(food string) {
fmt.Printf("%v正在吃%v\n", d.Name, food)
}
func main() {
d := Dog{
Name: "小花",
Age: 3,
Color: "花色",
}
// 调用方法
d.Eat("骨头")
}
输出结果:
小花正在吃骨头
总结:
- Eat方法和Dog类型绑定
- Eat方法只能通过Dog类型来调用,既不能直接调用,也不能通过其他类型来调用。
- func (d Dog) Eat(food string) {...},方法的调用和传参机制和函数基本一样,不一样的地方是方法的调用时,会将调用方法的变量,当做实参也传递给方法。
2. 方法的注意事项和细节讨论
- 结构体是值类型,在方法的调用中遵守值类型的传递机制,是值拷贝传递方式
- 若程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Color string
}
// 给Dog类型绑定一个方法
func (d *Dog) test() {
d.Name = "旺财"
}
func main() {
d := Dog{
Name: "小花",
Age: 3,
Color: "花色",
}
// 编译器底层做了优化,(&d).test()等价d.test()
// 因为编译器会自动加上&
// (&d).test()
d.test()
fmt.Println("d=", d)
}
输出结果:
d= {旺财 3 花色}
- Golang中的方法是作用在数据类型上(即和指定的数据类型绑定),因此自定义数据类型,都可以有方法。而不仅仅是struct。
package main
import (
"fmt"
)
type interge int
func (i interge) change() {
fmt.Println("i=", i)
}
func main() {
var i interge = 2
i.change()
}
输出结果:
i= 2
-
方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法的首字母大写,可以在本包和其他包访问
-
如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
str := fmt.Sprintf("Name=[%v],Age=[%v]", p.Name, p.Age)
return str
}
func main() {
person := Person{
Name: "Tom",
Age: 18,
}
fmt.Println("person=", person)
fmt.Println("&person=", &person)
}
输出结果:
person= Name=[Tom],Age=[18]
&person= Name=[Tom],Age=[18]
3.方法和函数的区别
- 调用方式不一样
函数的调用方式:函数名(实参列表)
方法的调用方式:变量.方法名(实参列表) - 对于普通函数,接收者是值类型时,不能将指针类型的数据直接传递,反之亦然
- 对于方法(如strtuct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反之同样也可以>对于方法(如strtuct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反之同样也可以
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func (p Person) test() {
p.Name = "Merry"
}
func (p *Person) test01() {
p.Name = "Jack"
}
func main() {
person := Person{
Name: "Tom",
Age: 18,
}
person.test()
fmt.Println("name=", person.Name)
(&person).test() // 从形式上是传入地址,但本质任然是值拷贝
fmt.Println("name=", person.Name)
person.test01() // 等价于(&person).test01() 从形式上是传入值类型,但本质任然是地址拷贝
fmt.Println("name=", person.Name)
(&person).test01()
fmt.Println("name=", person.Name)
}
输出结果:
name= Tom
name= Tom
name= Jack
name= Jack
对上面代码总结:
不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和那个类型绑定的
如果是和值类绑定型,比如: (p Person) test(),则是值拷贝
如果是和地址类型绑定,比如:func (p *Person) test01(),则是地址拷贝