面向对象编程:继承,封装,多态,重用,多线程,低耦合。
封装:通过方法实现。
继承:通过匿名字段实现。
多态:通过接口实现。
Go语言的匿名字段/匿名组合/嵌入字段:
结构体类型匿名字段:匿名字段既会继承字段的成员变量,又会继承字段的方法(方法在下文详解)。
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person //匿名字段,只有类型,没有名字。继承字段实现代码复用即继承了Person全部成员
id int
addr string
}
func (tmp *Person) PrintInfo(){ //Person实现的方法,通过匿名字段可以被Student调用
fmt.Printf("name=%s, sex=%c, age=%d\n",tmp.name,tmp.sex,tmp.age)
}
匿名字段的初始化:
顺序初始化/全部初始化:
var s1 Student = Student{Person{"tom",'m',18},1,"北京"}
fmt.Printf("s = %+v",s) //格式化打印:%+v占位,详细输出
指定成员初始化,没有初始化的成员自动赋值为0:
s2:=Student{Person:Person{name:"tom"},addr:"北京"}
成员的操作:与结构体完全一致
匿名字段的整体赋值:
s1.Person=Person{"tom",'m',18}
处理匿名字段中的同名字段:
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person
id int
addr string
name string //同名字段
}
赋值同名字段:就近原则,与同名局部/全局变量规则相同。如果能在本作用域找到此成员则对起进行操作,如果没有找到则找继承的字段操作。直接操作继承字段的同名字段,则运用以下语法:显式调用
s1.Person.name = "go"
非结构体匿名字段:
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person //结构体匿名字段
int //非结构体匿名字段
string
}
赋值时与正常结构体无区别,但要注意的是在使用非结构体匿名字段时,可以直接用其类型名来代替成员名使用。如:
s1.Person
s1.int
结构体指针类型匿名字段:
type Student struct{
*Person //指针匿名字段
id int
addr string
}
指针匿名字段初始化:在对指针类型进行整体打印时只会显示内存地址。
直接初始化:
s1 := Student{&Person{"tom",'m',18},1,"北京"}
分配空间及赋值初始化:
var s2 Student
s2.Person=new(Person) //分配空间
s2.name="tom"
s2.sex='m'
s2.age=18
s2.id=1
s2.addr="北京"
方法/method绑法函数:func (reviver Type) funname(){}
接收者reciver,绑定类型Type。带有接收者的函数就叫做方法。
通过方法讨论面向过程和变相对象函数的区别:
面向过程:
func add(a,b int)int{
return a+b
}
func main(){
result:=add(1,1)
}
面向对象:
type long int //int别名为long
func (tmp long) add(other long) long{ //add方法绑定类型接收者,tmp接收者,就是一个传递参数
return tmp+other
}
func main(){
var a long = 2
result:=a.add(3) //调用格式:变量名.函数(参数)
}
需要注意的是:Go语言类型检查非常严格,此时long与int不能相互接收。面向对象函数只是换了一种表现形式。
结构体类型绑定添加函数方法:
type Person struct{
name string
sex byte
age int
}
func (tmp Person) PrintInfo(){
fmt.Println(tmp)
}
func main(){
p:=Person{"tom",'m',18}
p.PrintInfo()
}
指针作为接收者:
func (p *Person) SetInfo(n string,s byte,a int){
p.name=n
p.sex=s
p.age=a
}
func main(){
var p2 Person
(&p2).SetInfo("tom",'m',18)
p2.PrintInfo()
}
需要注意的是:接受者不能原本就是指针类型,方法不支持重载/重复定义(接收者类型不同,方法同名不算做重载)。
值语义和引用语义:传递类型
接收者为普通变量:值语义(复制)
func (p Person) SetInfoValue(n string,s byte,a int){
p.name=n
p.sex=s
p.age=a
}
func main(){
s1:=new(Person)
s1.SetInfoValue("tom",'m',18)
}
接收者为指针变量:引用语义(地址传递)
func (p *Person) SetInfoValue(n string,s byte,a int){
p.name=n
p.sex=s
p.age=a
}
func main(){
s1:=new(Person)
(&s1).SetInfoValue("tom",'m',18)
}
Go语言中的方法集:定义的变量可以调用的方法就是方法集。
指针变量调用普通变量:
正常调用:(*p).SetInfoValue()
内部转换:p.SetInfoValue() 先把指针p转为*p再调用
指针变量调用指针变量:
正常调用:p.SetInfoPointer()
内部转换:(*p).SetInfoValue()先把*p转换成p再调用
指针变量调用指针变量:
内部转换:p.SetInfoPointer() 内部先把p转化为&p再调用
普通变量调用普通变量:
正常调用:p.SetInfoValue()
总结:用实例value和pointer调用方法(含匿名字段)不受方法集的约束,编译器总是查找全部方法,并自动转换receiver实参。
方法的继承:方法可以通过匿名字段继承给其他结构体,其他结构体可以直接调用。
方法的重写/同名方法:
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person
id int
addr string
}
func (tmp *Person) PrintInfo(){ //Person实现的方法,通过匿名字段可以被Student调用
fmt.Printf("name=%s, sex=%c, age=%d\n",tmp.name,tmp.sex,tmp.age)
}
func (tmp *Student) PrintInfo(){ //Srudent实现的同名方法
fmt.Printfln(tmp)
}
同名方法的调用:就近原则,优先本作用域方法。若直接操作继承的同名方法,则需要显式方法。
s.Person.PrintInfo()
方法值和方法表达式:
func (p Person) SetInfoValue(){
fmt.Printf("SetInfoValue: %p, %v\n", &p, p)
}
func (p *Person) SetInfoPointer(){
fmt.Printf("SetInfoPointer: %p, %v\n", p, p)
}
方法值:函数指针的使用。
p.SetInfoPointer() //传统调用方式
pFunc:=p.SetInfoPointer //方法值保存函数入口
pFunc() //调用函数,不需要再传递接收者,接收者被隐藏
方法表达式:
f1:=(*Person).SetInfoPointer //显式传递接收者
f1(&p) //也可去掉&
f2:=(Person).SetInfoValue
f2(p)
Go语言的接口:描述了一系列方法的结合,只声明函数不去实例化。只关心实现的行为。
接口的定义:
type Humaner interface{
sayhi() //只有声明,由自定义类型实现
}
接口的实现:
func (tmp *Student) sayhi(){
}
接口变量的定义和赋值,调用:
var i Humaner
s:=&Student{}
i=s
i.sayhi() //只要实现了此接口方法的类型,那么这个类型的变量就可以给i赋值,调用自动匹配方法
多态的实现:定义普通函数,函数的参数为接口类型:
func WhoSayHi(i Humaner){
i.sayhi() //只有一个函数,拥有不同表现--多态
}
接口调用:
WhoSayHi(s) //普通调用
x:=make([]Humaner, 1) //创建切片
x[0]=s
for _,data:=range x{ //通过迭代调用
i.sayhi()
}
接口的继承:
type Humaner interface{ //子集
sayhi()
}
type Personer interface{ //超集
Humaner //匿名字段,继承了sayhi()
sing(lrc string) //含有参数的方法
}
func (tem *Student) sayhi(){
}
func (tem *Student) saing(lrc string){
}
func main(){
var i Personer
s:=&Student{}
i=s
i.sayhi() //继承的方法
i.sing("song")
}
接口转换:超集变量可以转换为子集,但反过来不行
var iPro Personer //超集
var i Humaner //子集
iPro=i //err
i=iPro
空接口类型:万能类型,可以保存任何类型的值。没有方法,所有类型都实现了这个接口。
var i interfave{}=1
i="abc"
var a []int //切片空接口
var b []interface{} //空接口
参数为空接口类型的函数:
func fun(args ...interface{}){
} //可以报错0-多个不限类型的参数
类型断言/类型查询:
通过if的类型断言:
func main(){
i := make([]interface{}, 3)
i[0] = 1 //int
i[1] = "hello go" //string
i[3] = Student{} //struct
for index, data := range i{ //index返回的是下标,data返回空接口变量
if value, ok := data.(int); ok==true{ //value返回接口变量本身,ok返回bool类
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)
}
}
通过switch的类型断言:
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)
}
}
通过格式化输出的类型断言:
for index, data := range i{
fmt.Printf("x[%d]的类型为%T,内容为%v\n", index, value, value)
} //自动类型的类型断言