面向对象编程
对于面向对象编程的支持go语言设计得非常简洁而优雅。因为,Go语言并没有沿袭面向对象编程中诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的this指针等.
尽管go语言中没有封装,继承,多态这些概念,但同样通过别的方式实现这些特性:
1:封装:通过方法实现
2:继承:通过匿名字段实现
3:多态:通过接口实现
匿名字段作用—继承
一般情况下,定义结构体的时候是字段名与其类型一 一对应,实际上go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。
//定义一个结构体类型type Person struct {name string //名字sex byte //性别age int //年龄
}type Student struct {Person //只有类型 没有名字,匿名字段,继承了Person的成员id intaddr string
}
匿名字段初始化
package mainimport "fmt"//定义一个结构体类型type Person struct {name string //名字sex byte //性别age int //年龄
}type Student struct {Person //只有类型 没有名字,匿名字段,继承了Person的成员id intaddr string
}func main() {//顺序初始化var s1 Student = Student{Person{"mike", 'm', 18},1,"bj",}fmt.Println("s1 = ", s1) //s1 = {{mike 109 18} 1 bj}//自动推导类型s2 := Student{Person{"mike", 'm', 18},1,"bj",}fmt.Println("s2 = ", s2) //s2 = {{mike 109 18} 1 bj}//%+v 显示更详细fmt.Printf("s2 = %+v\n", s2) //s2 = {Person:{name:mike sex:109 age:18} id:1 addr:bj}//指定成员初始化,没有初始化的常用自动赋值为0s3 := Student{id: 1}fmt.Printf("s3 = %+v\n", s3) //s3 = {Person:{name: sex:0 age:0} id:1 addr:}s4 := Student{Person: Person{name: "mike"}, id: 1}fmt.Printf("s4 = %+v\n", s4) //s4 = {Person:{name:mike sex:0 age:0} id:1 addr:}
}
成员的操作
package mainimport "fmt"//定义一个结构体类型type Person struct {name string //名字sex byte //性别 字符类型age int //年龄
}type Student struct {Person //只有类型 没有名字,匿名字段,继承了Person的成员id intaddr string
}func main() {//自动推导类型s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}fmt.Println(s1.name, s1.sex, s1.id, s1.addr, s1.age) //mike 109 1 bj 18s1.name = "yoyo" //等价于s1.Person.name = "mike"s1.sex = 'f's1.age = 22s1.id = 666s1.addr = "wh"fmt.Println(s1.name, s1.sex, s1.id, s1.addr, s1.age) //yoyo 102 666 wh 22s1.Person = Person{"go", 'm', 23}fmt.Println(s1.name, s1.sex, s1.id, s1.addr, s1.age) //go 109 666 wh 23
}
同名字段
package mainimport "fmt"//定义一个结构体类型type Person struct {name string //名字sex byte //性别 字符类型age int //年龄
}type Student struct {Person //只有类型 没有名字,匿名字段,继承了Person的成员id intaddr stringname string //和Person同名了
}func main() {//声明(定义一个变量)var s Student//默认规则(就近原则),如果能在本作用域找到此成员,就操作此成员//如果没有找到,找到继承的字段s.name = "mike" //操作的是Student的names.sex = 'm's.age = 18s.addr = "bj"fmt.Printf("s = %+v\n", s) //s = {Person:{name: sex:109 age:18} id:0 addr:bj name:mike}//显示调用s.Person.name = "yoyo"fmt.Printf("s = %+v\n", s) //s = {Person:{name:yoyo sex:109 age:18} id:0 addr:bj name:mike}
}
非结构体匿名字段
package mainimport "fmt"//定义一个结构体类型type mystr string //自定义类型,给一个类型改名type Person struct {name string //名字sex byte //性别 字符类型age int //年龄
}type Student struct {Person //只有类型 没有名字,匿名字段,继承了Person的成员int //基础类型的匿名字段mystr //基础类型的匿名字段
}func main() {s := Student{Person{"mike", 'm', 18}, 666, "hehehe"}fmt.Printf("s = %+v\n", s) //s = {Person:{name:mike sex:109 age:18} int:666 mystr:hehehe}fmt.Println(s.name, s.age, s.sex, s.int, s.mystr) //mike 18 109 666 hehehefmt.Println(s.Person, s.int, s.mystr) //{mike 109 18} 666 hehehes.Person = Person{"go", 'm', 22}fmt.Println(s.Person, s.int, s.mystr) //{go 109 22} 666 hehehefmt.Println(s.name, s.age, s.sex, s.int, s.mystr) //go 22 109 666 hehehe
}
结构体指针类型匿名字段
package mainimport "fmt"//定义一个结构体类型type mystr string //自定义类型,给一个类型改名type Person struct {name string //名字sex byte //性别 字符类型age int //年龄
}type Student struct {*Person //指针类型id intaddr string
}func main() {s1 := Student{&Person{"mike", 'm', 18}, 1, "wh"}fmt.Println("s1 = ", s1) //s1 = {0xc00008e380 1 wh}fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) //mike 109 18 1 wh//先定义变量var s2 Students2.Person = new(Person) //分配空间s2.name = "yoyo"s2.sex = 'm's2.age = 19s2.id = 2s2.addr = "wh"fmt.Println("s2 = ", s2) //s2 = {0xc000054400 2 wh}fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.addr) //yoyo 109 19 2 wh
}
方法介绍—封装
在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法。本质上,一个方法则是一个和特殊关联的函数。
一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这种事情。
在Go语言中,可以任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。
方法总是绑定对象实例,并隐式将实例作为第一实参(receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters)(results)
1:参数receiver可任意命名.如方法中未曾使用,可以省略参数.
2: 参数receiver类型可以是T或*T。基类型T不能是接口或者指针。
3:不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
面向过程和面向对象函数区别
package mainimport "fmt"// 实现2数相加
// 面向过程
func Add01(a, b int) int {return a + b
}// 面向对象,方法:给某个类型绑定一个函数
type long int //改名// tmp叫接收者,接收者就是传递的一个参数
func (tmp long) Add02(other long) long {return tmp + other
}func main() {result := Add01(1, 1) //普通函数调用方式fmt.Println("result = ", result) //result = 2//定义一个变量var a long = 2//调用方法格式add1 := a.Add02(3)fmt.Println("result = ", add1) //result = 5//面向对象只是换了一种表现形式
}
结构体类型添加方法
package mainimport "fmt"type Person struct {name stringsex byteage int
}//带有接收者的函数叫方法func (tmp Person) PrintInfo() {fmt.Println("tmp = ", tmp)
}// 通过一个函数,给成员赋值
func (p *Person) SetInfo(name string, sex byte, age int) {p.name = namep.sex = sexp.age = age
}
func main() {//定义同时初始化p := Person{"mike", 'm', 18}p.PrintInfo() //tmp = {mike 109 18}//定义一个结构体变量var p2 Person(&p2).SetInfo("yoyo", 'm', 19)p2.PrintInfo() //tmp = {yoyo 109 19}
}
type pointer *int
//pointer 为接收者类型,它本身不能是指针类型
func (tmp pointer) test(){} //error invalid receiver type pointer
//不支持重载,只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误
func (tmp Person) PrintInfo() {fmt.Println("tmp = ", tmp)
}type char bytefunc (tmp char) PrintInfo() {}
值语义和引用语义
package mainimport "fmt"type Person struct {name stringsex byteage int
}// 接收者为普通变量,非指针,值语义,一份拷贝
func (p Person) SetInfoValue(name string, sex byte, age int) {p.name = namep.sex = sexp.age = agefmt.Println("SetInfoValue p = ", p) //SetInfoValue p = {mike 109 19}fmt.Printf("SetInfoValue &p = %p\n", &p) //SetInfoValue &p = 0xc00008e3c0
}// 接收者为指针变量,引用传递
func (p *Person) SetInfoPoubter(name string, sex byte, age int) {p.name = namep.sex = sexp.age = agefmt.Printf("SetInfoPoubter p = %p\n", &p) //SetInfoPoubter p = 0xc00008e380fmt.Println("SetInfoPoubter &p = ", *p) //SetInfoPoubter &p = {mike 109 19}
}
func main() {s1 := Person{"go", 'm', 18}fmt.Printf("&s1 = %p\n", &s1) //&s1 = 0xc00008e380//值语义s1.SetInfoValue("mike", 'm', 19)fmt.Println("s1 = ", s1) //s1 = {go 109 18}//引用语义(&s1).SetInfoPoubter("mike", 'm', 19)fmt.Println("s1 = ", s1) //s1 = {mike 109 19}
}
指针类型和普通类型的方法集
指针类型的方法集
package mainimport "fmt"type Person struct {name stringsex byteage int
}// 接收者为普通变量,非指针,值语义,一份拷贝
func (p Person) SetInfoValue() {fmt.Println("SetInfoValue")
}// 接收者为指针变量,引用传递
func (p *Person) SetInfoPoubter() {fmt.Println("SetInfoPoubter")
}
func main() {//假如,结构体是一个指针变量,它能够调用那些方法,这些方法就是一个集合,简称方法集p := &Person{"mike", 'm', 18}p.SetInfoPoubter()(*p).SetInfoPoubter() //把(*p)转换成p调用,等价于上面//内部做的转换,先把指针p,转成*p后再调用//(*p).SetInfoValue()p.SetInfoValue()
}
普通类型的方法集
package mainimport "fmt"type Person struct {name stringsex byteage int
}// 接收者为普通变量,非指针,值语义,一份拷贝
func (p Person) SetInfoValue() {fmt.Println("SetInfoValue")
}// 接收者为指针变量,引用传递
func (p *Person) SetInfoPoubter() {fmt.Println("SetInfoPoubter")
}
func main() {p := Person{"mike", 'm', 18}p.SetInfoPoubter() //内部会先把p转化为&p再调用(&p).SetInfoPoubter()p.SetInfoValue()
}
方法的继承
package mainimport "fmt"type Person struct {name stringsex byteage int
}// Person类型,实现一个方法
func (tmp *Person) PrintInfo() {//PrintInfo: name = mike,sex = m, age = 18fmt.Printf("PrintInfo: name = %s,sex = %c, age = %d\n", tmp.name, tmp.sex, tmp.age)
}// 有一个学生,继承Person字段,成员和方法都继承了
type Student struct {Person //匿名字段id intaddr string
}func main() {s := Student{Person{"mike", 'm', 18}, 666, "bj"}s.PrintInfo()
}
方法的重写
package mainimport "fmt"type Person struct {name stringsex byteage int
}// PrintInfo Person类型,实现一个方法
func (tmp *Person) PrintInfo() {//PrintInfo: name = mike,sex = m, age = 18fmt.Printf("PrintInfo: name = %s,sex = %c, age = %d\n", tmp.name, tmp.sex, tmp.age)
}// PrintInfo Student也实现了一个方法,这个方法和Person方法重名,这个方法叫重写
func (tmp *Student) PrintInfo() {//PrintInfo: name = mike,sex = m, age = 18fmt.Println("Student: tmp = ", tmp) //Student: tmp = &{{mike 109 18} 666 bj}fmt.Println("Student: *tmp = ", *tmp) //Student: *tmp = {{mike 109 18} 666 bj}
}// Student 有一个学生,继承Person字段,成员和方法都继承了
type Student struct {Person //匿名字段id intaddr string
}func main() {s := Student{Person{"mike", 'm', 18}, 666, "bj"}//就近原则:先找本作用域的方法,找不到再用继承的方法s.PrintInfo() //这样会调用Student的//显示调用继承的方法s.Person.PrintInfo() //PrintInfo: name = mike,sex = m, age = 18
}
方法值
package mainimport "fmt"type Person struct {name stringsex byteage int
}func (p Person) SetInfoValue() {fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
}func (p *Person) SetInfoPointer() {fmt.Printf("SetInfoPointer %p, %v\n", p, *p) //SetInfoPointer 0xc00008e380, {mike 109 18}
}func main() {p := Person{"mike", 'm', 18}fmt.Printf("main: %p,%v\n", &p, p) //main: 0xc00008e380,{mike 109 18}p.SetInfoPointer() //传统调用方式//保存方式入口地址pFunc := p.SetInfoPointer //这个就是方法值,调用函数时,无需再传递接收者,隐藏了接收者pFunc() //等价于p.SetInfoPointer() SetInfoPointer 0xc00008e380, {mike 109 18}vFunc := p.SetInfoValuevFunc() //等价于p.SetInfoValue() SetInfoValue: 0xc000054440,{mike 109 18} 值传递
}
方法表达式
package mainimport "fmt"type Person struct {name stringsex byteage int
}func (p Person) SetInfoValue() {fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
}func (p *Person) SetInfoPointer() {fmt.Printf("SetInfoPointer %p, %v\n", p, *p) //SetInfoPointer 0xc00008e380, {mike 109 18}
}func main() {p := Person{"mike", 'm', 18}fmt.Printf("main: %p,%v\n", &p, p) //main: 0xc00008e380,{mike 109 18}p.SetInfoPointer() //传统调用方式//方法值,f := p.SetInfoPointer//隐藏了接收者//方法表达式f := (*Person).SetInfoPointerfmt.Println("=========")f(&p) //显示把接收者传递过去 ---->p.SetInfoPointer() SetInfoPointer 0xc0000543a0, {mike 109 18}f2 := (Person).SetInfoValuef2(p) //SetInfoValue: 0xc000054440,{mike 109 18}
}
接口类型介绍
在go语言,接口是一个自定义类型,接口类型具体描述了一系列方法的集合。
接口类型时一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。
接口定义:
1:接口命令习惯以er结尾
2:接口只有方法声明,没有实现,没有数据字段
3:接口可以匿名嵌入其他接口,或嵌入到结构中
接口的定义和实现
package mainimport "fmt"// Humaner 定义接口类型
type Humaner interface {//方法,只有声明,没有实现,由别的类型(自定义类型)实现sayhi()
}type Student struct {name stringid int
}// Student实现了此方法
func (tmp *Student) sayhi() {fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)}type Teacher struct {addr stringgroup string
}// Teacher实现了此方法
func (tmp *Teacher) sayhi() {fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)}type MyStr string// MyStr实现了此方法
func (tmp *MyStr) sayhi() {fmt.Printf("MyStr[%s] sayhi\n", *tmp)
}type MyStr1 stringfunc (tmp MyStr1) sayhi() {fmt.Printf("MyStr1[%s] sayhi\n", tmp)
}func main() {//定义接口类型的变量var i Humaner//只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值s := &Student{"mike", 666}i = si.sayhi() //Student[mike, 666] sayhit := &Teacher{"wh", "go"}i = ti.sayhi() //Teacher[wh, go] sayhivar str MyStr = "hello cheng"i = &stri.sayhi() //MyStr[hello cheng] sayhivar str1 MyStr1 = "hello"i = str1i.sayhi() //MyStr1[hello] sayhi
}
多态的表现
package mainimport "fmt"// Humaner 定义接口类型
type Humaner interface {//方法,只有声明,没有实现,由别的类型(自定义类型)实现sayhi()
}type Student struct {name stringid int
}// Student实现了此方法
func (tmp *Student) sayhi() {fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)}type Teacher struct {addr stringgroup string
}// Teacher实现了此方法
func (tmp *Teacher) sayhi() {fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)}type MyStr string// MyStr实现了此方法
func (tmp *MyStr) sayhi() {fmt.Printf("MyStr[%s] sayhi\n", *tmp)
}type MyStr1 stringfunc (tmp MyStr1) sayhi() {fmt.Printf("MyStr1[%s] sayhi\n", tmp)
}// 定义一个普通函数,函数的参数为接口类型
// 只有一个函数,可以有不同表现,多态
func WhoSayHi(i Humaner) {i.sayhi()
}func main() {s := &Student{"mike", 666}t := &Teacher{"wh", "go"}var str MyStr = "hello cheng"var str1 MyStr1 = "hello"//调用同一函数,不同表现,多态,多种形态WhoSayHi(s)WhoSayHi(t)WhoSayHi(&str)WhoSayHi(str1)//创建一个切片x := make([]Humaner, 4)x[0] = sx[1] = tx[2] = &strx[3] = str1//第一个返回下标,第二个返回鞋标所对应的值for _, data := range x {fmt.Println("===========")WhoSayHi(data)}
}
接口继承—(接口嵌入)
如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式地包含了interface1里面的方法。
package mainimport "fmt"// Humaner 定义接口类型
type Humaner interface { //子集//方法,只有声明,没有实现,由别的类型(自定义类型)实现sayhi()
}type Personer interface { //超集Humaner //匿名字段,继承了sayhi()sing(lrc string)
}type Student struct {name stringid int
}// Student实现了此方法
func (tmp *Student) sayhi() {fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)}func (tmp *Student) sing(lrc string) {fmt.Println("Student在唱着:", lrc)
}func main() {//定义一个接口类型的变量var i Personers := &Student{"mike", 666}i = si.sayhi() //继承过来的方法 //Student[mike, 666] sayhii.sing("三月") //Student在唱着: 三月
}
接口转换
package mainimport "fmt"// Humaner 定义接口类型
type Humaner interface { //子集//方法,只有声明,没有实现,由别的类型(自定义类型)实现sayhi()
}type Personer interface { //超集Humaner //匿名字段,继承了sayhi()sing(lrc string)
}type Student struct {name stringid int
}// Student实现了此方法
func (tmp *Student) sayhi() {fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)}func (tmp *Student) sing(lrc string) {fmt.Println("Student在唱着:", lrc)
}func main() {//超集可以转换为子集,反过来不可以var iPro Personer //超集iPro = &Student{"mike", 666}var i Humaner //子集//cannot use i (variable of type Humaner) as Personer value in assignment://Humaner does not implement Personer (missing method sing)//iPro = i //errori = iPro //OK 超集可以转换为子集i.sayhi()
}
空接口
空接口不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型,它有点类似于C语言的void*类型。
package mainimport "fmt"func xxx(args ...interface{}) {}func main() {//空接口万能类型,保存任意类型的值var i interface{} = 1fmt.Println("i = ", i)i = "abc"fmt.Println("i = ", i)
}
通过if实现类型断言
package mainimport "fmt"type Student struct {name stringid int
}func main() {i := make([]interface{}, 3)i[0] = 1 //inti[1] = "hello go" //stringi[2] = Student{"mike", 666} //Student//类型查询,类型断言//第一个返回下标,第二个返回下标对应的值,data是i[0],i[1],i[2]for index, data := range i {//第一个返回值,接口变量本身,第二个返回判断结果的真假if value, ok := data.(int); ok == true {fmt.Printf("x[%d] 类型为int,内容为%d\n", index, value) //x[0] 类型为int,内容为1fmt.Println("int data = ", data) //int data = 1fmt.Println("------------------")} else if value, ok := data.(string); ok == true {fmt.Printf("x[%d] 类型为string, 内容为%s\n", index, value) //x[1] 类型为string, 内容为hello gofmt.Println("string data = ", data) //string data = hello gofmt.Println("------------------")} else if value, ok := data.(Student); ok == true {fmt.Printf("x[%d] 类型为Student, 内容为name = %s, id = %d\n", index, value.name, value.id) //x[2] 类型为Student, 内容为name = mike, id = 666fmt.Println("Student data = ", data) //Student data = {mike 666}fmt.Println("------------------")}}
}
通过switch实现类型断言
package mainimport "fmt"type Student struct {name stringid int
}func main() {i := make([]interface{}, 3)i[0] = 1 //inti[1] = "hello go" //stringi[2] = Student{"mike", 666} //Student//类型查询,类型断言//第一个返回下标,第二个返回下标对应的值,data是i[0],i[1],i[2]for index, data := range i {switch value := data.(type) { //type参数类型case int:fmt.Printf("x[%d] 类型为int,内容为%d\n", index, value) //x[0] 类型为int,内容为1fmt.Println("int data = ", data) //int data = 1fmt.Println("------------------")case string:fmt.Printf("x[%d] 类型为string, 内容为%s\n", index, value) //x[1] 类型为string, 内容为hello gofmt.Println("string data = ", data) //string data = hello gofmt.Println("------------------")case Student:fmt.Printf("x[%d] 类型为Student, 内容为name = %s, id = %d\n", index, value.name, value.id) //x[2] 类型为Student, 内容为name = mike, id = 666fmt.Println("Student data = ", data) //Student data = {mike 666}fmt.Println("------------------")}}
}