golang struct(结构体)详解一
Go语言的struct,与C语言中的struct或其他面向对象编程语言中的类(class)类似,可以定义字段(属性)和方法,但也有很多不同的地方。
1.定义
type Person struct {
Name string
Age int
}
上面,我们定义了一个包含2个属性的 Struct,其中 Name属性是 string类型, Age属性是 int类型。
细节说明
- 属性(字段)的类型可以为:基本类型、数组 或 引用类型
- 一个结构体的不同变量的字段是相互独立,互不影响的
var p1 Person
p1.Name = "cat"
p1.Age = 23
p2 := p1 // 结构体是值类型,默认是值拷贝,两结构体 p1、p2 互不影响
p2.Name = "tom"
- 在创建一个结构体变量后,如果没有给字段赋值,都有默认值:
布尔类型是 false, 数值是 0, 字符串是 “”
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0]
指针,slice,和 map 的初始值都是 nil ,即还没有分配内存空间
type Cat struct {
// 字段的类型可以为:基本类型、数组或引用类型
Name string // 默认值: ""
Age int // 默认值: 0
live map[string]string // 默认值: nil
}
func main() {
var cat1 Cat // 直接声明方式,所有字段都有初始化值
fmt.Println(cat1.Name, cat1.Age) // 打印: "" 0
fmt.Println(cat1.live == nil) // 打印: true
}
2.初始化结构体
方式1:直接声明
var person Person
方式2:使用{ }
var p = Person{} // 初始化一个默认值的结构体
var p2 = Person{"tom", 19} // 按字段在结构体中的顺序赋值,即 Name 为 "tom",Age 为 19
这种方式要求所有的字段都必须赋值,如果字段太多,则每个字段都要赋值,会很繁琐。
方式3:指定字段名
// 把字段名和值写在一起,就不依赖字段的定义顺序。对于其他没有指定的字段,则使用该字段类型的初始化值
var p = Person{
Name: "tomcat",
Age: 23,
}
方式4:& 指针赋值方式
// 方式1
var p1 *Person = new(Person) // 给 Person创建空间, 即给指针赋值
p1.Name = "tom" // 指针变量访问字段的标准方式: (*p1).Name = "tom"
// 方式2
var p2 *Person = &Person{} // 或 &Person{"tom", 19}
var p1 *Person
只是一种指针变量的定义,必须要赋值才行;否则他只是一个未初始化的 [指针变量]。new(Person)
使用 Go内置的 new()函数,分配内存来初始化结构体,每个字段都有默认的初始值。- 结构体指针访问字段的标准方式:
(*结构体指针).字段名
,但 Go 底层做了转换,允许结构体指针.字段名
。
3.结构体指针
结构体与数组一样,都是值传递,比如当实参传给函数的形参时,会复制一个副本,所以为了提高性能,一般不建议直接传递结构体,可以使用结构体指针。
示例1:
type Person struct {
Name string
Age int
}
func main() {
var p1 = Person{
Name: "tom",
Age: 23,
}
var p2 *Person = &p1 // p2是指向 p1 的指针变量。并非值拷贝。两者指向同一块内存空间
p2.Name = "mary"
fmt.Printf("p1.Name: %v, p2.Name: %v\n", p1.Name, p2.Name)
// 打印: p1.Name: mary, p2.Name: mary
}
示例2:指针变量作为形参
func doChange(p1 Person, p2 *Person) { // 结构体是值传递的
p1.Name = "tom"
p2.Name = "golang"
}
func main() {
p3 := Person{}
p4 := new(Person) // p4 为结构体指针
doChange(p3, p4)
fmt.Printf("p3.Name: %v, p4.Name: %v\n", p3.Name, p4.Name) // 形参修改后,实参p4的Name也改变了
// 打印: p3.Name: , p4.Name: golang ---可见,p3.Name没有被修改,而p4.Name修改了
}
总结:
- 结构体作为形参是 值传递的,所以 p3(结构体变量)的Name值没有被改变。
- 对结构体指针所做的修改,是会影响到指针指向的结构体,如 p4(结构体指针)的Name值被修改了。
4.结构体方法
type Person struct {
Name string
}
// 给Person结构体绑定一个方法
func (p Person) getName() string {
return p.Name
}
(p Person)
表示 getName方法 和 Person类型进行绑定- getName方法只能通过 Person 类型的变量来调用,而不能直接调用
- getName方法想在其他包中被访问,则该方法名的首字母必须大写
方法的注意事项
-
结构体方法 和 函数 调用方式的区别:
函数调用: 直接 函数名(…), 如 doChange(p3, p4)
方法调用:结构体变量.方法名(...)
, 如 p.getName() -
指针类型的变量调用方法
为了提高效率,通常使用【方法】和【结构体的指针类型】进行绑定
// 给 Person结构体的指针类型 绑定一个方法
func (p *Person) getName() string {
p.Name = "tom"
return p.Name // 等同于 (*p).Name
}
// 测试
p1 := Person{} // 普通结构体变量
var p2 *Person = &Person{} // p2为结构体指针变量
p1.getName() // 本质上是 (&p1).getName()
p2.getName()
- 方法的访问权限 和 函数一样
方法名
首字母小写
如同 private, 只能在本包访问
方法名首字母大写
如同 public, 可以在本包和其它包访问
5.结构体访问权限
结构体、结构体字段、结构体方法,想在其他包中访问,必须:
- 结构体名称 首字母大写
- 字段名 首字母大写
- 方法名 首字母大写
用工厂模式
来解决权限访问问题
当结构体名、字段名首字母小写,如何在其他包中被访问呢?
utils包:
package utils
type member struct { // 结构体首字母小写,类似 private
Name string
age int
}
// NewMember 创建工厂模式
// 由于 member结构体首字母小写,只能在当前包(utils包)中使用。需提供工厂模式让外界访问。
func NewMember(name string, age int) *member {
return &member{
age : age,
Name : name,
}
}
// age字段首字母小写,也只能在当前包中使用,我们需要提供一个【首字母大写的结构体方法】来访问
func (m *member) GetAge() int {
return m.age
}
main函数中的调用:
func main() {
// 调用工厂模式
// member := utils.NewMember("tom", 20)
var member = utils.NewMember("tom", 20) // member 是指针类型
fmt.Println(member.Name, member.GetAge()) // 打印: tom 20
}
总之,工厂模式是对结构体的一种封装的实现。