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 *Personnew(Person)(*结构体指针).字段名结构体指针.字段名

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)

方法的注意事项

结构体变量.方法名(...)

为了提高效率,通常使用【方法】和【结构体的指针类型】进行绑定

// 给 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()
  • 方法的访问权限 和 函数一样
首字母小写首字母大写

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
}

总之,工厂模式是对结构体的一种封装的实现。