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访问
}
- 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)
}
- JSON反序列化结构体(从json中读取数据为结构体数据)
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)
}