8小时速成golang(四)语法新奇 defer slice 和 map 面向对象的特征 封装 继承 多态
5、defer
defer作用:
●释放占用的资源
●捕捉处理异常
●输出日志
func Demo(){ defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") defer fmt.Println("4") } func main() { Demo() }
结果
4 3 2 1
一、defer执行顺序
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。package main //知识点一:defer 的执行顺序package main import "fmt" func func1(){ fmt.Println("A") } func func2(){ fmt.Println("B") } func func3(){ fmt.Println("C") } func main(){ defer func1() defer func2() defer func3() }
defer是当前函数的声明周期全部结束再结束的 即}之后 所以defer是在return之后
//知识点二: defer和return谁先谁后 package main import "fmt" func deferFunc()int{ fmt.Println("defer func called...") return 0 } func returnFunc()int{ fmt.Println("return func called...") return 0 } func returnAndDefer()int { defer deferFunc() return returnFunc() } func main() { returnAndDefer() }
结果
return func called... defer func called...
func recover interface{}
运行时panic异常一旦被引发就会导致程序崩溃。
Go语言提供了专用于“拦截”运行时panic的内建函数“recover”。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。
注意:recover只有在defer调用的函数中有效。
package main import "fmt" func Demo(i int) { //定义10个元素的数组 var arr [10]int //错误拦截要在产生错误前设置 defer func() { //设置recover拦截错误信息 err := recover() //产生panic异常 打印错误信息 if err != nil { fmt.Println(err) } }() //根据函数参数为数组元素赋值 //如果i的值超过数组下标 会报错误:数组下标越界 arr[i] = 10 } func main() { Demo(10) //产生错误后 程序继续 fmt.Println("程序继续执行...") }
结果
runtime error: index out of range 程序继续执行...
package main import ( "fmt" ) func main() { //第一种声明 var test1 map[string]string //在使用map前,需要先make,make的作用就是给map分配数据空间 test1 = make(map[string]string, 10) test1["one"] = "php" test1["two"] = "golang" test1["three"] = "java" fmt.Println(test1) //map[two:golang three:java one:php] //第二种声明 test2 := make(map[string]string) test2["one"] = "php" test2["two"] = "golang" test2["three"] = "java" fmt.Println(test2) //map[one:php two:golang three:java] //第三种声明 test3 := map[string]string{ "one" : "php", "two" : "golang", "three" : "java", } fmt.Println(test3) //map[one:php two:golang three:java] language := make(map[string]map[string]string) language["php"] = make(map[string]string, 2) language["php"]["id"] = "1" language["php"]["desc"] = "php是世界上最美的语言" language["golang"] = make(map[string]string, 2) language["golang"]["id"] = "2" language["golang"]["desc"] = "golang抗并发非常good" fmt.Println(language) //map[php:map[id:1 desc:php是世界上最美的语言] golang:map[id:2 desc:golang抗并发非常good]] //增删改查 // val, key := language["php"] //查找是否有php这个子元素 // if key { // fmt.Printf("%v", val) // } else { // fmt.Printf("no"); // } //language["php"]["id"] = "3" //修改了php子元素的id值 //language["php"]["nickname"] = "啪啪啪" //增加php元素里的nickname值 //delete(language, "php") //删除了php子元素 fmt.Println(language) }
二、map的操作 增删改查
package main import "fmt" func main() { cityMap := make(map[string]string) //添加 cityMap["china"]="Beijing" cityMap["Japan"]="Tokyo" cityMap["USA"]="NewYork" //删除 delete(cityMap,"Japan") //修改 cityMap["USA"]="DC" fmt.Println("------") //遍历 for key,value :=range cityMap{ fmt.Println("key =",key) fmt.Println("value =",value) } }
结果
------
key = china
value = Beijing
key = USA
value = DC
7、面向对象特征
一、面向对象类的 表示 与 封装
==================================================================
类名、属性名、方法名 首字母大写表示对外(其他包)可以访问,否则只能够在本包内访问
==================================================================
package main import "fmt" //如果类名首字母大写,表示其地台也能够访问 type Hero struct{ //如果说类的属性首字母大写,表承设属性是对外能够访问的,香则的话只能够当前的内能访问 Name string Ad int level int } func(this *Hero) Show(){ fmt.Println("Name:",this.Name) fmt.Println("Ad:",this.Ad) fmt.Println("Level:",this.level) } func(this *Hero) GetName() string{ return this.Name } func (this *Hero) SetName(newName string){ //this 是调用该方法的对象的一个副本(拷贝) this.Name = newName } func main() { hero :=Hero{Name:"zhang3",Ad:100} hero.Show() hero.SetName("li4") hero.Show() fmt.Println() }
结果
Name: zhang3 Ad: 100 Level: 0 Name: li4 Ad: 100 Level: 0
二、面向对象的继承
没有私有化 公有化 只有对包内和包外
package main import "fmt" type Human struct { name string sex string } func (this *Human) Eat() { fmt.Println("Human.Eat()...") } func (this *Human) Walk() { fmt.Println("Human.Walk()...") } //================= type SuperMan struct { Human //SuperMan类继承了Human类的方法 level int } //重定义父类的方法Eat() func (this *SuperMan) Eat() { fmt.Println("SuperMan.Eat()...") } //子类的新方法 func (this *SuperMan) Fly() { fmt.Println("SuperMan.Fly()...") } func (this *SuperMan) Print() { fmt.Println("name = ", this.name) fmt.Println("sex = ", this.sex) fmt.Println("level = ", this.level) } func main() { h := Human{"zhang3", "female"} h.Eat() h.Walk() //定义一个子类对象 //s := SuperMan{Human{"li4", "female"}, 88} var s SuperMan s.name = "li4" s.sex = "male" s.level = 88 s.Walk() //父类的方法 s.Eat() //子类的方法 s.Fly() //子类的方法 s.Print() }
结果
Human.Eat()... Human.Walk()... Human.Walk()... SuperMan.Eat()... SuperMan.Fly()... name = li4 sex = male level = 88
假设有两个方法,一个方法的接收者是指针类型,一个方法的接收者是值类型,那么:
●对于值类型的变量和指针类型的变量,这两个方法有什么区别?
●如果这两个方法是为了实现一个接口,那么这两个方法都可以调用吗?
●如果方法是嵌入到其他结构体中的,那么上面两种情况又是怎样的?
package main
import "fmt"
//定义一个结构体
type Book struct {
title string
auth string
}
func changeBook(book Book) {
//值传递,传递一个book的副本
book.auth = "Jams"
}
func changeBook2(book *Book) {
//指针传递
book.auth = "777"
}
func main() {
var book1 Book
book1.title = "Golang"
book1.auth = "zhang3"
changeBook(book1)
fmt.Printf("%v\n",book1)
changeBook2(&book1)
fmt.Printf("%v\n",book1)
}
运行结果
{Golang zhang3}
{Golang 777}
package main
import "fmt"
//定义一个结构体
type T struct {
name string
}
func (t T) method1() {
t.name = "new name1"
}
func (t *T) method2() {
t.name = "new name2"
}
func main() {
t := T{"old name"}
fmt.Println("method1 调用前 ", t.name)
t.method1()
fmt.Println("method1 调用后 ", t.name)
fmt.Println("method2 调用前 ", t.name)
t.method2()
fmt.Println("method2 调用后 ", t.name)
}
结果
method1 调用前 old name
method1 调用后 old name
method2 调用前 old name
method2 调用后 new name2
当调用t.method1()
时相当于method1(t)
,实参和行参都是类型 T,可以接受。此时在method1
()中的t只是参数t的值拷贝,所以method1
()的修改影响不到main中的t变量。
当调用t.method2()
=>method2(t)
,这是将 T 类型传给了 *T 类型,go可能会取 t 的地址传进去:method2(&t)
。所以 method1
() 的修改可以影响 t。
T 类型的变量这两个方法都是拥有的。
方法值和方法表达式
方法值
我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,
实际上将其分成两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"一个将方法(Point.Distance)
绑定到特定接收器变量的函数
。这个函数可以不通过指定其接收器即可被调用;即调用时不需要指定接收器,只要传入函数的参数即可:
package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
distanceFormP := p.Distance // 方法值(相当于C语言的函数地址,函数指针)
fmt.Println(distanceFormP(q)) // "5"
fmt.Println(p.Distance(q)) // "5"
//实际上distanceFormP 就绑定了 p接收器的方法Distance
distanceFormQ := q.Distance //
fmt.Println(distanceFormQ(p)) // "5"
fmt.Println(q.Distance(p)) // "5"
//实际上distanceFormQ 就绑定了 q接收器的方法Distance
}
在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,方法"值"会非常实用.
举例来说,下面例子中的time.AfterFunc这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函数。且这个函数操作的是一个Rocket对象r
type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
直接用方法"值"传入AfterFunc的话可以更为简短:
time.AfterFunc(10 * time.Second, r.Launch)
省掉了上面那个例子里的匿名函数。
方法表达式
和方法"值"相关的还有方法表达式。当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。
当T是一个类型时,方法表达式可能会写作T.f
或者(*T).f
,会返回一个函数"值",这种函数会将其第一个参数用作接收器,
所以可以用通常(译注:不写选择器)的方式来对其进行调用:
package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
distance1 := Point.Distance //方法表达式, 是一个函数值(相当于C语言的函数指针)
fmt.Println(distance1(p, q))
fmt.Printf("%T\n", distance1) //%T表示打出数据类型 ,这个必须放在Printf使用
distance2 := (*Point).Distance //方法表达式,必须传递指针类型
distance2(&p, q)
fmt.Printf("%T\n", distance2)
}
执行结果
5
func(main.Point, main.Point) float64
func(*main.Point, main.Point) float64
// 这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
// 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
// 看起来本书中函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。
当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。
你可以根据选择来调用接收器各不相同的方法。下面的例子,变量op代表Point类型的addition或者subtraction方法,
Path.TranslateBy方法会为其Path数组中的每一个Point来调用对应的方法:
package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p Point) Add(another Point) Point {
return Point{p.X + another.X, p.Y + another.Y}
}
func (p Point) Sub(another Point) Point {
return Point{p.X - another.X, p.Y - another.Y}
}
func (p Point) Print() {
fmt.Printf("{%f, %f}\n", p.X, p.Y)
}
//定义一个Point切片类型 Path
type Path []Point
//方法的接收器 是Path类型数据, 方法的选择器是TranslateBy(Point, bool)
func (path Path) TranslateBy(another Point, add bool) {
var op func(p, q Point) Point //定义一个 op变量 类型是方法表达式 能够接收Add,和 Sub方法
if add == true {
op = Point.Add //给op变量赋值为Add方法
} else {
op = Point.Sub //给op变量赋值为Sub方法
}
for i := range path {
//调用 path[i].Add(another) 或者 path[i].Sub(another)
path[i] = op(path[i], another)
path[i].Print()
}
}
func main() {
points := Path{
{10, 10},
{11, 11},
}
anotherPoint := Point{5, 5}
points.TranslateBy(anotherPoint, false)
fmt.Println("------------------")
points.TranslateBy(anotherPoint, true)
}
运行结果
{5.000000, 5.000000}
{6.000000, 6.000000}
------------------
{10.000000, 10.000000}
{11.000000, 11.000000}
三、面向对象的多态 实现与基本
①基本元素
1、有一个父类
2、由子类实现父类的方法
3、父类类型的变量(指针) 指向 引用子类的具体变量
通俗的讲 我调用你父类 父类却是让子类具体实现方法去做事
package main import "fmt" //本质是一个指针 type AnimalIF interface { Sleep() GetColor() string //获取动物的颜色 GetType() string //获取动物的种类 } //具体的类 type Cat struct { //继承具体的类的话 需要在这边写入类名 //继承接口不需要写入 只需要实现继承的类的方法 color string //猫的颜色 } func (this *Cat) Sleep() { fmt.Println("Cat is Sleep") } func (this *Cat) GetColor() string { return this.color } func (this *Cat) GetType() string { return "Cat" } //具体的类 type Dog struct { color string } func (this *Dog) Sleep() { fmt.Println("Dog is Sleep") } func (this *Dog) GetColor() string { return this.color } func (this *Dog) GetType() string { return "Dog" } func showAnimal(animal AnimalIF) { animal.Sleep() //多态 fmt.Println("color = ", animal.GetColor()) fmt.Println("kind = ", animal.GetType()) } func main() { var animal AnimalIF //接口的数据类型, 父类指针 animal = &Cat{"Green"} animal.Sleep() //调用的就是Cat的Sleep()方法 , 多态的现象 animal = &Dog{"Yellow"} animal.Sleep() // 调用Dog的Sleep方法,多态的现象 cat := Cat{"Green"} dog := Dog{"Yellow"} showAnimal(&cat) showAnimal(&dog) }
结果
② 通用万能类型,同一个服务用到的相同包一定要调同一个地方的,否则会报错
1、通用万能类型,空接口,interface{ }可以引用任何的基本数据类型,int,float32,string,struct,float64
2、通用万能类型 类型断言,选择 参数是 interface{ }
Golang的语言中提供了断言的功能。
golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,
这种做法和java中的Object类型比较类似。
那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。
func funcName(a interface{}) string {
return string(a)
}
结果
cannot convert a (type interface{}) to type string: need type assertion
此时,意味着整个转化的过程需要类型断言。类型断言有以下几种形式:
1)直接断言使用
var a interface{}
fmt.Println("Where are you,Jonny?", a.(string))
但是如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。示例:
value, ok := a.(string) if !ok { fmt.Println("It's not ok for type string") return } fmt.Println("The value is ", value)
完整例子
package main
import "fmt"
/*
func funcName(a interface{}) string {
return string(a)
}
*/
func funcName(a interface{}) string {
value, ok := a.(string)
if !ok {
fmt.Println("It is not ok for type string")
return ""
}
fmt.Println("The value is ", value)
return value
}
func main() {
// str := "123"
// funcName(str)
//var a interface{}
//var a string = "123"
var a int = 10
funcName(a)
}
2)配合switch使用
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
或者如下使用方法
func sqlQuote(x interface{}) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
例子1:做是否为0值判断
in := 0. var tmp interface{} = float32(in) fmt.Println("float 0==0:", in == 0)
fmt.Println("float -> interface{} -> float", tmp.(float32) == 0)
switch v := tmp.(type) { case float32: fmt.Println("float -> interface -.type-> float", v == 0) }
结果
float 0==0: true float -> interface{} -> float true float -> interface -.type-> float true
例子2:判断数据类型是否为string
package main import "fmt" //interface{}是万能数据类型 func myFunc(arg interface{}) { fmt.Println("myFunc is called...") fmt.Println(arg) //interface{} 如何区分 此时引用的底层数据类型到底是什么? //给 interface{} 提供 “类型断言” 的机制 value, ok := arg.(string) if !ok { fmt.Println("arg is not string type") } else { fmt.Println("arg is string type, value = ", value) fmt.Printf("value type is %T\n", value) } } type Booking struct { auth string } func main() { book := Booking{"Golang"} myFunc(book) myFunc(100) myFunc("abc") myFunc(3.14) }