go中没有类的概念,其中的结构体与其他语言中的类有点相似,并且具有更高的扩展性和灵活性。
结构体是值类型,改变副本的值时,原来的变量不会变。(值类型的复制为深拷贝)
结构体名、字段名 首字母大写时,表示为公有的,外部包可访问;首字母为小写,表示为私有的,仅当前包可访问(私有字段不参与转化json)。
package main
import "fmt"
type Person struct {
Name string
Age int
Gender string
}
func main() {
// 第一种声明结构体的方式:使用var关键字
var p1 Person
p1.Name = "Tom"
p1.Age = 20
p1.Gender = "Male"
fmt.Printf("%T\n", p1) // main.Person
fmt.Println(p1) // {Tom 20 Male}
fmt.Println(p1.Name) // Tom 在使用某一个元素时,使用 . 即可
fmt.Println()
// 第二种声明结构体的方式:使用 new 关键字实例化,将返回一个指针类型的变量
var p2 = new(Person)
fmt.Println("2:")
fmt.Printf("%T\n", p2) // *main.Person 指针类型
fmt.Printf("%#v\n", p2) // &main.Person{Name:"", Age:0, Gender:""} 全部都是字段的默认值
// 在go中,支持对结构体指针直接使用 . 来访问结构体的成员。
// p2.Name = "li2" 其实在底层是 (*p2).Name = "li2"
p2.Name = "li2" // 简单赋值
(*p2).Age = 12 // 底层赋值
fmt.Printf("%#v\n", p2) // &main.Person{Name:"li2", Age:18, Gender:""}
fmt.Println()
// 第三种声明结构体的方式:
// 使用&对结构体进行取地址操作相当于对该结构体类型进行了一次 new 实例化操作,返回的也是指针
p3 := &Person{}
fmt.Println("3:")
fmt.Printf("%T\n", p3) // *main.Person
p3.Name = "li3"
(*p3).Age = 13
fmt.Printf("%#v\n", p3) // &main.Person{Name:"li3", Age:13, Gender:""}
fmt.Println()
// 第四种声明结构体的方式:使用键值对的方式
// 注意最后一个值的后面也要加上 , 规则就是,如果值的结尾立马是 } 就不用加 , 否则需要加上
p4 := Person{
Name: "li4",
Age: 14,
Gender: "Female", // 也可以只给结构体的部分变量赋值
}
fmt.Println("4:")
fmt.Printf("%T\n", p4) // main.Person
fmt.Printf("%#v\n", p4) // main.Person{Name:"li4", Age:14, Gender:"Female"}
p40 := Person{Name: "li4", Age: 14} // 如果写成一行,最后一个就不用加 , 元素都有默认值
fmt.Printf("%#v\n", p40) // main.Person{Name:"li4", Age:14, Gender:""}
fmt.Println()
// 第五种声明结构体的方式:使用指针的方式声明第四种
p5 := &Person{
Name: "li5",
Age: 15,
Gender: "Male",
}
fmt.Println("5:")
fmt.Printf("%T\n", p5) // *main.Person
fmt.Println()
fmt.Println("6:")
// 第六种声明结构体的方式:使用值的列表初始化
// 就是初始化的时候不写键,只写值。
// 这种声明方式要一次性初始化所有字段,并且字段的顺序不能变。
p6 := Person{ // 也可以声明成一行
"li6",
16,
"Male",
}
fmt.Printf("%T\n", p6) // main.Person
fmt.Printf("%#v\n", p6) // main.Person{Name:"li6", Age:16, Gender:"Male"}
}
指针类型接收者的使用场景:
package main
import "fmt"
type Person struct {
Name string
Age int
Gender string
}
// Person结构体 值传递的自定义方法,修改p不影响原变量的值
func (p Person) PrintInfo() {
fmt.Printf("name=%v; age=%v\n", p.Name, p.Age)
}
// Person结构体 引用传递的自定义方法,修改p将影响原变量的值
func (p *Person) SetInfo(name string, age int) {
p.Name = name
p.Age = age
}
func main() {
p1 := Person{
Name: "li4",
Age: 18,
Gender: "男",
}
p2 := p1 // 复制p1
p1.PrintInfo() // name=li4; age=18
p2.PrintInfo() // name=li4; age=18
p1.SetInfo("张三", 200)
p1.PrintInfo() // name=张三; age=200
p2.PrintInfo() // name=li4; age=18
}
package main
import "fmt"
type MyInt int
func (i MyInt) ShowInfo() {
fmt.Println("这是自定义类型的自定义方法")
}
func main() {
var mi MyInt = 8
mi.ShowInfo() // 这是自定义类型的自定义方法
}
结构体允许其成员字段在声明时没有字段名而只有类型,称为匿名字段,字段类型不能出现重复。
package main
import "fmt"
// 采用匿名字段的结构体,每个类型只能声明一次
type Person struct {
string
int
}
func main() {
p1 := Person{"张三", 18}
fmt.Printf("%#v\n", p1) // main.Person{string:"张三", int:18}
fmt.Printf(p1.string) // 张三
// 匿名字段其实也就相当于 string:string
}
一个结构体中可以嵌套另一个结构体或结构体指针。
go中没有类的概念,只有结构体的概念。因此嵌套也就类比成了继承。
嵌套分为命名嵌套和匿名嵌套。一般用匿名就行。
就是将被嵌套(/被继承)的结构体一个参数名,在之后的实例化和调用时都需要使用这个参数名。
package main
import "fmt"
type Person struct {
Name string
Age int
Address Address // 命名嵌套
// Addr2 Address // 当然这种命名也是没问题的
}
type Address struct {
Addr string
Phone string
}
func main() {
p1 := Person{
Name: "张三",
Age: 18,
Address: Address{
Addr: "北京",
Phone: "123456",
},
}
fmt.Printf("%#v\n", p1) // main.Person{Name:"张三", Age:18, Address:main.Address{Addr:"北京", Phone:"123456"}}
fmt.Print(p1.Address.Addr) // 北京
}
package main
import "fmt"
type Person struct {
Name string
Age int
Address // 匿名嵌套
}
type Address struct {
Addr string
Phone string
}
func main() {
var p1 Person
p1.Name = "张三"
p1.Age = 18
p1.Address.Addr = "北京" // 匿名嵌套只能逐个赋值
p1.Phone = "123456" // 但是可以省略匿名结构体名而直接赋值
fmt.Printf("%#v\n", p1) // main.Person{Name:"张三", Age:18, Address:main.Address{Addr:"北京", Phone:"123456"}}
fmt.Println(p1.Address.Addr) // 北京
fmt.Println(p1.Addr) // 北京 先去Person结构体中查找Addr字段,找不到了再去匿名结构体中查找
}
当使用(匿名)嵌套结构体时,可能存在同名字段,为了避免歧义,需要区分一下
下面分别展示两种情况:
package main
import "fmt"
type Person struct {
Name string
Age int
Ctime int // 主结构体与嵌套结构体出现同名字段时,匿名结构体相关字段需要加前缀用以区分
Address // 匿名嵌套
}
type Address struct {
Addr string
Phone string
Ctime int
}
func main() {
var p1 Person
p1.Name = "张三"
p1.Age = 18
p1.Address.Addr = "北京"
p1.Address.Phone = "123456"
p1.Ctime = 1000
p1.Address.Ctime = 99 // 需要增加 Address 前缀用以区分
// main.Person{Name:"张三", Age:18, Ctime:1000, Address:main.Address{Addr:"北京", Phone:"123456", Ctime:99}}
fmt.Printf("%#v\n", p1)
fmt.Println(p1.Address.Ctime) // 99
fmt.Println(p1.Ctime) // 1000
}
情况二:
package main
import "fmt"
type Person struct {
Name string
Age int
Address // 匿名嵌套的两个结构体中有同名的 Ctime 字段
Email
}
type Address struct {
Addr string
Phone string
Ctime int
}
type Email struct {
Email string
Ctime int
}
func main() {
var p1 Person
p1.Name = "张三"
p1.Age = 18
p1.Address.Addr = "北京"
p1.Phone = "123456"
// p1.Ctime = 1000 // 不能这样给字段赋值了,因为会出现歧义,会报错
p1.Email.Ctime = 1000
p1.Address.Ctime = 99
// main.Person{Name:"张三", Age:18, Address:main.Address{Addr:"北京", Phone:"123456", Ctime:99}, Email:main.Email{Email:"", Ctime:1000}}
fmt.Printf("%#v\n", p1)
fmt.Println(p1.Address.Ctime) // 99
// fmt.Println(p1.Ctime) // 同时也就不能这样来访问变量了,因为也会出现歧义
}
主结构体可以直接调用内嵌结构体的自定义方法。
如果主结构体和内嵌结构体存在同名方法,会调用主结构体自己的方法。
package main
import "fmt"
type Animal struct {
Age int
Color string
}
func (a Animal) GetColor() {
fmt.Printf("Color=%v\n", a.Color)
}
type Dog struct {
Name string
Weight int
Animal
}
func (d Dog) Run() {
fmt.Println("Running...")
}
func main() {
var d1 Dog
d1.Name = "大黄"
d1.Weight = 20
d1.Age = 3
d1.Color = "黄"
fmt.Println(d1) // {大黄 20 {3 黄}}
d1.Run() // Running...
d1.GetColor() // Color=黄 Gog结构体实例可以直接调用Animal结构体的方法
}
结构体可以很方便的转化为json格式,同样的json字符串也能很方便的转化为结构体对象。
借助结构体的标签的json标签,可以自定义结构体字段在与 json字符串相互转化时的字段名。
package main
import (
"encoding/json"
"fmt"
)
type Dog struct {
Name string `json:"ab"` // 给name增加tag标签,在转化成json字符串时,Name字段名将会被替换成ab
Weight int
height int // 小写字母开头的字段(私有字段)不会参与序列化
}
func main() {
var d1 Dog
d1.Name = "J"
d1.Weight = 20
d1.height = 1
fmt.Println(d1) // {J 20 1}
a, err := json.Marshal(d1) // 使用json包进行序列化,私有字段不会被序列化
if err != nil {
fmt.Println(err)
return
}
fmt.Println(a) // 直接输出将会按照字节输出,是一串数字: [123 34 97 98 34 58 ...
fmt.Printf("%s\n", a) // 输出方式1:输出是按照字符串格式
str := string(a)
fmt.Println(str) // 输出方式2:将[]byte转化为字符串,然后普通输出
}
package main
import (
"encoding/json"
"fmt"
)
type Dog struct {
Name string `json:"ab"` // 给name增加tag标签,在转化成json字符串时,Name字段名将会被替换成ab
Weight int
}
func main() {
str2 := `{"ab":"Jaa","Weight":20}`
var d2 Dog
e2 := json.Unmarshal([]byte(str2), &d2) // 函数的第二个参数要求是一个指针
if e2 != nil {
fmt.Println(e2)
}
fmt.Println(d2) // {Jaa 20}
d3 := &Dog{} // 也可以直接将结构体变量初始化成一个指针
json.Unmarshal([]byte(str2), d3)
fmt.Println(d3) // &{Jaa 20}
}