Day_04 面向对象
概述
对于面向对象编程的支持,Go语言设计得非常简洁而优雅。因为,Go语言并没有沿袭传统 面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、 虚函数、构造函数和析构函数、隐藏的this指针等等 尽管Go语言中没有封装、继承、多态这些概念,但同样通过别的方式实现这些特性: 封装:通过方法实现 继承:通过匿名字段实现 多态:通过接口实现 接口概述: 在Go语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合 接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合, 它们只会展示出它们自己的方法。因此接口类型不能将其实例化。 Go通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子、那么这只鸟就可以被称为鸭子了” 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为
01.匿名字段初始化
//匿名字段体现的是面向对象的继承特点 package main import "fmt" type Person struct { name string //名字 sex byte //性别 age int //年龄 } type Student struct { Person //只有类型,没有名字,匿名字段,继承了Person的成员 id int addr string } func main() { //顺序初始化 var s1 Student = Student{Person{"Mike", 'm', 18}, 1, "bj"} fmt.Println("s1 = ", s1) //自动推导类型 s2 := Student{Person{"Jason", 'f', 19}, 2, "sz"} //%+v, 显示更详细 fmt.Println("s2普通输出 = ", s2) fmt.Printf("s2详细输出 = %+v\n", s2) //指定成员初始化,没有初始化的成员自动赋值为0(int型),其他类型的为空 s3 := Student{id: 1} fmt.Printf("s3详细输出 = %+v\n", s3) s4 := Student{Person: Person{name: "Nicole"}, id: 88} fmt.Printf("s4详细输出 = %+v\n", s4) //s5 := Student{"Hanson",'m', 19,77,"sh"} //这种写法是错误的,编不过 }
02.成员的操作
package main import "fmt" type Person struct { name string //名字 sex byte //性别 age int //年龄 } type Student struct { Person //只有类型,没有名字,匿名字段,继承了Person的成员 id int addr string } func main() { s1 := Student{Person{"mike", 'm', 18}, 1, "bj"} s1.name = "yoyo" s1.sex = 'f' s1.age = 22 s1.id = 666 s1.addr = "sz" fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) s1.Person = Person{"go", 'm', 19} fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) }
03.同名字段
package main import "fmt" type Person struct { name string //名字 sex byte //性别 age int //年龄 } type Student struct { Person //只有类型,没有名字,匿名字段,继承了Person的成员 id int addr string name string //和Person里面的string同名了 } func main() { //声明(定义一个变量) var s Student //默认规则(就近原则),如果能在本作用域找到此成员,就操作此成员 // 如果没有找到,找到继承的字段 s.name = "mike" //s = {Person:{name: sex:102 age:18} id:0 addr:gz name:mike} 可以看到,操作的是student的name,还是Person的name? s.sex = 'f' s.age = 18 s.addr = "gz" //显式调用 s.Person.name = "yoyo" //Person的name fmt.Printf("s = %+v\n", s) }
04.非结构体匿名字段
package main import "fmt" type mystr string //自定义类型,给一个类型改名 type Person struct { name string //名字 sex byte //性别 age int //年龄 } type Student struct { Person //结构体匿名字段 int mystr //基础类型的匿名字段 } func main() { s := Student{Person{"mike", 'm', 18}, 666, "hehehehe"} fmt.Printf("s = %+v\n", s) s.Person = Person{"go", 'm', 22} fmt.Println(s.name, s.age, s.sex, s.int, s.mystr) fmt.Println(s.Person, s.int, s.mystr) }
05.结构体指针类型匿名字段
/* 方法体现的是面向对象的封装特点 在面向对象编程中,一个对象其实就是一个简单的值或者一个变量,在这个对象中会包含 一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数 一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接取操作对象, 而是借助方法来做这些事情。 在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。 方法总是绑定对象实例,并隐式将实例作为第一参数(receiver),方法的语法如下: func (receiver ReceiverType) funcName(parameters) (results) */ package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } type Student struct { *Person //指针类型 id int addr string } func main() { s := Student{&Person{"mike", 'm', 18}, 666, "gz"} fmt.Println(s.name, s.sex, s.age, s.id, s.addr) //先定义变量 var s1 Student s1.Person = new(Person) //分配空间 s1.name = "yoyo" s1.sex = 'm' s1.age = 18 s1.id = 222 s1.addr = "sz" fmt.Println(s1.name, s1.sex, s1.age, s1.id, s1.addr) }
06.面向过程和对象函数的区别
/* 方法体现的是面向对象的封装特点 在面向对象编程中,一个对象其实就是一个简单的值或者一个变量,在这个对象中会包含 一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数 一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接取操作对象, 而是借助方法来做这些事情。 在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。 方法总是绑定对象实例,并隐式将实例作为第一参数(receiver),方法的语法如下: func (receiver ReceiverType) funcName(parameters) (results) */ package main import "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() { var result int result = Add01(1, 1) //普通函数调用方式 fmt.Println("面向过程:result = ", result) //定义一个变量 var a long = 2 //调用方法格式:变量名.函数(所需参数) result02 := a.Add02(3) fmt.Println("面向对象:result02 = ", result02) // 面向对象只是换了一种表现形式 }
07.为结构体类型添加方法
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } //带有接收者的函数叫方法 //Person为接收者类型,它本身不能是指针类型 func (tmp Person) PrintInfo() { fmt.Println("tmp = ", tmp) } func (p *Person) SetInfo(n string, s byte, a int) { p.name = n p.sex = s p.age = a } //接收者只要类型不一样,它就是不同的方法,就算同名也没有关系,不会出现重复定义函数的错误 type long int func (tmp long) test() { } type char byte func (tmp char) test() { } func main() { //定义同时初始化 p := Person{"mike", 'm', 19} p.PrintInfo() //定义一个结构体变量 var p2 Person (&p2).SetInfo("yoyo", 'f', 22) p2.PrintInfo() }
08.值语义和引用语义
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } //修改成员变量的值 //参数为普通变量,非指针,值语义,一份拷贝 func (tmp Person) PrintInfo() { fmt.Println("tmp = ", tmp) } func (p Person) SetInfoValue(n string, s byte, a int) { p.name = n p.sex = s p.age = a fmt.Println("p = ", p) fmt.Printf("SetInfoValue &p = %p\n", &p) } //接收者为指针变量,引用传递 func (p *Person) SetInfoPointer(n string, s byte, a int) { p.name = n p.sex = s p.age = a fmt.Printf("SetInfoPointer p = %p\n", p) } func main() { s1 := Person{"go", 'f', 22} fmt.Printf("&s1 = %p\n", &s1) //打印地址 //值语义 s1.SetInfoValue("Mike", 'm', 16) fmt.Println("s1 = ", s1) //打印内容 //引用语义 //(&s1).SetInfoPointer("Jason", 'f', 66) //fmt.Println("s1 = ", s1) //打印内容 } /* 值语义结果: &s1 = 0xc000004460 p = {Mike 109 16} SetInfoValue &p = 0xc0000044a0 s1 = {go 102 22} 总结:值语义只是一份拷贝,里面的修改不会影响到外面 引用语义结果: &s1 = 0xc000058420 SetInfoPointer p = 0xc000058420 s1 = {Jason 102 66} 总结:引用语义从内存地址可以看出操作的是同一份,里面的修改会影响到外面 */
09.指针变量的方法集
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } func (p Person) SetInfoValue() { fmt.Println("SetInfoValue\n") } func (p *Person) SetInfoPointer() { fmt.Printf("SetInfoPointer\n") } func main() { //结构体变量是一个指针变量,它能够调用哪些方法,这些方法就是一个集合,简称方法集 p := &Person{"mike", 'f', 18} p.SetInfoPointer() //func (p *Person) SetInfoPointer()调用了这个方法 (*p).SetInfoPointer() //把(*p)转换成p后再调用,等价于上面 //内部自己做了转换,先把指针p,转成*p后再调用 //(*p).SetInfoValue p.SetInfoValue() //func (p Person) SetInfoValue() 可以发现它相当灵活,这个非指针类型的也可以调用 } /* 总结: 类型的方法集是指可以被该类型的值调用的所有方法的集合 用实例value和pointer调用方法(含匿名字段)不受方法集约束,编译器会总是查找全部方法,并自动转换receiver实参。 你不用关心方法是否是指针,都可以相互调用 */
10.普通变量的方法集
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } func (p Person) SetInfoValue() { fmt.Println("SetInfoValue\n") } func (p *Person) SetInfoPointer() { fmt.Printf("SetInfoPointer\n") } func main() { //普通变量调指针 p := Person{"mike", 'f', 18} p.SetInfoPointer() //发现也能够成功调用 //内部先把p转为&p再调用,(&p).SetInfoPointer() }
11.方法的继承
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } //Person类型,实现了一个方法 func (tmp *Person) PrintInfo() { fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age) } //有个学生,继承了Person字段,成员和方法都继承了 type Student struct { Person //匿名字段 id int addr string } func main() { s := Student{Person{"mike", 'm', 18}, 666, "bj"} s.PrintInfo() }
12.方法的重写
package main import "fmt" type Person struct { name string //名字 sex byte //性别,字符类型 age int //年龄 } //Person类型,实现了一个方法 func (tmp *Person) PrintInfo() { fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age) } //有个学生,继承了Person字段,成员和方法都继承了 type Student struct { Person //匿名字段 id int addr string } //Student也实现了一个方法,这个方法和Person方法同名,这种方法叫重写 func (tmp *Student) PrintInfo() { fmt.Println("Student: tmp = ", tmp) } func main() { s := Student{Person{"mike", 'm', 18}, 666, "bj"} //就近原则:先找本作用域的方法,找不到再用继承的方法 s.PrintInfo() //到底调用的是Person,还是Student,结论是Student //显式调用继承的方法 s.Person.PrintInfo() }
13.方法值
package main import "fmt" type Person struct { name string sex byte age 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) } func main() { p := Person{"mike", 'f', 19} fmt.Printf("main: %p, %v\n", &p, p) p.SetInfoPointer() // 传统调用方式 //保存方式入口地址,这样之后其他需要调用的地方就可以直接调用 pFunc := p.SetInfoPointer //这个就是方法值,调用函数时,无需再传递接收者,隐藏了接收者 pFunc() //等价于 p.SetInfoPointer() vFunc := p.SetInfoValue vFunc() //等价于p.SetInfoValue() }
14.方法表达式
package main import "fmt" type Person struct { name string // 名字 sex byte // 性别,字符类型 age 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) } func main() { p := Person{"mike", 'f', 19} fmt.Printf("Main: %p,%v\n", &p, p) //方法值: f := p.SetInfoPointer // 隐藏了接收者 //方法表达式 f := (*Person).SetInfoPointer f(&p) //显式把接收者传递过去========>p.SetInfoPointer() f2 := (Person).SetInfoValue f2(p) //显式把接收者传递过去========>p.SetInfoValue() }
15.接口的定义和实现
package main import "fmt" //定义接口类型 type Humaner interface { //方法,只有声明,没有实现,由别的类型(自定义类型)实现 sayhi() } type Student struct { name string id int } //Student实现了此方法 func (tmp *Student) sayhi() { fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id) } type Teacher struct { addr string group 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) } //定义一个普通函数,函数的参数为接口类型 //只有一个函数,可以有不同表现,多态 func WhoSayHi(i Humaner) { i.sayhi() } func main() { s := &Student{"Jason", 777} t := &Teacher{"bj", "go"} var str Mystr = "Hello Mike" //调用同一函数,不同表现,多态,多种形态 WhoSayHi(s) WhoSayHi(t) WhoSayHi(&str) //创建一个切片 x := make([]Humaner, 3) x[0] = s x[1] = t x[2] = &str //第一个返回下标,第二个返回下标所对应的值 for _, i := range x { i.sayhi() } } func main_bak() { //定义接口类型的变量 var i Humaner //只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值 s := &Student{"Jason", 777} i = s i.sayhi() t := &Teacher{"bj", "go"} i = t i.sayhi() var str Mystr = "Hello Mike" i = &str i.sayhi() } /* Student[Jason,777] sayhi Teacher[bj,%!d(string=go)] sayhi Mystr[Hello Mike] sayhi 最终发现都是调用的同一个Human接口,却是不同表现 这就是Go语言里的接口(实现的是面向对象的多态特性) */
16.接口的继承
package main import "fmt" type Humaner interface { //子集 sayhi() } type Personer interface { //超集 Humaner //匿名字段,继承了Humaner的sayhi方法 sing(lrc string) } type Student struct { name string id int } //Student实现了sayhi()方法 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 Personer s := &Student{"Jason", 666} i = s i.sayhi() //这个是继承过来的方法 i.sing("好高兴好高兴") }
17.接口转换
package main import "fmt" type Humaner interface { //子集 sayhi() } type Personer interface { //超集 Humaner //匿名字段,继承了Humaner的sayhi方法 sing(lrc string) } type Student struct { name string id int } //Student实现了sayhi()方法 func (tmp *Student) sayhi() { fmt.Printf("Student[%s,%d] sayhi\n", tmp.name, tmp.id) } //Student实现了sing()方法 func (tmp *Student) sing(lrc string) { fmt.Println("Student在唱着:", lrc) } func main() { //子集可以转换为超集,反过来不可以 var iPro Personer //超集 iPro = &Student{"Jason", 777} var i Humaner //子集 //iPro = i //err,因为超集不能转换为子集 i = iPro // 可以,子集可以转换为超集 i.sayhi() }
18.空接口
//空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值,它有点类似于C语言的void *类型。 package main import "fmt" func xxx(arg ...interface{}) { //fmt函数就是对空接口的典型应用 } func main() { //空接口万能类型,保存任意类型的值 var i interface{} = 1 fmt.Println("i = ", i) i = "abc" fmt.Println("i = ", i) }
19.类型断言:if
package main import "fmt" type Student struct { name string id int } func main() { i := make([]interface{}, 3) i[0] = 1 //int i[1] = "Hello Go" //string i[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) } else if value, ok := data.(string); ok == true { fmt.Printf("x[%d]类型为string,内容为%s\n", index, value) } else if value, ok := data.(Student); ok == true { fmt.Printf("x[%d]类型为Student,内容为name = %s,id = %d\n", index, value.name, value.id) } } }
20.类型断言:switch
package main import "fmt" type Student struct { name string id int } func main() { i := make([]interface{}, 3) i[0] = 1 //int i[1] = "Hello Go" //string i[2] = Student{"mike", 666} //Student //类型查询,类型断言,推荐用这种 for index, data := range i { switch value := data.(type) { case int: fmt.Printf("x[%d] 类型为int,内容为%d\n", index, value) case string: fmt.Printf("x[%d] 类型为string,内容为%s\n", index, value) case Student: fmt.Printf("x[%d] 类型为Student,内容为name = %s,id = %d\n", index, value.name, value.id) } } }