go语言的一些基础总结

最近在学习golang,使用的教材是《the way to go》,github上面有该书的中文版,这里记录了一些基础的内容。
1、类型转换
使用type为一个类型定义一个别名。
type  width  int8
这里 int8 就是width的底层类型,二者可以相互转换(当然是强制类型转换)
2、类型 T(或 *T)上的所有方法的集合叫做类型 T(或 *T)的方法集。
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在


func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix


如上面那样,虽然都是Add方法,但是因为接收者不同,所以是允许的。


别名类型不能有它原始类型(底层)上已经定义过的方法。


3、类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。试图在 int 类型上定义方法会得到一个编译错误:
cannot define new methods on non-local type int
比如想在 time.Time 上定义如下方法:
func (t time.Time) first3Chars() string {
return time.LocalTime().String()[0:3]
}
类型在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。


4、指针接收者和值接收者
       指针方法和值方法都可以在指针或非指针上被调用


5、方法和未导出字段
go语言规定:如果一个类型名称的首字母是大写的话,那么这个类型是导出的,也就是说我们可以在其他包中访问。反之则是不可导出的。
package person
type Person struct {
firstName string
lastName  string
}
如上,类型Person是导出的,但是它的字段 firstName 和 lastName 并没有被导出,因为这两个字段的首字母是小写,也就是说,我们在其他包中可以访问 Person 这个类型,但是不能访问它的 firstName 和 lastName 字段。


6、结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。
       Go 语言中的继承是通过内嵌或组合来实现的,所以可以说,在 Go 语言中,相比较于继承,组合更受青睐。
内嵌匿名结构体的话,可能会造成命名冲突:当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?
  1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
  2. 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。
例子:
type A struct {a int}
type B struct {a, b int}


type C struct {A; B}
var c C;
规则 2:使用 c.a 是错误的,到底是 c.A.a 还是 c.B.a 呢?会导致编译器错误:ambiguous DOT reference c.a disambiguate with either c.A.a or c.B.a。
type D struct {B; b float32}
var d D;
规则1:使用 d.b 是没问题的:它是 float32,而不是 B 的 b。如果想要内层的 b 可以通过 d.B.b 得到。


7、内嵌类型的方法和继承
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 继承 了这些方法:将父类型放在子类型中来实现亚型。这个机制提供了一种简单的方式来模拟经典面向对象语言中的子类和继承相关的效果,也类似 Ruby 中的混入(mixin)。
下面是一个示例:假定有一个 Engine 接口类型,一个 Car 结构体类型,它包含一个 Engine 类型的匿名字段:
type Engine interface {
Start()
Stop()
}


type Car struct {
Engine
}
我们可以构建如下的代码:
func (c *Car) GoToWorkIn() {
// get in car
c.Start()
// drive to work
c.Stop()
// get out of car
}




内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。当然类型可以有只作用于本身实例而不作用于内嵌“父”类型上的方法,
可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。
因为一个结构体可以嵌入多个匿名类型,所以实际上我们可以有一个简单版本的多重继承,就像:type Child struct { Father; Mother}。
结构体内嵌和自己在同一个包中的结构体时,可以彼此访问对方所有的字段和方法。


8、接口:接口(interface)定义了一种规范,实现了该接口的所有类型都必须符合接口的规范。
接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。
type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
}
我们可以声明一个接口类型的变量,var ai Namer。任何实现了Namer接口类型的变量都可以赋值给ai。
注意:不能声明指向接口值的指针。




9、接口的匿名嵌套
  原理和struct匿名嵌套相同


10、类型断言
如上声明了一个接口类型的变量:var  ai Namer;那么任何实现了Namer接口的变量都可以赋值给 ai,所以要有一种方法可以判断 ai 的动态类型。那就是类型断言:v,ok := varI.(T),其中 varI 必须是一个接口变量,用于判断 varI 类型此时是否是一个 T 类型。
注意:如果类型断言成功,那么 v 就是一个 T 类型的变量,可以通过 v 来访问 T 类型的任何属性。




11、接口的方法集调用方式
弄懂下面的例子就可以了:
作用于变量上的方法(这里的变量指的是非接口类型的其他变量)实际上是不区分变量到底是指针还是值的。当碰到接口类型值时,这会变得有点复杂,原因是接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误。考虑下面的程序:
示例 11.5 methodset2.go:
package main


import (
"fmt"
)


type List []int


func (l List) Len() int {
return len(l)
}


func (l *List) Append(val int) {
*l = append(*l, val)
}


type Appender interface {
Append(int)
}


func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}


type Lener interface {
Len() int
}


func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}


