1 方法
Go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。也可以声明一些方法,属于某个结构体。
1.1 语法
Go中的方法,是一种特殊的函数,定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。
通俗的讲,方法就是有接收者的函数。
语法格式如下:
type mytype struct{}
func (recv mytype) my_method(para) return_type {}
func (recv *mytype) my_method(para) return_type {}
// 参数说明:
1.`mytype`:定义一个结构体
2.`recv`:接受该方法的结构体(receiver)
3.`my_method`:方法名称
4.`para`:参数列表
5.`return_type`:返回值类型
// 从语法格式可以看出,方法和函数非常相似,多了一个接受类型。
1.2 实例
type Person struct {
name string
}
type Customer struct {
name string
}
// 属性和方法分开写
// (per Person)---接收者(receiver)
// eat是属于Person这个结构体的
// 方法是通过接收者与结构体关联在一起的
func (per Person) eat() {
fmt.Printf("%v,eat...", per.name)
}
func (per Person) sleep() {
fmt.Printf("%v,sleep...", per.name)
}
func (cust Customer) login(name string, pwd string) bool {
fmt.Printf("cust.name: %v\n", cust.name)
if name == "kite" && pwd == "123" {
return true
} else {
return false
}
}
func main() {
per_method := Person{
name: "kite",
}
per_method.eat()
per_method.sleep()
cus := Customer{
name: "kite",
}
b := cus.login("kite", "123")
fmt.Printf("b: %v\n", b)
}
1.3 方法的注意事项
1.方法的receiver type并非一定要是struct类型,type定义的类型别名、slice、map、channel、func类型等都可以。
2.struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
3.方法有两种接收类型:(T Type)和(T *Type),它们之间有区别。
4.方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
5.如果receiver是一个指针类型,则会自动解除引用。
6.方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
1.4 结构体类型
结构体有值类型和指针类型,方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是否复制结构体副本。值类型复制,指针类型不复制。
type Person struct {
name string
}
func test1() {
p1 := Person{
name: "Tom",
}
p2 := &Person{
name: "Tom",
}
fmt.Printf("p1: %T\n", p1)
fmt.Printf("p2: %T\n", p2)
}
// 运行结果:p1: main.Person p2: *main.Person
// 从运行结果,可以看出p1是值类型,p2是指针类型。
1.4.1 值类型结构体
type Person struct {
name string
}
func showPerson1(per Person) {
per.name = "Tom..."
}
func test2() {
p1 := Person{
name: "Tom",
}
showPerson1(p1)
fmt.Printf("p1: %v\n", p1)
}
// 运行结果:p1: {Tom}
// 从运行结果看,p1是值传递,拷贝了副本,地址发生了改变
1.4.2 指针类型结构体
type Person struct {
name string
}
func showPerson2(per *Person) {
// per.name 自动解引用
// (*per).name = "Tom..."
per.name = "Tom..."
}
func test2() {
p2 := &Person{
name: "Tom",
}
fmt.Println("------------------")
showPerson2(p2)
fmt.Printf("p2: %v\n", *p2)
}
// 运行结果:p2: {Tom...}
// 从运行结果看,p2是指针类型,地址没有改变。
1.5 方法接收者类型
结构体有值类型和指针类型,方法的接收者是结构体,那么也有值类型和指针类型。区别就是接收者是否复制结构体副本。值类型复制,指针类型不复制。
1.5.1 方法的值类型接收者
值类型和指针类型接收者,本质上和函数传参道理相同。
type Person struct {
name string
}
func (per Person) showPerson3() {
per.name = "Tom..."
}
func test3() {
p1 := Person{
name: "Tom",
}
p1.showPerson3()
fmt.Printf("p1: %v\n", p1)
}
// 运行结果:p1: {Tom}
// 从运行结果看,p1是值传递,拷贝了副本,地址发生了改变。
1.5.1 方法的指针类型接收者
值类型和指针类型接收者,本质上和函数传参道理相同。
type Person struct {
name string
}
func (per *Person) showPerson4() {
// per.name 自动解引用
// (*per).name = "Tom..."
per.name = "Tom..."
}
func test3() {
p2 := &Person{
name: "Tom",
}
p2.showPerson4()
fmt.Printf("p2: %v\n", *p2)
}
// 运行结果:p2: {Tom...}
// 从运行结果看,p2是指针类型,地址没有改变。
2 接口
接口像是一个公司里面的领导,会定义一些通用规范,只设计规范,而不实现规范。
Go语言的接口,是一种新的类型定义,把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
语法格式和方法非常类似。
2.1 语法格式
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
在接口定义中定义,若干个空方法。这些方法都具有通用性。
2.2 接口实例
定义一个USB接口,有读(read)和写(write)两个方法,再定义一个电脑Computer和一个手机Mobile来实现这个接口。
// 系统中有一些接口,如:writer、Reader...
// USB接口
type USB interface {
read()
write()
}
// Computer结构体
type Computer struct {
name string
}
// Mobile结构体
type Mobile struct {
model string
}
// Computer实现USB接口的方法
func (c Computer) read() {
fmt.Printf("c.name: %v\n", c.name)
fmt.Println("Computer read...")
}
func (c Computer) write() {
fmt.Printf("c.name: %v\n", c.name)
fmt.Println("Computer wtite...")
}
// Mobile实现USB接口的方法
func (m Mobile) read() {
fmt.Printf("m.model: %v\n", m.model)
fmt.Println("Mobile read...")
}
func (m Mobile) wtite() {
fmt.Printf("m.model: %v\n", m.model)
fmt.Println("Mobile write...")
}
// 测试
func main() {
c_windows := Computer{
name: "联想",
}
c_windows.read()
c_windows.write()
m_mobile := Mobile{
model: "华为Nova 15",
}
m_mobile.read()
m_mobile.wtite()
}
2.3 接口注意事项
实现接口必须实现接口中的所有方法。
定义一个OpenClose接口,里面有两个方法open和close,定义一个Door结构体,实现其中一个方法。
type OpenClose interface {
open()
close()
}
type Door struct {
}
func (d Door) open() {
fmt.Println("open door...")
}
func main() {
var oc OpenClose
oc = Door{} // 这里编译错误,提示只实现了一个接口
}
2.4 接口类型
2.4.1 值类型接收者
golang接口值类型接收者和指针类型接收者
这个话题,本质上和方法的值类型接收者和指针类型接收者,的思考方法是一样的,值接收者是一个拷贝,是一个副本,而指针接收者,传递的是指针。
实例演示
定义一个Pet接口
type Pet interface {
eat()
}
定义一个Dog结构体
type Dog struct {
name string
}
实现Pet接口(接收者是值类型)
func (dog Dog) eat() {
fmt.Printf("dog: %p\\n", &dog)
fmt.Println("dog eat..")
dog.name = "黑黑"
}
测试
func main() {
dog := Dog{name: "花花"}
fmt.Printf("dog: %p\\n", &dog)
dog.eat()
fmt.Printf("dog: %v\\n", dog)
}
运行结果
dog: 0xc000046240
dog: 0xc000046250
dog eat..
dog: {花花}
从运行结果,我们看出dog的地址变了,说明是复制了一份,dog的name没有变说明,外面的dog变量没有被改变。
将Pet接口改为指针接收者
func (dog *Dog) eat() {
fmt.Printf("dog: %p\\n", dog)
fmt.Println("dog eat..")
dog.name = "黑黑"
}
再测试
func main() {
dog := &Dog{name: "花花"}
fmt.Printf("dog: %p\\n", dog)
dog.eat()
fmt.Printf("dog: %v\\n", dog)
}
运行结果
dog: 0xc00008c230
dog: 0xc00008c230
dog eat..
dog: &{黑黑}