前言

本文将介绍go语言中结构体的定义与使用,以及介绍方法的含义。通过结构体和方法相结合,能够完成许多面向对象编程语言中对象能完成的事情。

一、结构体与方法的定义

1.结构体与工厂函数

  • go语言通过type与struct关键字定义结构体,每个结构体可以拥有若干个字段,这些字段可以是具名的,也可以是匿名的,结构体的实例可以通过"."符号来访问到对应的字段。
  • 工厂函数,工厂函数是一种约定俗成的函数,通常用于实例化一个结构体,通常以newName方式命名。
  • 结构体与工厂函数的示例
type person struct{
	name string  // 结构体中每个字段名必须唯一
	age int  // 结构体字段名大写字母开头,可以导出给外部变量使用
	int  // 匿名字段:结构体中每种数据类型的匿名字段有且仅有一个
}

// newPerson 工厂函数,类似于面向对面编程语言中的构造函数
func newPerson(name string,age int)*person{
	return &person{
		name: name,
		age: age,
		int:32,
	}
}

2.方法与接收者

func (p *person) setAge(age int){
	p.age = age // 改变接收者的值,得用指针接收者
}

func (p person) getName()string{
	return p.name 
}
二、结构体内嵌


1.继承

        在面向对象编程的语言中,子类可以通过继承父类,从而获取与父类相同的字段属性与方法。在Go语言中虽然没有类的概念,但可以通过内嵌结构体实现类似的效果。

// 继承
type man struct{
	person  // 通过匿名字段嵌入person结构体,man可以访问person的所有字段,也能调用其方法
	sex string
}

func newMan(name string,age int,sex string)*man{
	return &man{
		person:person{
			name: name,
			age: age,
			int:32,
		},
		sex: sex,
	}
}

func main(){
    m := newMan("lhq",22,"male")
	fmt.Println(m.name,m.age,m.sex) // 输出: lhq 22 male,man可以访问其内嵌结构体的字段
	m.setAge(19)
	fmt.Println(m.age) // 输出: 19 ,通过内嵌结构体进行继承,man可以调用person的方法
}

        上述代码定义了一个新的man结构体,其中匿名内嵌了一个person结构体,再定义一个属于自己的字段sex,这样,man结构体的实例既可以访问person结构体的字段,又能访问自己的sex字段,同时还能调用person结构体的方法。这就近似实现了继承。


2.字段冲突

        结构体内嵌使得结构体更加丰富,但同时也会带来相应的冲突。考虑如下代码:

type women struct{
	age int  // 外部有一个age字段
	sex string
	person // person内部也有一个age字段
}

func newWomen(name string,age int,sex string)*women{
	return &women{
		age: age,
		sex: sex,
		person:person{
			name: name,
			age: age,
			int:32,
		},
	}
}

        我们定义一个women,里面内嵌了person结构体,其本身又定义了一个age字段。那么当我们使用women.age来访问或者修改age时,到底是访问的哪一个age呢? 

w := newWomen("xyt",18,"female")
	fmt.Println(w.name,w.age,w.person.age,w.sex) //输出:xyt 18 18 female
	w.setAge(21)
	fmt.Println(w.age,w.person.age) // 输出:18 21,可以看到,通过w.age访问age字段,得到的是外部字段的值,想要获取内部字段,需要指定内部结构体对象

        我们可以看到,实际访问的是外部的age字段,go语言中规定,当结构体中不同层次存在相同名字的字段时,默认外部字段会覆盖内部字段。(注意:这里的覆盖并不是指内部字段的值被外部字段的值覆盖,实际上内部字段的值依然存在,只是我们需要使用"."符号进入到内部结构体再去访问内部字段) 

3.方法冲突

        与字段冲突一样,如果结构体中不同层次声明了同一个方法,那么通过外部使用该方法,默认使用的是外部结构体声明的方法。

func (p person) getName()string{
	return p.name
}

func (w *women) getName()string{
	return "i don't tell you"
}

func main(){
    fmt.Println(w.getName()) // 同理,通过w.getName()调用方法,调用的是外部的方法,输出:I don't tell you
}

需要注意的是:当结构体同一层次中出现字段或方法冲突时,go语言将会报错,因为产生了模糊的字段域,我们要避免同一层次中出现字段或方法冲突。 

三、结构体字段的导出与标签
// 标签的使用
type user struct{
	name string 
	age int	
}

func newUser(name string,age int)*user{
	return &user{
		name: name,
		age: age,
	}
}

func main(){
    // 使用json包序列化与反序列化数据
	u1 := newUser("u1",18)
	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
	if err != nil{
		fmt.Printf("Marshal failed,err:%v\n",err)
		return
	}
	fmt.Println(string(msg)) // 输出{}
}
// 标签的使用
type user struct{
	Name string 
	Age int	
}

func newUser(name string,age int)*user{
	return &user{
		Name: name,
		Age: age,
	}
}

func main(){
    // 使用json包序列化与反序列化数据
	u1 := newUser("u1",18)
	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
	if err != nil{
		fmt.Printf("Marshal failed,err:%v\n",err)
		return
	}
	fmt.Println(string(msg)) // 输出{"Name":"u1","Age":18}
}
// 标签的使用
type user struct{
	Name string `json:"name"`
	Age int	`json:"age"`
}

func newUser(name string,age int)*user{
	return &user{
		Name: name,
		Age: age,
	}
}

func main(){
    // 使用json包序列化与反序列化数据
	u1 := newUser("u1",18)
	msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
	if err != nil{
		fmt.Printf("Marshal failed,err:%v\n",err)
		return
	}
	fmt.Println(string(msg)) // 输出{"name":"u1","age":18}
}
总结

结构体可以定义具名字段与匿名字段,同时可以进行结构体内嵌,但要注意字段冲突与方法冲突。当外部包需要访问结构体的字段或方法时,首字母必须大写让字段或方法导出。方法是指定接收者调用的函数,接收者分为值接收者与指针接收者,尽可能使用指针接收者。