文章概述
- 匿名字段
- 方法
- 接口
对于面向对象编程的支持Go 语言设计得非常简洁而优雅。因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的this指针等。
尽管Go语言中没有封装、继承、多态这些概念,但同样通过别的方式实现这些特性:
l 封装:通过方法实现
l 继承:通过匿名字段实现
l 多态:通过接口实现
匿名字段(相当于Java当中的继承)
匿名组合,也叫匿名字段,相当于java中的继承。
一般情况下,定义结构体的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。
package main
import "fmt"
//定义一个人结构体类型
type Person struct {
name string
age int
sex byte
}
type Student struct {
Person //这是匿名字段,Student包含了Person定义的所有字段
grade string // 年级
score int
}
func main() {
fmt.Println("匿名字段使用演示案例")
// 1. 匿名字初始化
s1 := Student{Person{"zs", 18, 'm'}, "三年级", 98}
fmt.Printf("s1 = %v\n", s1) //{{zs 18 109} 三年级 98}
// 部分成员初始化
// 为什么不行呢??留待考察
// s2 := Student{Person{"lily", 18, 'm'}, "四年级", score: 96} //err, mixture of field:value and value initializers
// fmt.Printf("s2 = %v\n", s2)
// 2. 成员操作
var s3 Student
s3.age = 18
s3.grade = "六年级"
s3.Person.name = "Tim"
s3.score = 99
s3.sex = 'm'
fmt.Printf("s3 = %v\n", s3) //{{Tim 18 109} 六年级 99}
// 3. 同名字段
var s4 Student
s4.hobby = "看书"
fmt.Printf("s4 = %v\n", s4)
}
同名字段:
package main
import "fmt"
//定义一个人结构体类型
type Person struct {
name string
age int
sex byte
hobby string // 演示同名字段
}
type Student struct {
Person //这是匿名字段,Student包含了Person定义的所有字段
grade string // 年级
score int
hobby string // 演示同名字段
}
func main() {
fmt.Println("匿名字段使用演示案例")
// 1. 同名字段
var s1 Student
s1.hobby = "看书" // 是给student类赋值的hobby
fmt.Printf("s1 = %v\n", s1) //{{ 0 0 } 0 看书}
// 如果要给匿名成员赋值,则需要显示调用
s1.Person.hobby = "旅游"
fmt.Printf("s1 = %v\n", s1) //{{ 0 0 旅游} 0 看书}
}
非结构体匿名字段:
上面的例子是结构体字段,我们举一个非结构体匿名字段。
所有的内置类型和自定义类型都是可以作为匿名字段的:
package main
import "fmt"
type mystring string //自定义类型,也可以作为字段使用
type Person struct {
name string
age int
sex byte
}
type Student struct {
Person //结构体类型作为匿名字段
mystring
int // 基础类型的匿名字段
}
func main() {
fmt.Println("非结构体匿名字段使用演示案例")
s1 := Student{Person{"zz", 18, 'm'}, "哈哈", 666}
fmt.Printf("s1 = %v\n", s1) //{{zz 18 109} 哈哈 666}
}
结构体指针类型匿名字段
package main
import "fmt"
type Person struct {
name string
age int
sex byte
}
type Student struct {
*Person //构体指针作为匿名字段
grade string
score int
}
func main() {
fmt.Println("结构体指针作为匿名字段使用演示案例")
// 初始化
s1 := Student{&Person{"zz", 18, 'm'}, "三年级", 99}
fmt.Printf("s1 = %v\n", s1) //s1 = {0xc00004c420 三年级 99}
fmt.Println(s1.name, s1.Person.age) //zz 18
// 声明变量
var s2 = new(Student)
s2.Person = &Person{"lily", 18, 'm'}
s2.score = 66
s2.grade = "三年级"
fmt.Printf("s2 = %v\n", s2) //&{0xc00004a480 三年级 66}
fmt.Println(s2.name, s2.Person.age) //lily 18
}
方法
在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法(method)。 本质上,一个方法则是一个和特殊类型关联的函数。
一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters) (results)
l 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
l 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
l 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
面向过程和面向对象函数区别:
package main
import "fmt"
func add01(a, b int) int {
return a + b
}
type myInt int
func (obj myInt) add02(a myInt) myInt {
return obj + a
}
// func (obj int) add02(a int) int { // err,cannot define new methods on non-local type int
// return obj + a
// }
func main() {
fmt.Println("方法使用演示案例")
// 1. 面向过程和面向对象函数区别
res := add01(1, 2)
fmt.Println("add01:", res) //3
var a myInt = 1
res2 := a.add02(2)
fmt.Println("add02:", res2) //3
}
结构体类型添加方法:
package main
import "fmt"
type Person struct {
name string
age int
sex byte
}
func (p Person) PrintInfo() {
fmt.Println("person:", p)
}
func (p *Person) SetInfo(name string, age int, sex byte) {
p.age = age
p.name = name
p.sex = sex
}
//参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。
//type pointer *int
type myInt int
// func (p pointer) test01() { // invalid receiver type pointer (pointer is a pointer type)
// }
func (p myInt) test02() { // ok
}
//不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
// func (p myInt) test02(a int) { // err, method redeclared: myInt.test02
// }
func main() {
fmt.Println("结构体类型添加方法使用演示案例")
// 1. 结构体类型添加方法
var p Person
p.SetInfo("zz", 18, 'm')
p.PrintInfo() //person: {zz 18 109}
}
值语义和引用语义:
package main
import "fmt"
type Person struct {
name string
age int
sex byte
}
type Student struct {
Person //这是匿名字段,Student包含了Person定义的所有字段
grade string // 年级
score int
}
func (s Student) PrintInfo2() {
fmt.Println("student print:", s)
}
func (p Person) PrintInfo() {
fmt.Printf("%s,%d,%c\n", p.name, p.age, p.sex)
}
func (p Person) PrintInfo2() {
fmt.Printf("%s,%d,%c\n", p.name, p.age, p.sex)
}
func (p Person) setInfoValue() { //值语义
p.age = 18
p.name = "zz"
p.sex = 'm'
fmt.Println("setInfoValue方法被调用")
}
func (p *Person) setInfoPoint() { //引用语义
p.age = 18
(*p).name = "zz"
p.sex = 'm'
fmt.Println("setInfoPoint方法被调用")
}
func main() {
fmt.Println("值语义和引用语义使用演示案例")
// 1. 值语义和引用语义
p1 := Person{"mike", 17, 'f'}
fmt.Println("p1修改前:", p1) // {mike 17 102}
p1.setInfoValue()
fmt.Println("p1修改后:", p1) // {mike 17 102}
p2 := &Person{"mike", 17, 'f'}
fmt.Println("p2修改前:", p2) // &{mike 17 102}
p2.setInfoPoint()
fmt.Println("p2修改后:", p2) // &{zz 18 109}
fmt.Println("----------------------")
// 2. 指针类型和普通类型的方法集
// 2.1 指针变量的方法集
// 类型的方法集是指可以被该类型的值调用的所有方法的集合。
//用实例实例 value 和 pointer 调用方法(含匿名字段)不受方法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。
//一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
//如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。
// 结构体变量是一个指针类型,它能够调用哪些方法,这些方法就是一个集合,简称防腐剂
p3 := &Person{"mike", 17, 'f'}
p3.setInfoPoint()
(*p3).setInfoPoint()
(*p3).setInfoValue()
//内部做的转换,先把指针p3转成*p在调用
p3.setInfoValue()
fmt.Println("----------------------")
// 2.2 普通变量的方法集
//一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
//但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。
p4 := Person{"mike", 17, 'f'}
p4.setInfoPoint() //内部先把p转换成&p在调用 func (p *Person) setInfoPoint()
(&p4).setInfoPoint() //
fmt.Println("----------------------")
// 3. 方法的继承
s1 := Student{Person{"zz", 18, 'm'}, "四年级", 98}
s1.PrintInfo() //zz,18,m
s1.PrintInfo2() //student print: {{zz 18 109} 四年级 98}
}
方法值和方法表达式
package main
import "fmt"
type Person struct {
name string
age int
sex byte
}
func (p Person) setInfoValue() { //值语义
fmt.Println("setInfoValue方法被调用")
}
func (p *Person) setInfoPoint() { //引用语义
fmt.Println("setInfoPoint方法被调用")
}
func main() {
fmt.Println("方法值和方法表达式使用演示案例")
//类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。
//根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可像普通函数那样赋值和传参,
//区别在于方法值绑定实例,而方法表达式则须显式传参。
// 1. 方法值
p1 := Person{"zz", 18, 'm'}
p1.setInfoPoint() //传统的调用方式
p1.setInfoValue() //传统的调用方式
f1 := p1.setInfoPoint //这个就是方法值,调用函数时,无需传递接受者,隐藏了接受者
f1() //等价于p1.setInfoPoint()
f2 := p1.setInfoValue
f2()
fmt.Println("---------------")
// 2. 方法表达式
p2 := Person{"mike", 18, 'm'}
f3 := Person.setInfoValue
f4 := (*Person).setInfoPoint
f3(p2)
f4(&p2)
}
接口
在Go语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合。
接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。
Go通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
- 接口命名习惯以 er 结尾
- 接口只有方法声明,没有实现,没有数据字段
- 接口可以匿名嵌入其他接口,或嵌入到结构中
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。
如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。
package main
import "fmt"
type Humaner interface {
SayHi()
}
type Student struct {
name string
score int
}
type Teacher struct {
name string
group string
}
type myInt int
func (s *Student) SayHi() {
fmt.Println("student say Hi:", s.name, s.score)
}
func (t *Teacher) SayHi() {
fmt.Println("teacher say Hi:", t.name, t.group)
}
func (a myInt) SayHi() {
fmt.Println("myInt say Hi:", a)
}
func whoSayHi(i Humaner) {
fmt.Printf("whoSayHi say Hi:")
i.SayHi()
}
func main() {
fmt.Println("接口使用演示案例")
// 1. 接口的定义和实现
var i Humaner
//i = Student{"zz", 18} // err,cannot use Student literal (type Student) as type Humaner in assignment
// Student does not implement Humaner (SayHi method has pointer receiver)
i = &Student{"zz", 18}
i.SayHi() //student say Hi: zz 18
i = &Teacher{"Mike", "English"}
i.SayHi() //teacher say Hi: Mike English
var a myInt = 666
i = a
i.SayHi() //myInt say Hi: 666
// 2. 多态的体现
s1 := &Student{"jack", 18}
whoSayHi(s1) //whoSayHi say Hi:student say Hi: jack 18
//s2 := Student{"jack2", 18} //err,cannot use s2 (type Student) as type Humaner in argument to whoSayHi:
// Student does not implement Humaner (SayHi method has pointer receiver)
//whoSayHi(s2)
t1 := &Teacher{"Mike", "Math"}
whoSayHi(t1) //whoSayHi say Hi:teacher say Hi: Mike Math
var a1 myInt = 666
whoSayHi(a1) //whoSayHi say Hi:myInt say Hi: 666
fmt.Println("----------")
x := make([]Humaner, 3)
x[0] = s1
x[1] = t1
x[2] = a1
for _, v := range x {
v.SayHi()
// student say Hi: jack 18
// teacher say Hi: Mike Math
// myInt say Hi: 666
}
}
接口的继承:
如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的方法。
package main
import "fmt"
type Humaner interface {
SayHi()
}
type Personer interface {
Humaner // 接口的继承
Sing(lrc string)
}
type Student struct {
name string
score int
}
func (s *Student) SayHi() {
fmt.Println("student SayHi")
}
func (s *Student) Sing(lrc string) {
fmt.Println("student Sing " + lrc)
}
func main() {
fmt.Println("接口的继承演示案例")
s1 := &Student{"zz", 18}
var p1 Personer
p1 = s1
p1.SayHi() //student SayHi
p1.Sing("哈哈哈") //student Sing 哈哈哈
//超集接口对象可转换为子集接口,反之出错:就是按抽象的层次来的。大可以转小,小不可以转大。
var i Humaner
var p2 Personer
i = p2
//p2 = i //err,cannot use i (type Humaner) as type Personer in assignment: Humaner does not implement Personer (missing Sing method)
fmt.Println(i, p2)
}
空接口:
空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。它有点类似于C语言的void *类型。
package main
import "fmt"
func main() {
var v1 interface{} = 1 // 将int类型赋值给interface{}
var v2 interface{} = "abc" // 将string类型赋值给interface{}
var v3 interface{} = &a // 将*interface类型赋值给interface{}
var v4 interface{} = struct{ x int }{1}
var v5 interface{} = &struct{ x int }{1}
//当函数可以接受任意对象实例的时候,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXX系列函数,例如:
// func Printf(fmt string,args...interface{})
// func Println(args...interface{})
}
通过if实现类型断言:
我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
l comma-ok断言
l switch测试
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。
如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
package main
import "fmt"
type Student struct {
id int
name string
}
func main() {
fmt.Println("类型判断if演示案例")
x := make([]interface{}, 3)
x[0] = 1
x[1] = "abc"
x[2] = Student{1, "zz"}
for _, v := range x {
//fmt.Println(k, v)
//k1, v1 := v.(int) // 第一个返回的是值,第二个返回的是true和false
//fmt.Println(k1, v1)
if k1, v1 := v.(int); v1 == true {
fmt.Printf("%d is int\n", k1) //1 is int
} else if k1, v1 := v.(string); v1 == true {
fmt.Printf("%s is string\n", k1) //abc is string
} else if k1, v1 := v.(Student); v1 == true {
fmt.Printf("%v is Student\n", k1) //{1 zz} is Student
}
}
}
通过switch实现类型断言:
package main
import "fmt"
type Student struct {
id int
name string
}
func main() {
fmt.Println("类型判断witch演示案例")
x := make([]interface{}, 3)
x[0] = 1
x[1] = "abc"
x[2] = Student{1, "zz"}
for k, v := range x {
switch v1 := v.(type) {
case int:
fmt.Println(k, v1, "is int")
case string:
fmt.Println(k, v1, "is string")
case Student:
fmt.Println(k, v1, "is Student")
}
}
// 0 1 is int
// 1 abc is string
// 2 {1 zz} is Student
}
END.