方法
什么是方法?
方法其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
package main import "fmt" type Person struct { name string age int sex int } // 语法 //func (a Person) getName { // //} //方法是给对象调用的 //给Person结构体加一个打印名字的方法 func (a Person)printName() { fmt.Println(a.name) } func main() { p:=Person{name:"GAI"} p.printName() }
>>>GAI
为什么我们已经有函数了还需要方法呢?
针对指针类型的数据时,方法获取元素比函数更强大。
package main import "fmt" //方法 其实就是一个函数 type Person struct { name string age int sex int } //语法 //func (a Person) getName() { // //} //方法给对象用 //给Person结构体加一个打印名字的方法 func (a Person)printName() { fmt.Println(a.name) } //func (a Person)changeName(name string) { // a.name=name // fmt.Println(a) //} func (a *Person)changeName(name string) { a.name=name fmt.Println(a) } //func printName(a Person) { //// fmt.Println(a.name) ////} ////func printName(a *Person) { //// fmt.Println(a.name) ////} func main() { var p *Person=&Person{name:"lqz"} //这种也可以,go给你处理了 var p Person=Person{name:"lqz"} p.changeName("egon") fmt.Println(p) p.printName() //为什么我们已经有函数了还需要方法呢 //执行函数 //printName(p) //printName(&p) }
指针接收器与值接收器
到目前为止,我们只看到了使用值接收器的方法。还可以创建使用指针接收器的方法。值接收器和指针接收器之间的区别在于,
在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的情况不是这样的。让我们用下面的程序来帮助理解这一点。
//指针接收器与值接收器 //func (值)changeName(name string):在内部修改值,不会影响外部的值 //func (指针)changeName(name string):在内部修改值,会影响外部的值
那么什么时候使用指针接收器,什么时候使用值接收器?
//那么什么时候使用指针接收器,什么时候使用值接收器 //想修改原值,就用指针接收器 //匿名字段的方法
匿名字段的方法
属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。
//匿名字段的方法 package main import "fmt" //方法 其实就是一个函数 type Person1 struct { name string age int sex int Hobby } type Hobby struct { id int hobbyname string } func (h Hobby)printHobbyId() { fmt.Println(h.id) } func (a Person1)printName() { fmt.Println(a.name) } func (a Person1)printHobbyId() { fmt.Println("我重写了") //重用父类的方法 //a.Hobby.printHobbyId() fmt.Println(a.Hobby.id) } func main() { //p:=Person1{name:"lqz",Hobby:Hobby{id:10}} // ////正常操作 ////p.Hobby.printHobbyId() ////非正常操作 方法也提升了 //p.printHobbyId() }
在方法中使用值接收器 与 在函数中使用值参数;在方法中使用指针接收器 与 在函数中使用指针参数
当一个函数有一个值参数,它只能接受一个值参数。
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
package main import "fmt" //在方法中使用值接收器 与 在函数中使用值参数 //在方法中使用指针接收器 与 在函数中使用指针参数 type Person2 struct { name string age int sex int } //方法中使用值接收器 func (a Person2)printName() { fmt.Println(a.name) } //方法中使用指针接收器 func (a *Person2)printName2() { fmt.Println(a.name) } //函数中使用值参数 func printName(a Person2) { fmt.Println(a.name) } //在函数中使用指针参数 func printName2(a *Person2) { fmt.Println(a.name) } func main() { //p:=&Person2{name:"lqz"} //调用值接收器 //p.printName() //调用指针接收器 //p.printName2() //调用值函数 //printName(p) //调用指针函数 //printName2(&p) //不管是指针接收器还是值接收器,都可以使用值来调用 //不管是指针接收器还是值接收器,都可以使用指针来调用 //函数中,是什么参数,就得传什么参数 }
在非结构体上的方法
package main import "fmt" //在非结构体上的方法 //重命名 type MyInt int func (i *MyInt)add() { (*i)++ fmt.Println(*i) } func main() { var a MyInt=10 a.add() fmt.Println(a) }
接口
什么是接口?
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
例如,WashingMachine
是一个含有 Cleaning()
和 Drying()
两个方法的接口。任何定义了 Cleaning()
和 Drying()
的类型,都称它实现了 WashingMachine
接口。
类似Python中的接口,定义一个接口,继承可以实现不同的方法,.属性即可调用接口中的属性方法。
package main import "fmt" //接口 //接口是一系列方法的集合 //语法 //type 接口名 interface{ // 方法一 // 方法二 //} //定义了一个鸭子接口 type Duck interface { run() speak() } //结构体实现该接口(只要实现了接口中的所有方法,该结构体就叫实现了Duck接口) //侵入式接口和非侵入式接口(了解) //定义普通鸭子结构体 type PDuck struct { name string age string } func (p PDuck)run() { fmt.Println("我是普通鸭子,我名字叫",p.name,"我走路歪歪扭扭") } func (p PDuck)speak() { fmt.Println("我是普通鸭子,我名字叫",p.name,"我嘎嘎说话") } //定义一个唐老鸭,实现鸭子接口 type TDuck struct { name string age string wife bool } func (p TDuck)run() { fmt.Println("我是普通鸭子,我名字叫",p.name,"我走路歪歪扭扭") } func (p TDuck)speak() { fmt.Println("我是普通鸭子,我名字叫",p.name,"我嘎嘎说话") } func main() { pD:=PDuck{name:"水鸭子"} tD:=TDuck{name:"唐老鸭"} //写一个函数,让鸭子说话,不管是唐老鸭还是普通鸭子 //speak(pD) //speak(tD) //var d Duck //d=pD //d=tD //fmt.Println(d) //问题研究:我想在函数中拿出唐老鸭的wife属性 speak(tD) speak(pD) } //func speak(p PDuck) { // p.speak() //} //func speak2(p TDuck) { // p.speak() //} //func speak(p Duck) { // p.speak() //} //我想在函数中拿出唐老鸭的wife属性 //func speak(p Duck) { // //类型断言,我断言你是TDuck类型,如果没有问题,转成TDuck类型 // a:=p.(TDuck) // fmt.Println(a.wife) // p.speak() //} func speak(p Duck) { switch a:=p.(type) { case PDuck: //判断好了,你是普通鸭子,把鸭子名字拿出来 fmt.Println("你是普通鸭子") fmt.Println(a.name) case TDuck: //判断好了,你是唐老鸭,把唐老鸭的wife拿出来 fmt.Println("你是唐老鸭") fmt.Println(a.wife) } }
空接口
没有包含方法的接口称为空接口。空接口表示为 interface{}
。由于空接口没有方法,因此所有类型都实现了空接口。
package main import "fmt" //空接口(一个方法都没有) //匿名空接口 //所有的数据类型都实现了空接口 type Empty interface { } type TDuck2 struct { name string age string wife bool } func main() { test(1) test("ssss") test(TDuck2{}) test(10.45) var a Empty =1 var b Empty ="dddd" } //func test(a Empty) { // fmt.Println(a) //} func test(a interface{}) { switch a.(type) { case int: fmt.Println("你是int类型") case string: fmt.Println("你是string ") case TDuck2: fmt.Println("你是唐老鸭") default: fmt.Println("我不知道你是什么类型") } }
函数接收空接口作为参数,因此,可以给这个函数传递任何类型。
类型断言
类型断言用于提取接口的底层值(Underlying Value)。
类型选择(Type Switch)
类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值。
类型选择的语法类似于类型断言。类型断言的语法是 i.(T)
,而对于类型选择,类型 T
由关键字 type
代替。
接口的其他方法:
实现多个接口
类型可以实现多个接口。我们看看下面程序是如何做到的。
package main import "fmt" //实现多个接口 //type SalaryCalculator interface { // DisplaySalary() //} // //type LeaveCalculator interface { // CalculateLeavesLeft() int //} // //type Employee struct { // firstName string // lastName string // basicPay int // pf int // totalLeaves int // leavesTaken int //} // //func (e Employee) DisplaySalary() { // fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf)) //} // //func (e Employee) CalculateLeavesLeft() int { // return e.totalLeaves - e.leavesTaken //} // //func main() { // e := Employee { // firstName: "Naveen", // lastName: "Ramanathan", // basicPay: 5000, // pf: 200, // totalLeaves: 30, // leavesTaken: 5, // } // var s SalaryCalculator = e // s.DisplaySalary() // var l LeaveCalculator = e // fmt.Println(" Leaves left =", l.CalculateLeavesLeft()) //} //接口的零值 nil 接口是引用类型 type Describer interface { Describe() } func main() { var d1 Describer if d1 == nil { fmt.Println("xxxx") } }
接口的嵌套
尽管 Go 语言没有提供继承机制,但可以通过嵌套其他的接口,创建一个新接口。
//接口嵌套 type SalaryCalculator interface { DisplaySalary() } type LeaveCalculator interface { CalculateLeavesLeft() int } type EmployeeOperations interface { SalaryCalculator LeaveCalculator } type Employee struct { firstName string lastName string basicPay int pf int totalLeaves int leavesTaken int } func (e Employee) DisplaySalary() { fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf)) } func (e Employee) CalculateLeavesLeft() int { return e.totalLeaves - e.leavesTaken } func main() { e := Employee { firstName: "Naveen", lastName: "Ramanathan", basicPay: 5000, pf: 200, totalLeaves: 30, leavesTaken: 5, } var empOp EmployeeOperations = e empOp.DisplaySalary() fmt.Println(" Leaves left =", empOp.CalculateLeavesLeft()) }
接口的零值
接口的零值是 nil
。对于值为 nil
的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil
。
//接口的零值 nil 接口是引用类型 type Describer interface { Describe() } func main() { var d1 Describer if d1 == nil { fmt.Println("xxxx") } }
异常处理
错误表示程序中出现了异常情况。比如当我们试图打开一个文件时,文件系统里却并没有这个文件。这就是异常情况,它用一个错误来表示。
在 Go 中,错误一直是很常见的。错误用内建的 error
类型来表示。
就像其他的内建类型(如 int
、float64
等),错误值可以存储在变量里、作为函数的返回值等等。
总结:
//异常处理 //defer panic recover
//defer 表示延迟调用,即便程序出现严重错误,也会执行 //panic 就是python中的raise(主动抛出异常) //recover 恢复程序,继续执行
常见异常处理的方法:
package main import "fmt" func main() {
//先注册,后调用 //defer fmt.Println("xxxx") //defer fmt.Println("yyy") f1() f2() f3() } func f1() { fmt.Println("f1...") } func f2() { defer func() { if a:=recover();a!=nil{
//a 如果不等于nil,表示程序出了异常,a 就是异常信息 //a 等于nil,表示没有异常 //fmt.Println("出错了")
fmt.Println(a) } //用于会被执行(相当于finally) }() fmt.Println("f2...") //var a =make([]int,3,3) //fmt.Println(a[4]) panic("你给我出去") } func f3() { fmt.Println("f3...") }
错误处理
错误表示程序中出现了异常情况。比如当我们试图打开一个文件时,文件系统里却并没有这个文件。这就是异常情况,它用一个错误来表示。
在 Go 中,错误一直是很常见的。错误用内建的 error
类型来表示。
就像其他的内建类型(如 int
、float64
等),错误值可以存储在变量里、作为函数的返回值等等。
package main import ( "errors" "fmt" ) //错误 func circleArea(radius int) (int, error) { if radius < 0 { return 0, errors.New("错误信息") //panic("xddd") } return 100, nil } func main() { a,_:=circleArea(-10) if err!=nil{ fmt.Println(err) } //fmt.Println(err) fmt.Println(a) _,err:=fmt.Println() if err!=nil{ fmt.Println("打印出错") } }
主动的返回error