GO基础之结构体
1 、什么是结构体
go语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
2、什么是实例化?
- go结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正分配内存。因此必须在定义结构体并实例化后才能使用结构体实例化就是根据结构体定义的格式创建一份与格式一致的內存区域。
- 结构体实例之间的内存是完全独立的。
package main import "fmt" func main() { //实例化方式一 var stu1 student fmt.printf("stu1=%t,%v,%q \n", stu1, stu1, stu1) stu1.name = "zs" stu1.age = 12 stu1.sex = 1 fmt.printf("stu1=%t,%v,%q \n", stu1, stu1, stu1) //实例化方式二 stu2 := student{} stu2.name = "david" stu2.age = 33 stu2.sex = 1 fmt.printf("stu2=%t,%v,%q \n", stu2, stu2, stu2) //实例化方式三 stu3 := student{ name: "josh", age: 28, sex: 1, } fmt.printf("stu3=%t,%v,%q \n", stu3, stu3, stu3) //实例化四 stu4 := student{"ruby", 30, 0} fmt.printf("stu3=%t,%v,%q \n", stu4, stu4, stu4) //实例化五 //使用内置函数new()对结构体进行实例化,结构体实例化后形成指针类型的结构体 //·new内置函数会分配内存。第一个参数是类型,而不是值,返回的值是指向该类型新分配的零值的指针。该函数用于创建某个类型的指针。 stu5 := new(student) (*stu5).name = "running" (*stu5).age = 31 stu5.sex = 0 //语法糖的方式 fmt.printf("stu3=%t,%v,%q \n", stu5, stu5, stu5) //stu3=*main.student,&{running 31 0},&{"running" '\x1f' '\x00'} } //结构体的定义 type student struct { name string age int sex int8 }
语法糖的概念( syntactic sugar)
- 语法糖,也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达( peterj.landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
- 通常来说使用语法糖能够增加程序的可读性,从而减少程序代码岀错的机会
- 结构体和数组中都含有语法糖。
package main import "fmt" func main() { arr := [4]int{1, 2, 3, 4} arr2 := &arr fmt.println((*arr2)[3]) //数组中的语法糖 fmt.println(arr2[3]) //切片 arr3 := []int{10, 20, 30, 40} arr4 := &arr3 fmt.println((*arr4)[3]) //切片中没有语法糖 //fmt.println(arr4[3]) }
3、结构体是值类型传递
二、深拷贝与浅拷贝
struct 默认是深拷贝
package main import "fmt" func main() { stu1 := student{"zs", 18, 0} fmt.printf("stu1:=%t,%p,%v \n", stu1, &stu1, stu1) //stu1:=main.student,0xc0000044a0,{zs 18 0} stu2 := stu1 stu2.name = "ls" fmt.printf("stu2:=%t,%p,%v \n", stu2, &stu2, stu2) //stu2:=main.student,0xc000004520,{ls 18 0} fmt.printf("stu1:=%t,%p,%v \n", stu1, &stu1, stu1) //stu1:=main.student,0xc0000044a0,{zs 18 0} //修改stu2对stu1没有任何影响 }
实现浅拷贝
1、直接赋值指针地址
package main import "fmt" func main() { stu1 := student{"zs", 18, 0} fmt.printf("stu1:=%t,%p,%v \n", stu1, &stu1, stu1) //stu1:=main.student,0xc00005a440,{zs 18 0} stu2 := &stu1 stu2.name = "ls" fmt.printf("stu2:=%t,%p,%v \n", stu2, stu2, stu2) //stu2:=*main.student,0xc00005a440,&{ls 18 0} fmt.printf("stu1:=%t,%p,%v \n", stu1, &stu1, stu1) //stu1:=main.student,0xc00005a440,{ls 18 0} //修改stu2对stu1有影响 }
2、实现结构体浅拷贝:通过new()函数来实例化对象
package main import "fmt" func main() { //3、实现结构体浅拷贝:通过new()函数来实例化对象 d4 := new(dog) d4.name = "多多" d4.color = "棕色" d4.age = 1 d4.kind = "巴哥犬" d5 := d4 fmt.printf("d4: %t , %v , %p \n", d4, d4, d4) //d4: *main.dog , &{多多 棕色 1 巴哥犬} , 0xc000040080 fmt.printf("d5: %t , %v , %p \n", d5, d5, d5) fmt.println("--------------------------------------") d5.color = "金色" d5.kind = "金毛" fmt.println("d5修改后:", d5) //d5修改后: &{多多 金色 1 金毛} fmt.println("d4:", d4) //d4: &{多多 金色 1 金毛} } type dog struct { name string color string age int8 kind string }
三、struct做函数参数
结构体对象或指针作为函数的参数及函数返回值
- 结构体对象作为函数参数与结构体指针作为函数参数的不同
- 结构体对象作为函数返回值与结构体指针作为函数返回值的不同
四、匿名结构体和匿名字段
匿名结构体
- •没有名字的结构体。无需通过type关键字定义就可以直接使用。
- •在创建匿名结构体时,同时要创建对象。
- •匿名结构体由结构体定义和键值对初始化两部分组成
//匿名结构体 addr := struct { province, city string }{"陕西省", "西安市"} fmt.println(addr)
结构体的匿名字段
结构体中的字段没有名字,只包含一个没有字段名的类型。这些字段被称为匿名字段。
- •如果字段没有名字,那么默认使用类型作为字段名。
- •注意:同一个类型只能写一个。
- •结构体嵌套中采用匿名结构体字段可以模拟继承关系。
package main import "fmt" type user struct { string byte int8 float64 } func main() { // 实例化结构体 user:= user{"steven" , 'm' , 35 , 177.5} fmt.println(user) //如果想依次输出姓名、年龄、身高、性别 fmt.printf("姓名:%s \n" , user.string) fmt.printf("身高:%.2f \n" , user.float64) fmt.printf("性别:%c \n" , user.byte) fmt.printf("年龄:%d \n" , user.int8) }
五、结构体的聚合
•将一个结构体作为另一个结构体的属性(字段),这种结构就是结构体嵌套。
•结构体嵌套可以模拟面向对象中的两种关系:
- 〇聚合关系:一个类作为另_个类的属性
- 〇继承关系:一个类作为另一个类的子类。子类和父类。
package main import "fmt" type address struct { province, city string } type person struct { name string age int address *address } func main() { //模拟结构体对象之间的聚合关系 p := person{} p.name = "steven" p.age = 35 //赋值方式1: addr := address{} addr.province = "北京市" addr.city = "海淀区" p.address = &addr fmt.println(p) fmt.println("姓名:", p.name) fmt.println("年龄:", p.age) fmt.println("省:", p.address.province) fmt.println("市:", p.address.city) fmt.println("-----------------------") //修改person对象的数据,是否会影响address对象的数据? p.address.city = "昌平区" fmt.println("姓名:", p.name) fmt.println("年龄:", p.age) fmt.println("省:", p.address.province) fmt.println("市:", p.address.city) fmt.println("addr市:", addr.city) //?是否会受到影响? fmt.println("-----------------------") //修改address对象的数据,是否会影响person对象的数据? addr.city = "大兴区" fmt.println("姓名:", p.name) fmt.println("年龄:", p.age) fmt.println("省:", p.address.province) fmt.println("市:", p.address.city) fmt.println("addr市:", addr.city) //?是否会受到影响? fmt.println("-----------------------") //赋值方式2 p.address = &address{ province: "陕西省", city: "西安市", } fmt.println(p) fmt.println("姓名:", p.name) fmt.println("年龄:", p.age) fmt.println("省:", p.address.province) fmt.println("市:", p.address.city) fmt.println("-----------------------") }
六、结构体嵌套模拟继承关系
•继承是传统面向对象编程中三大特征之一。用于描述两个类之间的关系。一个类(子类、派生类)继承于另一个类(父类、超类)。
•子类可以有自己的属性和方法,也可以重写父类已有的方法。
•子类可以直接访问父类所有的属性和方法。
•提升字段:
〇在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。
〇换句话说,父类中的字段就是提升字段。
•继承的意义:避免重复代码、扩展类的功能
•采用匿名字段的形式就是模拟继承关系。而模拟聚合关系时一定要采用有名字的结构体作为字段
package main import ( "fmt" ) type person struct { name string age int sex string } type student struct { person schoolname string } func main() { //1、实例化并初始化person p1 := person{"steven", 35, "男"} fmt.println(p1) fmt.println("-------------------") //2、实例化并初始化student // 写法1: s1 := student{p1, "北航软件学院"} printinfo(s1) //写法2: s2 := student{person{"josh", 30, "男"}, "北外高翻学院"} printinfo(s2) //写法3: s3 := student{person: person{ name: "penn", age: 19, sex: "男", }, schoolname: "北大元培学院", } printinfo(s3) // 写法4: s4 := student{} s4.name = "daniel" s4.sex = "男" s4.age = 12 s4.schoolname = "北京十一龙樾" printinfo(s4) } func printinfo(s1 student) { fmt.println(s1) fmt.printf("%+v \n", s1) fmt.printf("姓名:%s, 年龄:%d , 性别:%s ,学校:%s \n", s1.name, s1.age, s1.sex, s1.schoolname) fmt.println("-------------------") }
七、结构体中的方法
• go语言同时有函数和方法,方法的本质是函数,但是方法和函数又具有不同点。
1、 含义不同
•函数function是一段具有独立功能的代码,可以被反复多次调用,从而实现代码复用。而方法method是一个类的行为功能,只有该类的对象才能调用。
2、 方法有接受者,而函数无接受者
• go语言的方法methods—种作用于特定类型变量的函数。这种特定类型变量叫做receiver (接受者、接收者、接收器)。
•接受者的概念类似于传统面向对象语言中的this或self关键字。
• go语言的接受者强调了方法具有作用对象,而函数没有作用对象。
•—个方法就是一个包含了接受者的函数。
• go语言中,接受者的类型可以是任何类型,不仅仅是结构体,也可以是struct类型外的其他任何类型。
3、 函数不可以重名,而方法可以重名
•只要接受者不同,则方法名可以一样。
package main import "fmt" import "base" type employee struct { name , currency string salary float64 } func main() { emp1 := employee{"daniel" , "$" , 2000} emp1.printsalary()//员工姓名:daniel ,薪资:$2000.00 emp1.updatesalary(1000) emp1.printsalary()//员工姓名:daniel ,薪资:$1000.00 } //printsalary方法 func (e employee) printsalary() { fmt.printf("员工姓名:%s ,薪资:%s%.2f \n", e.name , e.currency , e.salary) } func (e *employee) updatesalary(num float64) { e.salary=num }
方法的继承与重写
package main import "fmt" import "base" type human struct { name, phone string age int } type student struct { human school string } type employee struct { human company string } func main() { s1 := student{human{"daniel", "15012345678", 13}, "十一龙樾"} e1 := employee{human{"steven", "17812345678", 35}, "1000phone"} s1.printname() e1.printname() s1.sayhi() e1.sayhi() } //======================方法的继承============================= func (h *human) printname() { fmt.println("大家好!我是%s ", h.name) } //=====================方法的重写============================= func (h *human) sayhi() { fmt.printf("大家好!我是%s , 我%d岁 , 联系方式:%s \n", h.name, h.age, h.phone) } func (s *student) sayhi() { fmt.printf("大家好!我是%s , 我%d岁 , 我在%s上学,联系方式:%s \n", s.name, s.age, s.school, s.phone) } func (e *employee) sayhi() { fmt.printf("大家好!我是%s , 我%d岁 , 我在%s工作,联系方式:%s \n", e.name, e.age, e.company, e.phone) }