Contents

golang-学习笔记-结构(struct)与方法(method)

go官方文档 go语言参考

在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记

可以通过 new 函数来创建

组成结构体类型的那些数据称为 字段(fields)

结构体定义

1
2
3
4
5
6
7
type identifier struct {
    field1 type1
    field2 type2
    ...
}

type T struct {a, b int}

结构体里的字段都有 名字,如果字段在代码中从来也不会被用到,那么可以命名它为 _。

结构体的字段可以是任何类型

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)

使用 fmt.Println 打印一个结构体的默认输出可以很好的显示它的内容,类似使用 %v 选项。

使用点号符给字段赋值:structname.fieldname = value.Go 语言中这叫 选择器(selector).为选择器符(selector-notation).指针使用.操作会自动解引用

结构体能够定义在函数中,但是这种情况下只能函数可见,并且该类型不能作为返回值

初始化一个结构体实例:

1
2
3
4
5
6
ms := &struct1{10, 15.5, "Chris"}

var ms struct1
ms = struct1{10, 15.5, "Chris"}

intr := Interval{end:5, start:1} 

混合字面量语法(composite literal syntax)&struct1{a, b, c} 是一种简写,底层仍然会调用 new (),这里值的顺序必须按照字段顺序来写。

结构体类型和字段的命名遵循可见性规则

结构体的内存布局:结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体

递归结构体:

1
2
3
4
type Node struct {
    data    float64
    su      *Node
}

结构体转换:当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型,它们可以互相转换,使用强制类型转换typeA(b)

使用自定义包中的结构体

大写字母开头可见

带标签的结构体

标签的内容不可以在一般的编程中使用,只有包 reflect 能获取它,reflect.Field结构体的Tag属性存这个字符串,Tag字段的Get可以获取某一个键值对不过也是返回字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type TagType struct { // tags
    field1 bool   "An important answer"
    field2 string "The name of the thing"
    field3 int    "How much there are"
}

tt := TagType{true, "Barak Obama", 1}

ttType := reflect.TypeOf(tt)
ixField := ttType.Field(ix)
fmt.Printf("%v\n", ixField.Tag)

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。

匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体

只有内嵌结构体能继承方法,类型别名也不能继承方法

go的继承行为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
type innerS struct {
    in1 int
    in2 int
}

type outerS struct {
    b    int
    c    float32
    int  // anonymous field
    innerS //anonymous field
}

outer := new(outerS)
outer.int = 60
outer.in1 = 5

// 使用结构体字面量
outer2 := outerS{6, 7.5, 60, innerS{5, 10}}

在一个结构体中对于每一种数据类型只能有一个匿名字段

访问其他包的匿名字段时直接通过最后的结构体名访问即可,比如

1
2
3
4
5
6
type PriorityQueue struct {
  sort.IntSlice
}

a := PriorityQueue{[]int{1, 2, 3}}
fmt.Println(a.IntSlice[0])

命名冲突

当两个字段拥有相同的名字(可能是继承来的名字):

  1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
  2. 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。
1
2
3
4
5
type A struct {a int}
type B struct {a, b int}

type C struct {A; B}
var c C;

使用 c.a 是错误的,到底是 c.A.a 还是 c.B.a 呢?会导致编译器错误

方法

方法是什么

1
2
3
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

recv.Method1()

如果方法不需要使用 recv 的值,可以用 _ 替换它,比如:

1
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }

recv 就像是面向对象语言中的 this 或 self,仍然是值拷贝

类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。

有一个间接的方式:可以先定义该类型(比如:int 或 float)的别名类型(这里指的是不使用"=“的类型定义),然后再为别名类型定义方法。

函数和方法的区别

方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。

没啥区别,多了个默认参数recv,另外recv可以自动进行取地址,即使用引用可以接收示例

指针或值作为接收者

鉴于性能的原因,recv 最常见的是一个指向 receiver_type 的指针(因为我们不想要一个实例的拷贝,如果按值调用的话就会是这样),特别是在 receiver 类型是结构体时,就更是如此了。

如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。

指针方法和值方法都可以在指针或非指针上被调用

方法和未导出字段

可以给未导出字段添加Getter或Setter方法

并发访问对象:使用sync包进行并发安全控制

内嵌类型的方法和继承

当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型。

如何在类型中嵌入功能

主要有两种方法来实现在类型中嵌入功能:

A:聚合(或组合):包含一个所需功能类型的具名字段。

B:内嵌:内嵌(匿名地)所需功能类型

多重继承

但是在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承

通用方法和方法命名

约定,鸭形语言

和其他面向对象语言比较 Go 的类型和方法

总结

类型就是类(数据和关联的方法)

代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 组件编程(Component Programming)

类型的 String() 方法和格式化描述符

String() 方法: fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。

不要在 String() 方法里面调用涉及 String() 方法的方法,会陷入无限递归。

垃圾回收和 SetFinalizer

Go 运行时中有一个独立的进程,即垃圾收集器(GC),可以通过 runtime 包访问 GC 进程

runtime.GC() 函数显式的触发 GC,

给出已分配内存的总量,单位是 Kb

1
2
3
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%d Kb\n", m.Alloc / 1024)

设置一个对象 obj 被从内存移除前执行一些特殊操作:

1
runtime.SetFinalizer(obj, func(obj *typeObj))

结构体比较

结构体是否可比较:

  • 结构体名同(如果某一个结构体为匿名结构体也可比较)
  • 属性类型同
  • 属性个数同
  • 属性名同
  • 属性顺序同
  • 属性可比较(map,slice不可比较,chan可比较)

结构体是否相同:

  • 属性值同
 |