Golang构造函数

go三层架构(Go基础之结构体二)(1)

Go没有类似传统面向对象的构造函数,但是我们可以通过结构体来使用构造函数初始化结构体类型。

我们在使用构造函数要注意struct是值类型如果结构体比较复杂最好返回结构体指针类型,因为值拷贝性能开销大。

package main import "fmt" type Server struct { address string port int } func NewServer() *Server { return &Server{ address: "localhost", port: 8080, } } func main() { server1 := NewServer() fmt.Println(server1) } // result &{localhost 8080}

Go中的工厂模式其实就是包内一个不可直接实例的结构体(结构体名称首字母小写即私有结构体),包外不可直接实例化实例,那么为了解决这个问题可以写一个包外可调用的函数,通过这个函数实现返回结构体对象。

// main.go 代码 package main import ( "fmt" "app/model" ) func main(){ p1 := model.NewPerson("jack", 20) age := p.GetAge() fmt.Println(*p1) fmt.Println(age) } // result // {jack 20} // 20 // app/model.go package model type person struct( Name string age int ) func NewPerson(name string,age int) *person{ return &person{ Name: name, age: age, } } func (p *person) GetAge() int{ return p.age }

类型别名和自定义类型

类型别名是Go在1.9版本之后添加的新特性,主要是用在代码升级、代码重构和迁移中类型的兼容性。

type byte uint8 type rune int32

类型别名指定类型别名只是类型的别名。本质上类型的别名和类型是相同的类型,即基本数据类型是相同的。

package main import "fmt" type myInt int type intAlias = int func main() { var a myInt fmt.Printf("a Type: %T, value: %d\n", a, a) var b intAlias fmt.Printf("b Type: %T, value: %d\n", b, b) } // result // a Type: main.myInt, value: 0 // b Type: int, value: 0

通过上面的代码来看,在表面上类型别名和自定义类型之间只有相等的符号差异(“ =”),实际上我们可以代码运行结构看到a的类型是myint而且是main包定义的myInt类型,即生成了新的数据类型,而b的类型是int,则表示intAlias类型仅在代码中体现而编译完成之后没有intAlias类型。

结构体中的方法及接受者

Go 语言支持方法,Go方法类似于 Go 函数,但有一个区别即该方法包含一个接收者参数。通过接受者参数该方法就可以访问接收者的属性。这里接收者表示可以是结构类型或非结构类型,在代码中创建方法时,接收者和接收者类型必须存在于同一个包中。并且不允许创建接收者类型已在另一个包中定义的方法,包括 int、string 等内置类型。如果这样做,Go编译器将给出错误。

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) { 函数体 }

使用值类型接受者的方法时无法改变接受者变量的值。

package main import "fmt" type person struct { name, job string age int } func (p person) show() { fmt.Println("Person's Name: ", p.name) fmt.Println("Job: ", p.job) fmt.Println("age:", p.age) } func main() { res := person{ name: "jack", job: "engine", age: 20, } res.show() } // result // Person's Name: jack // Job: engine // age: 20

结构体指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。

package main import "fmt" type user struct { name, job string age int } func (u *user) SetAge(age int) { u.age = age } func main() { res := user{ name: "jack", job: "engine", age: 19, } fmt.Println("user's name: ", res.name) fmt.Println("user's job: ", res.job) fmt.Println("user's age: ", res.age) u := &res u.SetAge(22) fmt.Println("user's name: ", res.name) fmt.Println("user's job: ", res.job) fmt.Println("user's age: ", res.age) } // result // user's name: jack // user's job: engine // user's age: 19 // user's name: jack // user's job: engine // user's age: 22

在 Go 语言中,只要类型和方法定义存在于同一个包中,就可以创建具有非结构类型接收器的方法。如果不存在同一个包里,那么编译器就会报错,因为它们是在不同的包中定义的。

package main import "fmt" type data int func (d1 data) multiply(d2 data) data { return d1 * d2 } func main() { value1 := data(20) value2 := data(20) res := value1.multiply(value2) fmt.Println("Final result: ", res) } // result // Final result: 400

重点:什么时候使用值类型接受者方法,什么时候使用指针类型接受者方法。

结构体的”继承“

结构体嵌套可以模拟面向对象编程继承的特性中以下两种关系:

聚合关系:一个类作为另一个类的属性,聚合关系一定不能是匿名结构体必须用有名字的结构体作为结构体字段

继承关系:一个类作为另一个类的子类。子类和父类的关系。匿名结构体字段的形式就是继承关系

package main import "fmt" type Address struct { province, city string } type Person struct { name string age int address *Address } func main() { p1 := Person{name: "jack", age: 19, address: &Address{province: "Guangdong", city: "Guangzhou"}} fmt.Printf("Name: %s\nAge: %d\nProvince: %s\nCity: %s\n", p1.name, p1.age, p1.address.province, p1.address.city) // 当你修改Person结构体实例化p1的数据时,结构体Address的数据也会发生变化 p1.address.province = "Hunan" p1.address.city = "Changsha" fmt.Printf("Province: %s\nCity:%s\n", p1.address.province, p1.address.city) }

如果一个结构体嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的方法,从而实现继承。

package main import "fmt" type Cat struct { age int name string } func (c *Cat) Run() { fmt.Println("Cat Running") } type WhiteCat struct { Cat color string } type BlackCat struct { Cat } func (p *BlackCat) String() string { str := fmt.Sprintf("name=[%s] age=[%d]", p.name, p.age) return str } func main() { var a WhiteCat a.age = 2 a.name = "tom" a.color = "white" fmt.Println(a) a.Run() var b BlackCat b.age = 3 b.name = "rise" b.Run() fmt.Printf("%s", &b) }

注意

结构体嵌套时可能存在相同的属性名,属性名重名会导致属性名冲突,尽量避免这种情况。

结构体字段的私有属性和公开属性

结构体中字段大写开头表示可公开访问的属性,小写表示私有属性(仅在定义当前结构体的包中可访问)。

结构体标签(Tag)和JSON序列化

结构体tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag在结构体字段的后方定义,由一对反引号包裹起来。

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

type user struct { Name string `json:name` // 通过指定tag实现json序列化该字段时的key password string // 私有属性不能被json访问 }

package main import ( "encoding/json" "fmt" ) type User struct { ID int `json:"id"` Name string `json:"name"` } func main() { u1 := User{ ID: 1, Name: "jack", } data, err := json.Marshal(u1) if err != nil { fmt.Println("json marshal failed!") return } fmt.Printf("json str:%s\n", data) }

package main import ( "encoding/json" "fmt" ) type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"` } func main() { var r User s := `{ "name":"jack", "age":18, "id":1 }` err := json.Unmarshal([]byte(s), &r) if err != nil { fmt.Print(err) } fmt.Printf("结构体转换为字符串之后的值为:%#v", r) }

,