结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值。
结构体也是值类型,因此可以通过 new 函数来创建。
组成结构体类型的那些数据称为 字段(fields)。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。
结构体的概念在软件工程上旧的术语叫 ADT(抽象数据类型:Abstract Data Type),在一些老的编程语言中叫 记录(Record)。
1、结构体定义
type identifier struct {field1 type1field2 type2...}
type T struct {a, b int} 也是合法的语法,它更适用于简单的结构体。
示例:
package mainimport "fmt"type struct1 struct {i1 intf1 float32str string}func main() {//使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)//下面四行代码和 ms := &struct1{10, 15.5, "Chris"} 是相等的ms := new(struct1)ms.i1 = 10ms.f1 = 15.5ms.str= "Chris"fmt.Printf("The int is: %d\n", ms.i1)fmt.Printf("The float is: %f\n", ms.f1)fmt.Printf("The string is: %s\n", ms.str)fmt.Println(ms)}
2、带标签的结构体(Tag)
结构体中的字段除了有名字和类型外,还可以有一个可选的标签(Tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 reflect 能获取它。
在学习操作JSON和数据库(ORM)时,必须借助结构体的Tag来完成映射。
package mainimport ("fmt""reflect")type TagType struct { // tagsfield1 bool "An important answer"field2 string "The name of the thing"field3 int "How much there are"}func main() {tt := TagType{true, "Barak Obama", 1}for i := 0; i < 3; i++ {refTag(tt, i)}}func refTag(tt TagType, ix int) {ttType := reflect.TypeOf(tt)ixField := ttType.Field(ix)fmt.Printf("%v\n", ixField.Tag)}
3、匿名字段和内嵌结构体
结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。
package mainimport "fmt"type innerS struct {in1 intin2 int}type outerS struct {b intc float32int // anonymous fieldinnerS //anonymous field}func main() {outer := new(outerS)outer.b = 6outer.c = 7.5outer.int = 60outer.in1 = 5outer.in2 = 10fmt.Printf("outer.b is: %d\n", outer.b)fmt.Printf("outer.c is: %f\n", outer.c)fmt.Printf("outer.int is: %d\n", outer.int)fmt.Printf("outer.in1 is: %d\n", outer.in1)fmt.Printf("outer.in2 is: %d\n", outer.in2)// 使用结构体字面量outer2 := outerS{6, 7.5, 60, innerS{5, 10}}fmt.Println("outer2 is:", outer2)}
4、方法
结构体就像是类的一种简化形式,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。
接收者类型可以是任何类型,不仅仅是结构体类型。
//定义方法的一般格式func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
如果方法不需要使用接受者的值,可以用 _ 替换它,比如:
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的:
func (a *denseMatrix) Add(b Matrix) Matrixfunc (a *sparseMatrix) Add(b Matrix) Matrix
5、内嵌结构体的方法和继承
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型。这个机制提供了一种简单的方式来模拟经典面向对象语言中的子类和继承相关的效果。
package mainimport ("fmt""math")type Point struct {x, y float64}func (p *Point) Abs() float64 {return math.Sqrt(p.x*p.x + p.y*p.y)}type NamedPoint struct {Pointname string}func main() {n := &NamedPoint{Point{3, 4}, "Pythagoras"}fmt.Println(n.Abs()) // 打印5}
6、多重继承
多重继承指的是类型获得多个父类型行为的能力,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。
package mainimport ("fmt")type Camera struct{}func (c *Camera) TakeAPicture() string {return "Click"}type Phone struct{}func (p *Phone) Call() string {return "Ring Ring"}type CameraPhone struct {CameraPhone}func main() {cp := new(CameraPhone)fmt.Println("Our new CameraPhone exhibits multiple behaviors...")fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())fmt.Println("It works like a Phone too: ", cp.Call())}
7、String方法
在结构体被打印时的回调方法,可用来格式化结构体的打印格式。(类似Java中的toString方法)
package mainimport ("fmt""strconv")type TwoInts struct {a intb int}func (tn *TwoInts) String() string {return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"}func main() {two1 := new(TwoInts)two1.a = 12two1.b = 10fmt.Printf("two1 is: %v\n", two1)fmt.Println("two1 is:", two1)fmt.Printf("two1 is: %T\n", two1)fmt.Printf("two1 is: %#v\n", two1)}
8、垃圾回收和 SetFinalizer
Go 开发者不需要写代码来释放程序中不再使用的变量和结构占用的内存,在 Go 运行时中有一个独立的进程,即垃圾收集器(GC),会处理这些事情,它搜索不再使用的变量然后释放它们的内存。可以通过 runtime 包访问 GC 进程。
通过调用 runtime.GC() 函数可以显式的触发 GC,但这只在某些罕见的场景下才有用,比如当内存资源不足时调用 runtime.GC(),它会在此函数执行的点上立即释放一大片内存,此时程序可能会有短时的性能下降(因为 GC 进程在执行)。
如果想知道当前的内存状态,可以使用:
// fmt.Printf("%d\n", runtime.MemStats.Alloc/1024)// 此处代码在 Go 1.5.1下不再有效,更正为var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("%d Kb\n", m.Alloc / 1024)
如果需要在一个对象 obj 被从内存移除前执行一些特殊操作,比如写到日志文件中,可以通过如下方式调用函数来实现:
runtime.SetFinalizer(obj, func(obj *typeObj))
在对象被 GC 进程选中并从内存中移除以前,SetFinalizer 都不会执行,即使程序正常结束或者发生错误。
