结构体
- 可以封装多个基本数据类型;来实现面向对象;是值类型;占用一块连续的内存空间
- 相同struct类型的可以比较;不同struct类型的不可以比较;结构体是否相同不但与属性类型个数有关,还与属性顺序相关;但是结构体属性中有不可以比较的类型,如map,slice。
func main(){ type A struct { a int } type B struct { a int }s a := A{1} b := A{2} //b := B{1} //编译错误 if a == b { fmt.Println("a == b") }else{ fmt.Println("a != b") } } //output:a!=b
sn1 := struct { age int name string }{age: 11,name: "qq"} sn3:= struct { name string age int }{age:11,name:"qq"} //sn3与sn1就不是相同的结构体了,不能比较。
sm1 := struct { age int m map[string]string }{age: 11, m:map[string]string{"a": "1"}} sm2 := struct { age int m map[string]string }{age: 11, m:map[string]string{"a": "1"}} //sm1、sm2不可以比较;结构体属性中有不可以比较的类型,如map,slice。 //但是可以通过reflect.DeepEqual()来进行比较; if sm1 == sm2 { //错的 fmt.Println("sm1== sm2") } //可以通过reflect.DeepEqual()来实现比较 if reflect.DeepEqual(sm1, sm2) { fmt.Println("sm1==sm2") }else { fmt.Println("sm1!=sm2") }
关于Go语言中的内存对齐 :推荐阅读:在 Go 中恰到好处的内存对齐
/*
定义:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
类型名:标识自定义结构体的名称,在同一个包内不能重复。首字母大写对外包引用
字段名:表示结构体字段名。结构体中的字段名必须唯一。对外包引用首字符必须大写
字段类型:表示结构体字段的具体类型。
*/
type person struct {
name string
gender string
age int
hobby []string
}
type person struct {
name,gender string
age int
hobby []string
}
//匿名字段:结构体允许其成员字段在声明时没有字段名而只有类型;默认会采用类型名作为字段名
type Person struct {
string
int
}
//初始化,没有初始化的返回对应类型的零值
//方法1
var person1 person
person1.name = "zhangsan"
person1.age = 3000
person1.gender = "男"
person1.hobby = []string{
"更好",
"优秀",
"up",
}
fmt.Println(person1)
//方法2;使用值列表的形式初始化 但是字段顺序必须要和声明的结构体字段顺序一致 且字段不能少必须全部写上
p3 := person{
"tiedan",
1000,
"男",
[]string{
"up", "fight",
},
}
//匿名结构体 (多用于临时场景)
var s struct {
name string
age int
}
s.name = "zhangsan"
s.age = 1000
fmt.Printf("%T %v\n", s, s)
//结构体指针1
var p1 = new(person) //new返回的是指针 相当于p1 := &person{}
p1.name = "gaiya" //语法糖果 相当于 (*p1).name="gaiya"
fmt.Printf("%T %p\n", p1, p1)
//结构体指针2
//key-value 初始化
var p2 = person{
name: "tiezhu",
age: 2000,
}
fmt.Printf("%#v\n", p2)
//结构体嵌套
type addres struct {
province string
city string
}
type person struct {
name string
age int
addr addres
}
type company struct {
name string
addres //匿名结构体嵌套
}
func main() {
p1 := person{
name: "tiezhu",
age: 2000,
addr: addres{
province: "安徽",
city: "合肥",
},
}
c1 := company{
name: "公司",
addres: addres{
province: "浙江",
city: "杭州",
},
}
fmt.Println(p1, p1.addr.city)
fmt.Println(c1, c1.city) //只适用于匿名结构体的嵌套 会首先从自己的结构体中找字段 没有的话就去匿名结构体中寻找
}
//用结构体模拟实现其他语言中的“继承”
type fath struct {
name string
}
//给fath 实现一个move方法
func (a fath ) F() {
fmt.Printf("%s。。。\n", a.name)
}
type ch struct {
feet uint8
fath //fath 拥有的方法,ch 也会拥有
}
//给ch 实现一个C方法
func (d ch) C() {
fmt.Printf("%s。。。”\n", d.name)
}
func main() {
d1 := ch{
feet: 4,
fath: fath{
name: "gaiya",
},
}
fmt.Println(d1)
d1.F()
d1.C ()
}
//结构体标签(Tag)
type Student struct {
ID int `json:"id" form:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
Age int `json:"age,omitempty"`//omitempty 忽略空值 ,0、”” 等在传递的时候会被忽略
Hoby string `json:"_"`//"_"完全跳过字段
}
- 结构体由一个或多个键值对组成,键与值使用冒号分隔,值用双引号括起来;不同的键值对之间使用空格分隔
- 结构体标签的解析代码的容错能力很差;一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值;例如不要在key和value之间添加空格
//匿名空结构体
var a struct{}
var a =struct{}{}//实例化的话也不占内存;所以匿名空的结构体一般用来做通知使用:
//结构体和json转换
//1.序列化:Go语言中结构体变量转换为json格式的字符串
//2.反序列化:json格式的字符串转换为Go语言中可以识别的结构体变量
type person struct {
Name string `json:"name"` //因为结构体会传入到Marshal函数中所以字段必须是大写的才可以被别的包调用
Age int `json:"age"` //``是为了指定转换为对应格式的字段名。json:"age"标识转json时该字段显示为age
}
func main() {
p1 := person{
Name: "Bradley",
Age: 18,
}
fmt.Printf("%v\n", p1)
//序列化
v, error := json.Marshal(p1)
if error != nil {
fmt.Printf("error is %v\n", error)
return
}
fmt.Printf("%v\n", string(v))
//反序列化
str := `{"name":"coope","age":20}`
var p2 person
json.Unmarshal([]byte(str), &p2) //因为是传入函数且是修改 所以需要传入的是指针
fmt.Printf("%#v\n", p2)
}
因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以在传递结构体时使用指针类型
method
方法:method;
- 是作用于特定类型的函数;接收者表示的是调用该方法的具体类型变量,多用类型名的首字母来作为变量;
- 接收者的概念就类似于其他语言中的this或者 self。
- 接收者必须是自己包里定义的类型,不能是内置的类型如int 如果需要可以自定义int:(type myint int)
- 接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法
- 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法
//指针类型接收者
func (d *dog) wang() { //因为go函数传的值永远是拷贝的值,所以当需要修改接收者中的值的时候,需要传指针
d.age++ //语法糖果:相当于(*d).age++
fmt.Printf("%s ww...\n", d.name)
}
//值接收者
func (d dog) wang2() {
fmt.Printf("%s ww...\n", d.name)
}
//MyInt 将int定义为自定义MyInt类型
type MyInt int
func (m MyInt) SayHello() {
return
}
//由于slice、map这两种数据类型是引用类型,都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意;需要传他们的copy进行赋值
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDreams(dreams []string) {
p.dreams = dreams
}
func main() {
p1 := Person{name: "zs", age: 18}
data := []string{"a", "b", "c"}
p1.SetDreams(data)
data[1] = "B"
fmt.Println(p1.dreams) // ouput:{"a", "B", "c"}
}
//正确做法是
func (p *Person) SetDreams(dreams []string) {
p.dreams = make([]string,len(dreams))
p.dreams=dreams
}