func main() {
// A bare value
var lst List
// compiler error:
// cannot use lst (type List) as type Appender in argument to CountInto:
//List does not implement Appender (Append method has pointer receiver)
// CountInto(lst, 1, 10)
if LongEnough(lst) { // VALID:Identical receiver type
fmt.Printf("- lst is long enough\n")
}


// A pointer value
plst := new(List)
CountInto(plst, 1, 10) //VALID:Identical receiver type
if LongEnough(plst) {
// VALID: a *List can be dereferenced for the receiver
fmt.Printf("- plst is long enough\n")
}
}
讨论
在 lst 上调用 CountInto 时会导致一个编译器错误,因为 CountInto 需要一个 Appender,而它的方法 Append 只定义在指针上。 在 lst 上调用 LongEnough 是可以的因为 'Len' 定义在值上。
在 plst 上调用 CountInto 是可以的,因为 CountInto 需要一个 Appender,并且它的方法 Append 定义在指针上。 在 plst 上调用 LongEnough 也是可以的,因为指针会被自动解引用。
总结
在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 P 直接可以辨识的:
  ● 指针方法可以通过指针调用
  ● 值方法可以通过值调用
  ● 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
  ● 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
译注
Go 语言规范定义了接口方法集的调用规则:
  ● 类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
  ● 类型 T 的可调用方法集包含接受者为 T 的所有方法
  ● 类型 T 的可调用方法集不包含接受者为 *T 的方法




12、空接口
空接口是最小的接口,不包含任何方法,对他的实现不做任何要求。
type Any interface {}
任何其他类型都默认实现了空接口。可以给一个空接口类型的变量 var val interface {} 赋任何类型的值。


每个 interface {} 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。


13、接口到接口的赋值
一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 'Go' 语言动态的一面,可以拿它和 Ruby 和 Python 这些动态语言相比较。
假定:
var ai AbsInterface // declares method Abs()
type SqrInterface interface {
    Sqr() float
}
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}
那么下面的语句和类型断言是合法的:
empty = pp                // everything satisfies empty
ai = empty.(AbsInterface) // underlying value pp implements Abs()
// (runtime failure otherwise)
si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesn’t
empty = si             // *Point implements empty set
// Note: statically checkable so type assertion not necessary.
下面是函数调用的一个例子:
type myPrintInterface interface {
print()
}


func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
x 转换为 myPrintInterface 类型是完全动态的:只要 x 的底层类型(动态类型)定义了 print 方法这个调用就可以正常运行。






13、反射
反射是一个很重要的概念




14、接口和动态类型
在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为 类 的概念:类包含它们两者,并且不能剥离。
Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。


接收一个(或多个)接口类型作为参数的函数,实参可以是任何实现了该接口的类型。 实现了某个接口的类型可以被传给任何以此接口为参数的函数 。


15、动态方法调用
像 Python,Ruby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析(它们很可能有像 responds_to 这样的方法来检查对象是否可以响应某个方法,但是这也意味着更大的编码量和更多的测试工作)
Go 的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{} 这样的“泛型”上,你可以通过类型断言来检查变量是否实现了相应接口。
例如,你用不同的类型表示 XML 输出流中的不同实体。然后我们为 XML 定义一个如下的“写”接口(甚至可以把它定义为私有接口):
type xmlWriter interface {
WriteXML(w io.Writer) error
}
现在我们可以实现适用于该流类型的任何变量的 StreamXML 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 encodeToXML 来完成相应工作:
// Exported XML streaming function.
func StreamXML(v interface{}, w io.Writer) error {
if xw, ok := v.(xmlWriter); ok {
// It’s an  xmlWriter, use method of asserted type.
return xw.WriteXML(w)
}
// No implementation, so we have to use our own function (with perhaps reflection):
return encodeToXML(v, w)
}


// Internal XML encoding function.
func encodeToXML(v interface{}, w io.Writer) error {
// ...
}




16、空接口和函数重载
我们知道Go语言中函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
函数 fmt.Printf 就是这样做的:
fmt.Printf(format string, a ...interface{}) (n int, errno error)
这个函数通过枚举 slice 类型的实参动态确定所有参数的类型。




17、接口的嵌套和继承
① 首先下面是接口的嵌套:这个和struct的匿名嵌套是相同的。
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
比如接口 File 包含了 ReadWrite 和 Lock 的所有方法,它还额外有一个 Close() 方法。
type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}


type Lock interface {
    Lock()
    Unlock()
}


type File interface {
    ReadWrite
    Lock
    Close()
}
②  然后是接口的继承
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时(使用指针的原因:我个人觉得应该是因为指针类型的方法集既包含了接收者是指针类型的方法,又包含了接收者是值类型的方法。),这个类型就可以使用(另一个类型)所有的接口方法。
例如:
type Task struct {
Command string
*log.Logger
}
这个类型的工厂方法像这样:
func NewTask(command string, logger *log.Logger) *Task {
return &Task{command, logger}
}
当 log.Logger 实现了 Log() 方法后,Task 的实例 task 就可以调用该方法:
task.Log()
类型可以通过继承多个接口来提供像 多重继承 一样的特性:
type ReaderWriter struct {
*io.Reader
*io.Writer
}
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少。这被认为是 Go 编程中的重要的最佳实践。


注意:第①条是接口的嵌套,是接口之间的嵌套。第②条是接口的继承,是一个struct继承两一个struct的所有接口。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值