类型系统
可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法
type Integer int
func (a Integer) Less(b Integer) boo {
}
只有在需要修改对象的时候,才必须用指针
func (a *Integer) Add(b Integer) {
*a += b
}
go语言中类型都是基于值传递的,要像修改变量内容,职能传递指针
go语言中数组是值传递,要想修改内容,使用指针
var a = [3]int{1, 2, 3}
var b = &a
b[1] ++
go语言中有四个类型,看起来比较像引用类型,实际上是结构内部记录有指针,
数组切片,map,channel 和接口
结构体
type Rect struct {
x, y float64
width, height float64
}
结构体方法
func (r *Recv) Area() float {
return r.width *r.height
}
结构体初始化
rect1 := new(Rect)
rect2 := &Recv{}
rect3 := &Rect{0,0,100,200}
rect4 := &Rect(width: 100, height:200)
在go中未进行显示初始化的变量都被初始化为该类型零值,bool是false, int是0,string类型则是空字符串
在go中没有构造函数,对象创建通常由一个全局函数完成,以NewXXX来命名
匿名组合
go中通过组合的方式来实现其他语言中的继承
type Base struct {
Name string
}
func (base *Base) Foo() {}
func (base *Base) Bar(){}
type Foo struct {
Base
}
func (foo *Foo) Bar(){
foo.Base.Bar()
}
没有改写基类方法时,相应的方法就被继承
还可以通过指针的方式派生,但是此时需要在外部提供一个Base类实例的指针
不管是匿名还是非匿名,被组合的类型所包含的方法虽然都升级成了外部这个组合类的方法,但其实被组合方法调用时接受者并没有改变,
接口组合时的名字冲突
type X struct {
Name string
}
type Y struct {
X
Name string
}
不会有冲突,所有对Y类型Name成员的访问都只会访问最外层的Name
type Logger struct {
Level int
}
type Y struct {
*Logger
Name string
*log.Logger
}
匿名组合类型相当于以其类型名称(去掉 包名部分)作为成员变量的名字。按此规则,Y中相当于存在两个名为Logger的成员,会有编译错误
匿名字段struct 所拥有的全部字段都被隐式引入当前定义的struct
匿名字段和外部struct拥有同名变量时,优先访问外部变量,但是可以通过制定匿名字段来访问匿名字段中的变量。例如:Bob.Human.phone
method
method是附属在一个给定的类型上的,它的语法和函数声明语法几乎一样,只是在func后面增加了一个receiver,也就是method所已从的主体
虽然多个类型的method的名字是一样的,但是如果接收者不一样,那么method就不一样
method里面可以访问接收者的字段
调用method通过“.”访问,就像struct里面访问字段一样
method的接收者可以是普通类型,也可以是指针。普通类型作为receiver仅仅是以副本作为操作对象
method可以定义在自定义类型(type typeName typeLiteral)、内置类型、struct等各种类型上
如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method
同理如果receiver是T,也允许在一个*T类型的变量P上调用这个method
method和匿名字段中的其他变量一样,也是可以继承的,如果在外部类型中定义了同名的method,那么也一样先访问外部定义的method
可见性
没有private protected public等关键字
要想某个符号被其他包访问,需要将该符号定义为以大写字母开头
这个限制只是对包,同一个包中的其他对象都可以访问这些符号
接口
interface
是一组抽象方法的集合,它必须由其他非interface类型实现,而不鞥自我实现。
interface变量的内部实际上有一个slice记录了实现这个interface的其他类型。
空interface
不包含任何method,所有类型都实现了空interface
空interfface可以存储任意类型,有点类似void*
如果一个函数返回interface{} ,就可以返回任意的值
interface变量可以持有任意实现该interface类型的对象
实现了error接口的对象,即实现了Error() string的对象,使用fmt输出时,会调用Error()方法,因此不必再定义String()方法
怎么知道一个interface变量里面实际保存的是那个类型的对象呢Common-ok断言和switch测试
common-ok
value, ok = element.(T) value是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型
如果element里面确实存储了T类型的数值,ok就返回true,否则返回false
switch测试
switch value := element.(type) {
case int:
case string:
case Person:
default:
}
强调:element.(type)不能在switch外的任何逻辑里面使用
侵入式接口
主要表现在实现类需要明确声明自己实现某个接口
设计接口的纠结:
提供哪些接口
如果两个类实现了相同接口,应该把接口放在哪个包里
非侵入式接口
一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,可以进行赋值
影响:
其一,类的继承树并无意义,只需要直到这个类实现了哪些方法
其二,实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义,不用实现规划。
其三,不用为实现一个接口而引入一个包,多引入一个外部包,就以为着更多的耦合。接口由使用方按需定义,无需关心是否有其他模块定义过类似接口
接口赋值
对象赋值给接口,需要对象实现所有接口的方法
type Integer int
func (a Integer) Less(b Integer) bool {}
func (a *Integer) Add(b Integer) {}
type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
}
var a Integer = 1
var b LessAddr = &a 这个赋值是允许的 编译器可以根据指针类型,生成值类型函数
var b LessAddr = a 这个赋值是错误的
两个接口具有相同的方法列表就可以赋值,另外如果接口A的方法列表是接口B的子集,那么B也可以赋值给A
var file1 = Writer = ...
if file6, ok := file1.(*File); ok {} 查询接口指向的对象是否是*File类型
var v1 interface{} = ...
swititch v := v1.(type) 查询v1对象的类型
反射也可以查询类型
接口组合
type ReadWriter interface {
Reader
Writer
}
等价于
type ReadWriter interface {
Read(p []byte)(n int, err error)
Write(p []byte)(n int, err error)
}
如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式包含interface1的所有method
Any类型
interface{} 可以指向任何对象的Any类型
一个函数可以接受任意类型参数,会将其声明为interface{}