Go 结构体(Struct)
引用曾经看到的一篇文章里面对 Golang 中结构体的描述,如果说 Golang 的基础类型是原子,那么
结构体就是分子。我们都知道分子是由原子组成的,换言之就是结构体里面可以包含基础类型、切片、
字典、数组以及结构体自身。
结构体类型的定义
结构体定义的一般方式
type identifier struct { field1 type1 field2 type2 ... }
结构体里的字段都有名字,而对于字段在代码中从来也不被使用到,那么可以命名它为_。对于相同类型的
字段我们可以使用简写的形式,比如:
type T struct { a,b int }
还有一个需要注意的地方是结构体中字段的大小写,首字母大写表示公有变量,首字母小写表示私有变量,
相当于其他语言类成员变量的Public、Private ,但是这个公有变量和私有变量在 Go 中是相对于 Package 来
说的,在同一个 Package 都是公有的。
结构体变量的创建
1 最常见的创建结构体变量形式
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c Circle = Circle{ x:100, y:100, Radius:50, } fmt.Println(c) } -------------------------------- {100 100 50}
通过显示指定结构体内部字段的名称和初始值初始化结构体,可以只指定部分字段的初始值,或一个都不指定,
那些没有被指定初始值的字段会自动初始化为相应类型的零值。
2 结构体变量创建的第二种形式是不显示指定字段而是根据字段顺序初始化,但是这样需要显示的提供所有字段的
初始值
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c Circle = Circle{100,100,50} fmt.Println(c) } ------------------------------- {100 100 50}
3 使用 new() 函数创建结构体变量
结构体变量和普通变量都有指针形式,使用取地址符就可以得到结构体的指针类型
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c *Circle = &Circle{100,100,50} fmt.Printf("%+v\n", c) } ------------------------------------------ &{x:100 y:100 Radius:50}
下面看一下使用 new() 创建一个零值结构体,所有的字段都会被初始化成对应类型的零值
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c *Circle = new(Circle) fmt.Printf("%+v\n", c) } ----------------------------------- &{x:0 y:0 Radius:0}
注意:new 函数返回的是指针类型
结构体的参数传递
看一个实例
package main import "fmt" // 声明一个结构体 type employee struct { name,address string height,weight float64 } // 定一个方法,该方法的参数是一个结构体,主要用于修改结构体成员中name的值 func modifyAttribute(emy employee) { emy.name = "newer" fmt.Println(emy) } func main() { // 初始化结构体 emy := employee{ name:"xiaoming", address:"beijing", height:172.0, weight:75.3, } // 打印修改前的值 fmt.Println(emy) // 调用modifyAttribute modifyAttribute(emy) // 打印修改后值 fmt.Println(emy) } ---------------------------------- 输出结果 {xiaoming beijing 172 75.3} {newer beijing 172 75.3} {xiaoming beijing 172 75.3}
从上面的输出结果上来看,虽然在 modifyAttribute 方法中修改了 name 值,但是在 main 函数中打印 name 的
值并没有变化,说明这是一个值传递
我们把 modifyAttribute 函数的参数变成结构体的指针类型,如下
func modifyAttribute(emy *employee) { emy.name = "newer" fmt.Println(emy) } func main() { // 初始化结构体 emy := employee{ name:"xiaoming", address:"beijing", height:172.0, weight:75.3, } // 打印修改前的值 fmt.Println(emy) // 调用modifyAttribute modifyAttribute(&emy) // 打印修改后值 fmt.Println(emy) } ---------------------------- 输出结果 {xiaoming beijing 172 75.3} &{newer beijing 172 75.3} {newer beijing 172 75.3}
我们看到在函数 modifyAttribute 中的修改影响到了 main 函数中的 name 值,这里是引用传递
再看一个例子:编写扩大圆半径的函数
package main import "fmt" // 定义一个结构体 Circle type Circle struct { x int y int Radius int } // 通过值传递扩大圆半径 func expandByValue(c Circle) { c.Radius *= 2 } // 通过引用传递扩大圆半径 func expandByPointer(c *Circle) { c.Radius *= 2 } func main() { c := Circle{ Radius:50, } expandByValue(c) fmt.Println(c) expandByPointer(&c) fmt.Println(c) } -------------------------------- 输出结果 {0 0 50} {0 0 100}
我们可以从上面的输出中再次看到通过值传递,在函数里面修改结构体的状态不会影响原有结构
体的状态,通过值传递就不一样。
结构体方法
Go 语言不是面向对象的语言,在 Go 语言中没有类的概念,结构体正是类的替代品。类可以附加很多成员方法,
结构体也可以。看一个实例,如何给结构体绑定方法:
package main import ( "fmt" "math" ) // 定义一个结构体 Circle type Circle struct { x int y int Radius int } // 计算圆的面积 第一个括号里面表示的是方法的接收者 这里方法的接收者是结构体 Circle // Area() 表示函数名 float64 表示函数的返回值类型 func (c Circle) Area() float64{ return math.Pi * float64(c.Radius) * float64(c.Radius) } // 计算圆的周长 func (c Circle) Circumference() float64 { return 2 *math.Pi * float64(c.Radius) } func main() { // 初始化结构体 c := Circle{ Radius:50, } fmt.Println(c.Area(),c.Circumference()) } ------------------------------------------------- 输出结果 7853.981633974483 314.1592653589793
结构体的指针方法
如果使用结构体方法的形式给 Circle 增加一个扩大圆半径的方法,会发现半径还是扩大不了
func (c Circle) expand() { c.Radius *= 2 }
这个方法和前面的 expandByValue 函数是等价的,只是调整了一下第一个参数的位置,在参数传递的时候依然是值传递
,所以,还是无法起到扩大圆半径的作用,这个时候就需要使用结构体的指针方法
func (c *Circle) expand() { c.Radius *= 2 }
结构体指针方法和值方法在调用上没有区别,只是一个可以改变结构体内部状态,另一个不会。另外指针方法可以使用结
构体变量调用,值方法也可以使用结构体指针变量使用
结构体变量调用指针方法(比如调用计算圆周长的方法):
package main import ( "fmt" "math" ) // 定义一个结构体 Circle type Circle struct { x int y int Radius int } // 计算圆的周长 func (c *Circle) Circumference() float64 { c.Radius *= 2 return 2 *math.Pi * float64(c.Radius) } func main() { // 初始化结构体 c := Circle{ Radius:50, } fmt.Println(c.Circumference()) } ----------------------------------------- 输出结果 628.3185307179587
使用结构体指针变量调用值方法:
package main import ( "fmt" "math" ) // 定义一个结构体 Circle type Circle struct { x int y int Radius int } // 计算圆的面积 func (c Circle) Area() float64{ return math.Pi * float64(c.Radius) * float64(c.Radius) } func main() { // 初始化结构体 c := &Circle{ Radius:50, } fmt.Println(c.Area()) } --------------------------------- 输出结果 7853.981633974483
内嵌结构体
结构体作为一种变量可以嵌套在另一个结构体中作为一个字段使用,这种内嵌结构体在 Go 语言中称之为
组合
package main import "fmt" // 定义一个结构体 type Pointer struct { x int y int } func (p Pointer) show() { fmt.Println(p.x,p.y) } // 定义另一个结构体 type Circle struct { // 将结构体 Pointer 嵌套在 Circle loc Pointer Radius int } func main() { c := Circle{ loc:Pointer{ x:100, y:100, }, Radius:50, } fmt.Println(c) fmt.Println(c.loc) fmt.Println(c.loc.x,c.loc.y) c.loc.show() } ----------------------------------- 输出结果 {{100 100} 50} {100 100} 100 100 100 100
匿名内嵌结构体
还有一种特殊的内嵌结构体形式,内嵌的结构体不提供名称。这时外面的结构体直接继承内嵌结构体的所有
内部字段和方法
package main import "fmt" // 定义一个结构体 type Pointer struct { x int y int } func (p Pointer) show() { fmt.Println(p.x,p.y) } // 定义另一个结构体 type Circle struct { // 匿名内嵌结构体 Pointer Radius int } func main() { c := Circle{ Pointer:Pointer{ x:100, y:100, }, Radius:50, } fmt.Println(c) fmt.Println(c.Pointer) fmt.Println(c.x,c.y) //继承字段 fmt.Println(c.Pointer.x,c.Pointer.y) c.show() // 继承方法 c.Pointer.show() } -------------------------------------- 输出结果 {{100 100} 50} {100 100} 100 100 100 100 100 100 100 100
Go 语言的结构体没有多态性
我们知道面向对象语言的三大特性,封装、继承、多态。其中多态是指父类定义的方法可以调用子类实现的方法,不同的子类有不同的实现方法,
从而给父类的方法带来了多样的不同行为。而 Go 不是面向对象语言,它的结构体也不支持结构体,如下示例
package main import "fmt" // 定义一个fruit结构体 type Fruit struct { } func (f Fruit) eat() { fmt.Println("eat Fruit") } func (f Fruit) enjoy() { fmt.Println("smell first") f.eat() fmt.Println("clean finally") } // 定一个apple结构体 type Apple struct { Fruit } func (a Apple) eat() { fmt.Println("eat apple") } // 定义一个banana结构体 type Banana struct { Fruit } func (b Banana) eat() { fmt.Println("eat banana") } func main() { apple := Apple{} banana := Banana{} apple.enjoy() banana.enjoy() } ----------------------------- 输出结果 smell first eat Fruit clean finally smell first eat Fruit clean finally
从上面的输出结果中可以看到,虽然外部结构体可以继承内部结构体的方法和字段,但是外部结构体的方法不能覆盖内部结构体的方法,
enjoy 方法调用的 eat 方法还是 Fruit 自己的 eat 方法,它并没有被外面结构体的方法覆盖掉