类型
在声明一个新类型之后,声明一个该类型的方法之前,需先确定:这个类型的本质是什么?如果给这个类型增加或删除某个值,是要创建一个新值,还是要更改当前的值?如果要新值,就选择值接收者;如果要修改当前值,就选择指针接收者。这决定了 程序内部传递这个类型的值的方式:是按值做传递,还是按指针做传递。
1. 内置类型
包含数值类型、字符串类型和布尔类型。
这些类型本质上都是原始类型,在操作其值时,应该传递其对应的值的副本。
2. 引用类型
包含切片、映射、通道、接口和函数类型。
每个引用类型创建的标头值(引用类型创建的变量),都包含一个指向底层数据结构的指针。
所以通过复制来传递一个引用类型的值的副本,本质上就是在共享底层数据结构。
3. 结构类型
待补充
但结构类型的本质既可以是原始的,也可以是非原始的。
4. 看一个标准库中的接口 及 实现例子
4.1 io.Writer接口
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
Write(p []byte) (n int, err error)
}
4.2 bytes.Buffer实现了该接口
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m := b.grow(len(p))
return copy(b.buf[m:], p), nil
}
4.3 接口实现多态
当方法的接收参数是某个接口时,可以将不同的实现该接口的值作为参数传递给方法,从而实现采取不同行为的能力。
方法fmt.Fprintf
的第一个参数是io.Writer
接口
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
方法io.Copy
的第一个参数也是io.Writer
接口
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
4.4 简单的例子展示io多态
func ByteBuffer() {
var b bytes.Buffer
//将字符串写入Buffer
b.Write([]byte("Hello go"))
//使用Fprintf将字符串拼接到Buffer
fmt.Fprintf(&b, ", nice to meet you")
//将Buffer的内容写到Stdout
io.Copy(os.Stdout, &b)
}
最终输出结果:
Hello go, nice to meet you
5. Go语言的接口规则
因为涉及到指针,规则相比Java来说略微复杂一些。
Go
语言的方法有接收者这一说词,从Java
的角度来看,就是方法的调用者。在Java
中,调用者就是调用者。但是Go
语言中,分值接收者与指针接收者。但规则不外乎如下两种:
如果一个接口的实现类型,用指针接收者来作为方法的接收者(其实就是Java的调用者),那么只能用该类型的指针对象来调用该接口。
如果一个接口的实现类型,用值接收者来作为方法的接收者,那么既可以用指针对象也可以用值的副本来调用该接口。
以下是Go
语言给出的方法集规则:
Values | Methods Receivers | remark |
---|---|---|
T | (t T) | 如2 |
*T | (t T) and (t *T) | 如1 |
Methods Receivers | Values | remark |
---|---|---|
(t T) | T and *T | 如2 |
(t *T) | *T | 如1 |
5. 多态
如下实现了一个简单的多态:
//person接口
type personInter interface {
sayName()
}
//用户
type user struct {
name string
}
func (u *user) sayName() {
fmt.Printf("User name is %s \n", u.name)
}
//管理员
type admin struct {
name string
}
func (u *admin) sayName() {
fmt.Printf("Admin name is %s \n", u.name)
}
func Test() {
logger := user{"logger"}
//letsSayYourName(logger) //这是错误调用,不能用值的副本logger去调用接收者为指针接收者的实现方法
letsSayYourName(&logger)
admin := admin{"admin"}
letsSayYourName(&admin)
}
//方法入参为接口,只要实现了该接口的实现类型都可作为入参
func letsSayYourName(person personInter) {
person.sayName()
}
最终输出:
User name is logger
Admin name is admin
6. 嵌入类型
先介绍一下Go
语言所谓的嵌入类型:
嵌入类型是将已有的类型直接声明在新的结构类型中,被嵌入的类型被称为新的外部类型的内部类型。
个人理解Go
语言中的嵌入类型类似于Java
中的组合。
6.1 示例1 -- 外部类型调用内嵌类型的方法
type animal struct {
name string
}
func (u *animal) sayName() {
fmt.Printf("name is %s \n", u.name)
}
//管理员类型,内嵌一个user类型
type person struct {
animal //嵌入类型
name string
}
func TestInnerType() {
ad := person{animal{"logger"}, "admin"}
ad.animal.sayName()
ad.sayName()
}
输出:
name is logger
name is logger
小结:
- 可以将内嵌类型的方法提升到外部类型
-
ad.sayName()
调用内嵌类型的方法输出的还是内嵌类型的属性
6.2 示例2 -- 外部类型调用内嵌类型的实现接口
//接口
type foodInter interface {
sayFoodName()
}
//苹果
type apple struct {
name string
}
//超市
type market struct {
apple //内嵌类型 -- 苹果
}
func (a *apple) sayFoodName() {
fmt.Printf("food name is %s \n", a.name)
}
func TestInnerTypeInterface() {
walmart := market{ apple{"ApplyX"}}
//重点是这句
//用于实现接口的内部类型的方法,被提升到了外部类型
commonSayName(&walmart)
}
func commonSayName(food foodInter) {
food.sayFoodName()
}
输出:
food name is ApplyX
小结:
- 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。
6.3 示例3 -- 外部类型覆盖内嵌类型的实现接口
相比于示例2,增加了类型market
自己的实现方法以此覆盖内嵌类型的实现接口,并在类型market
中添加了属性marketName
以便输出展示。
//接口
type foodInter interface {
sayFoodName()
}
//苹果
type apple struct {
name string
}
//超市
type market struct {
marketName string
apple //内嵌类型 -- 苹果
}
func (a *apple) sayFoodName() {
fmt.Printf("food name is %s \n", a.name)
}
func (m *market) sayFoodName() {
fmt.Printf("market name is %s \n", m.marketName)
}
func TestInnerTypeInterface() {
walmart := market{ "walmart", apple{"ApplyX"}}
//重点是这句
//用于实现接口的内部类型的方法,被提升到了外部类型
commonSayName(&walmart)
}
func commonSayName(food foodInter) {
food.sayFoodName()
}
输出:
market name is walmart
小结:
- 由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。
7. 公开或未公开的标识符
类似于Java
中的public、private
等,你可以控制方法或变量的访问权限。Go
语言当然也提供了这种功能。
Go
语言支持从包里公开或隐藏标识符。如果一个标识符,如下示例中的counter
以小写字母开头,即包外代码不可见(类似Java的private),如果一个标识符,如下示例中的PublicCounter
以大写字母开头,这个标识符就是公开的,即包外代码可见(类似Java的public)。
7.1 原始类型
//声明一个未公开的计数器
type counter int
//声明一个公开的计数器
type PublicCounter int
当然,我们也可以给未公开的标识符,如counter
写一个公开的取值函数,或者写一个工厂函数新建一个counter
。
//取值函数
func GetCounter(value int) counter {
return counter
}
//工厂函数
func New(value int) counter {
return counter(value)
}
工厂函数命名为New
只是Go
语言中的一个习惯。
7.2 结构类型中的未公开字段
结构类型中带有未公开类型的字段
//公开的用户类型
type Admin struct {
Name string //公开的Name字段
email string //未公开的email字段
}
如果在其它包中创建该类型,并赋值email
字段,在定义阶段是不会报错的,如下不会直接报错(identifier
是包名):
ad := identifier.Admin{"admin", "admin@email.com"}
但是在运行的时候,会报错,错误如下:
# command-line-arguments
.\main.go:43: implicit assignment of unexported field 'email' in identifier.Admin literal
7.3 访问未公开的内嵌对象中的公开字段
在identifier
包中定义如下结构类型,apple
是未公开的结构类型,但是其Name
字段是公开的。
type apple struct {
Name string
}
//公开类型的Tree对象
type Tree struct {
TreeName string //公开类型的TreeName
apple //未公开的apple类型
}
在其它包中,可以利用内部对象属性升级为外部对象的特性,为tree
的未公开内嵌类型apple
的公开字段Name
设值。
tree := identifier.Tree{TreeName: "MoneyTree"}
tree.Name = "apply"
fmt.Printf("%v \n", tree)