结构体与继承
什么是结构体?
它是将多个任意类型的变量组合在一起的聚合数据类型,可以理解为Go语言的结构体structural和其他语言的class有相等的地位
//定义结构体
type 结构体名 struct {
属性名 属性类型
属性名 属性类型
…
}
若相邻的属性(字段)是相同类型,可以合并写在一起
规则一:当最后一个字段和结果不在同一行时,, 不可省略。
规则二:字段名要嘛全写,要嘛全不写,不能有的写,有的不写。
规则三:初始化结构体,并不一定要所有字段都赋值,未被赋值的字段,会自动赋值为其类型的零值。
使用组合函数的方式来定义结构体方法
package main
import "fmt"
// 定义一个名为Profile 的结构体
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
// 定义一个与 Profile 的绑定的方法
func (person Profile) FmtProfile() {
fmt.Printf("名字:%s\n", person.name)
fmt.Printf("年龄:%d\n", person.age)
fmt.Printf("性别:%s\n", person.gender)
}
func main() {
// 实例化
myself := Profile{name: "小明", age: 24, gender: "male"}
// 调用函数
myself.FmtProfile()
}
其中FmtProfile 是方法名,而(person Profile) :表示将 FmtProfile 方法与 Profile 的实例绑定。我们把 Profile 称为方法的接收者,而 person 表示实例本身,在方法内可以使用 person.属性名 的方法来访问实例属性。
方法的参数传递方式
想要在方法内改变示例的属性时,必须时使用指针作为方法的接收者
package main
import "fmt"
// 声明一个 Profile 的结构体
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
// 重点在于这个星号: *
func (person *Profile) increase_age() {
person.age += 1
}
func main() {
myself := Profile{name: "小明", age: 24, gender: "male"}
fmt.Printf("当前年龄:%d\n", myself.age)
myself.increase_age()
fmt.Printf("当前年龄:%d", myself.age)
}
在这两种情况下,直接使用指针作为方法的接收者
1、需要在方法内部改变结构体内容的时候
2、出于性能问题,当结构体过大的时候
Go语言本身并部支持继承,但可以使用组合的方法,实现类似继承的效果
把一个结构体嵌入另外一个结构体的方法,称之为组合
package main
import "fmt"
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
company
}
func main() {
myCom := company{
companyName: "Tencent",
companyAddr: "深圳市南山区",
}
staffInfo := staff{
name: "小明",
age: 28,
gender: "男",
position: "云计算开发工程师",
company: myCom,
}
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.companyName)
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.company.companyName)
}
内部方法与外部方法
方法首字母大写,相当于public,其他包可以随意调用
方法首字母小写,相当于private,其他包无法访问
三种实例化
//第一种,正常实例化
func main() {
xm := Profile{
name: "小明",
age: 18,
gender: "male",
}
}
//第二种:使用new
func main() {
xm := new(Profile)
// 等价于: var xm *Profile = new(Profile)
fmt.Println(xm)
// output: &{ 0 }
xm.name = "iswbm" // 或者 (*xm).name = "iswbm"
xm.age = 18 // 或者 (*xm).age = 18
xm.gender = "male" // 或者 (*xm).gender = "male"
fmt.Println(xm)
//output: &{iswbm 18 male}
}
//第三种:使用 &
func main() {
var xm *Profile = &Profile{}
fmt.Println(xm)
// output: &{ 0 }
xm.name = "iswbm" // 或者 (*xm).name = "iswbm"
xm.age = 18 // 或者 (*xm).age = 18
xm.gender = "male" // 或者 (*xm).gender = "male"
fmt.Println(xm)
//output: &{iswbm 18 male}
}
接口与多态
package main
import (
"fmt"
"strconv"
)
type good interface {
TotalPrice() int
OrderInfo() string
}
type fruits struct {
price int
quantity int
name string
}
func (f fruits) TotalPrice() int {
return f.price * f.quantity
}
func (f fruits) OrderInfo() string {
return "你要购买" + strconv.Itoa(f.quantity) + "个"+ f.name +",总价是" +strconv.Itoa(f.TotalPrice()) + "元"
}
type seafood struct {
price int
quantity int
name string
}
func (s seafood) TotalPrice() int {
return s.price * s.quantity
}
func (s seafood) OrderInfo() string {
return "你要购买" + strconv.Itoa(s.quantity) + "斤" + s.name +",总价是" +strconv.Itoa(s.TotalPrice()) + "元"
}
func SettleAccounts(goods []good) int {
var all_price = 0
for _,good := range goods{
fmt.Println(good.OrderInfo())
all_price += good.TotalPrice()
}
return all_price
}
func main() {
apple := fruits{
price: 5,
quantity: 3,
name: "苹果",
}
fish := seafood{
price: 30,
quantity: 2,
name: "石斑鱼",
}
all_price := SettleAccounts([]good{apple,fish})
fmt.Println("你总共消费",all_price,"元")
}
先定义了good的接口,里面有两个方法(多态)TotalPrice与OrderInfo,只要实现其中一个方法,那么这个结构体就是一个商品
然后定义两个结构体,分别时水果fruits与海鲜seafood,他们分别实现了good接口的两个方法
这时候挑选两个商品(实例化),分别是苹果与石斑鱼,创建一个购物车(类型为goo的切片),来存放商品,方法SettleAccounts计算购物车里面的订单金额
结构体的Tag
字段上还可以额外再加一个属性,用反引号(Esc键下面的那个键)包含的字符串,称之为 Tag,也就是标签。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr,omitempty"`
}
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr,omitempty"`
}
func main() {
p1 := Person{
Name: "Jack",
Age: 22,
}
data1, err := json.Marshal(p1)
if err != nil {
panic(err)
}
// p1 没有 Addr,就不会打印了
fmt.Printf("%s\n", data1)
// ================
p2 := Person{
Name: "Jack",
Age: 22,
Addr: "China",
}
data2, err := json.Marshal(p2)
if err != nil {
panic(err)
}
// p2 则会打印所有
fmt.Printf("%s\n", data2)
}
运行结果
$ go run demo.go
{"name":"Jack","age":22}
{"name":"Jack","age":22,"addr":"China"}
由于 Person 结构体里的 Addr 字段有 omitempty 属性,因此 encoding/json 在将对象转化 json 字符串时,只要发现对象里的 Addr 为 false, 0, 空指针,空接口,空数组,空切片,空映射,空字符串中的一种,就会被忽略。
type Person struct {
Name string `label:"Name is: "`
Age int `label:"Age is: "`
Gender string `label:"Gender is: " default:"unknown"`
}
func Print(obj interface{}) error {
// 取 Value
v := reflect.ValueOf(obj)
// 解析字段
for i := 0; i < v.NumField(); i++ {
// 取tag
field := v.Type().Field(i)
tag := field.Tag
// 解析label 和 default
label := tag.Get("label")
defaultValue := tag.Get("default")
value := fmt.Sprintf("%v", v.Field(i))
if value == "" {
// 如果没有指定值,则用默认值替代
value = defaultValue
}
fmt.Println(label + value)
}
return nil
}
输出
$ go run demo.go
Name is: MING
Age is: 29
Gender is: unknown
空结构体
跟正常结构体一样,可以接收方法函数,表象特征就是没有任何属性,是一个不占用空间的对象
type Lamp struct{}
func main() {
lamp := Lamp{}
fmt.Print(unsafe.Sizeof(lamp))
}
// output: 0
基于这个特性,在一些特殊的场合之下,可以用做占位符使用,合理的使用空结构体,会减小程序的内存占用空间。
比如在使用信道(channel)控制并发时,我们只是需要一个信号,但并不需要传递值,这个时候,也可以使用 struct{} 代替。
func main() {
ch := make(chan struct{}, 1)
go func() {
<-ch
// do something
}()
ch <- struct{}{}
// ...
}