1.Go语言面向对象编程特性
Go语言没有封装,继承,多态这些概念,但它通过以下方式实现了这些特性。
特性 | 实现方式 |
封装 | 通过方法实现 |
继承 | 通过匿名字段实现 |
多态 | 通过接口实现 |
2.Go语言方法
封装是指将对象运行所需的资源封装再程序对象中。而Go语言通过方法实现封装。
2.1定义格式
方法,本质上是一个函数。与普通函数的格式有一点不同,它在关键字func和方法名中间加入了一个特殊的接收器类型。其格式如下:
// 接收器 方法名 参数列表
func (t Type) methodName(parameter list) {
/*方法体代码*/
}
注:a.接收者t的名字自定义,它可以被方法的内部内容访问;
b.接受者即可以是结构体类型,也可以是非结构体类型。
2.2接收者类型
2.2.1结构体类型
由于接收者可以被方法内部内容访问,当接收者类型为结构体时,方法内部也可以访问结构体的成员变量,访问格式为:
接收者.结构体成员变量名
(&接收者).结构体成员变量名
调用此方法的格式为:
结构体类型变量名.方法名()
(&结构体类型变量名).方法名()
package main
import "fmt"
type Animal struct {
name string
sex byte
color string
}
/*接收者:a;接收者类型:Animal;方法名:printInfo*/
func (a Animal) printInfo() {
fmt.Printf("name=%s, sex=%c, color=%s\n", a.name, (&a).sex, a.color)
}
func main() {
p := Animal{"wangcai", 'm', "white"}
p.printInfo() //调用格式:结构体变量名.方法名
(&p).printInfo() //调用格式:(&结构体变量名).方法名
}
2.2.2基础类型
接收者类型为结构体类型,定义的结构体和定义在结构体上的方法同属一个main包,所以结构体类型可以直接使用方法。
基础类型在使用方法前先对基础类型声明类型别名。
package main
import "fmt"
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println("Sum is", sum)
}
2.3 值语义和引用语义
值语义和引用语义,指的是接收者类型分别是值类型和指针类型。
接收者类型 | 说明 |
值类型 | 不论传递到接收者的参数类型是值类型还是指针类型,方法都会将参数类型转换为值类型,在方法内部对值类型的接收者进行改变,方法调用时不会发生改变。 |
指针类型 | 不论传递到接收者的参数是值类型还是指针类型,方法都会将参数类型转换为指针类型,在方法内部对指针类型的接收者进行改变,方法调用时发生改变。 |
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
//接收者类型是值类型
func (a Animal) valueChange(newName, newColor string) {
a.name = newName
(&a).color = newColor
}
//接收者类型是指针类型
func (b *Animal) pointerChange(newName, newColor string) {
b.name = newName
(*b).color = newColor
}
func main() {
p := Animal{"xiaogou", 3, "white"}
/*当接收者类型是值类型,不论传递到接收者的参数类型是值类型还是指针类型,
方法都会将参数类型转换为值类型,在方法内部对值类型的接收者进行改变,
方法调用时相关元素没有改变*/
p.valueChange("wangcai", "huangse")
fmt.Println(p)
(&p).valueChange("旺财", "黄色")
fmt.Println(p)
/*当接收者类型是引用类型,不论传递到接收者的参数类型是值类型还是指针类型,
方法调用时相关元素发生改变*/
(&p).pointerChange("xiaoqiang", "blue")
fmt.Println(p)
p.pointerChange("小强", "蓝色")
fmt.Println(p)
}
注:这和其他语言的类不一样,其他语言类的方法改变类的成员变量直接生效。Go 成员变量是否改变,和其方法的接收器类型有关,只有接收器类型为引用时,才会对接收器的成员变量有效。
3.方法和函数的区别
类型 | 函数 | 方法 |
语法格式 | func A(a int,)(c int){ return a+1 } | type myint int func (a myint)A()(c int){ |
传入对象 | 形参值接收与自己类型相同的实参。 | 接收者类型为值类型或者指针类型的方法均可以接受值类型或者指针类型的传入对象。 |
同名 | 函数不允许同名。 | 同名的方法可以定义在不同类型的接收者。 |
3.1接收者为值类型的方法和参数为值类型的函数测区别
3.1.1 接收者为值类型的方法和参数为值类型的函数的区别
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
func functionValue(a Animal) { //函数参数是值类型
a.name = "wangcai"
(&a).color = "huangse"
}
func (b Animal) methodValue() { //接收者类型是值类型
b.name = "xiaoqiang"
(&b).color = "blue"
}
func main() {
p := Animal{"xiaogou", 3, "white"}
/*当函数参数是值类型,实参只能是值类型*/
functionValue(p)
fmt.Println(p)
//functionValue(&p) //不可以
/*当接收者类型是值类型,对值类型、指针类型的传入对象均可以接受,方法调用时相关元素没有改变*/
(&p).methodValue()
fmt.Println(p)
p.methodValue()
fmt.Println(p)
}
3.1.2 接收者为引用类型的方法和参数为引用类型的函数的区别
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
//函数参数是指针类型
func functionPointer(a *Animal) {
a.name = "wangcai"
(*a).color = "huangse"
}
//接收者类型是指针类型
func (b *Animal) methodPointer() {
b.name = "xiaoqiang"
(*b).color = "blue"
}
func main() {
p := Animal{"xiaogou", 3, "white"}
/*函数参数是指针类型,只能接受指针类型的实参*/
functionPointer(&p)
fmt.Println(p)
//functionPointer(p)
/*当接收者类型是指针类型,对值类型、指针类型的传入对象均可以接受,方法调用时相关成员发生改变*/
(&p).methodPointer()
fmt.Println(p)
p.methodPointer()
fmt.Println(p)
}
3.2 方法和函数在是否可以同名方面的区别
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
type animal struct {
name string
age int
color string
}
func (A Animal) printInfo() {
fmt.Printf("p=%v,p的地址为:%p\n", A, &A)
}
//和上面方法的接收者类型不同,但方法名相同
func (a animal) printInfo() {
fmt.Printf("p=%v,p的地址为:%p\n", a, &a)
}
func printInfo(A Animal) { //函数名可以和方法名相同
fmt.Printf("p=%v,p %p\n", A, &A)
}
//一个包内部不能出现两个相同的函数名,参数不同也不可以
//func printInfo(a animal) {
// fmt.Printf("p=%v,p的地址为:%p\n", a, &a)
//}
func main() {
A := Animal{"xiaogou", 3, "white"}
A.printInfo()
a := animal{"小狗", 3, "白色"}
a.printInfo()
printInfo(A)
}
4.方法值和方法表达式
根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可以像普通函数那样赋值和传参,区别在于方法值需要绑定实例,而方法表达式则需要显式传参。
4.1方法值
方法值进行赋值和传参时,方法值需要绑定实例。
4.2方法表达式
方法表达式进行赋值和传参时必须显式传参。
方法值:
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
//接收者类型是值类型
func (a Animal) printInfoValue() {
fmt.Printf("p=%v,p的地址为:%p\n", a, &a)
}
//接收者指针类型
func (a *Animal) printInfoPointer() {
fmt.Printf("p=%v,p的地址为:%p\n", a, a)
}
func main() {
p := Animal{"xiaogou", 3, "white"}
/*方法接收者为值类型*/
p.printInfoValue()
pFunc1 := p.printInfoValue //方法值进行赋值,绑定实例p
fmt.Printf("%T\n", pFunc1) //func()
pFunc1()
(&p).printInfoValue()
pFunc2 := (&p).printInfoValue //传入参数&p
pFunc2()
/*方法接收器为指针类型*/
p.printInfoPointer()
pFunc3 := p.printInfoPointer
pFunc3()
(&p).printInfoPointer()
pFunc4 := (&p).printInfoPointer
pFunc4()
}
方法表达式
package main
import "fmt"
type Animal struct {
name string
age int
color string
}
//接收者类型是值类型
func (a Animal) printInfoValue() {
fmt.Printf("p=%v,p的地址为:%p\n", a, &a)
}
//接收者指针类型
func (a *Animal) printInfoPointer() {
fmt.Printf("p=%v,p的地址为:%p\n", a, a)
}
func main() {
p := Animal{"xiaogou", 3, "white"}
pFunc1 := Animal.printInfoValue //方法值进行赋值,绑定实例p
fmt.Printf("%T\n", pFunc1) //func(main.Animal)
pFunc1(p)
pFunc2 := (*Animal).printInfoValue //传入参数&p
pFunc2(&p)
/*接收者是指针类型,用方法表达式进行传递时,
接收者只能接受指针类型的传入对象*/
//pFunc3 := Animal.printInfoPointer //不接受值类型
//pFunc3(p)
pFunc4 := (*Animal).printInfoPointer
pFunc4(&p) //*Animal是指针类型,传入的&p是指针类型
}
5.方法的延迟调用
在方法调用之前加上defer,则对方法延迟调用
package main
import "fmt"
type Animal struct {
name string
sex byte
color string
}
func (a Animal) printInfo() {
fmt.Printf("name=%s, sex=%c, color=%s\n", a.name, a.sex, a.color)
}
func main() {
p := Animal{"wangcai", 'm', "white"}
defer p.printInfo() //在方法调用之前家defer
fmt.Println("main")
}