目录
1、方法
1.1 匿名字段
通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 "override"。
//也是面向对象的内容
go支持只提供类型而不写字段名的方式,也就是匿名字段,也称为嵌入字段
所有的内置类型和自定义类型都是可以作为匿名字段去使用
package main
import "fmt"
type User struct {
id int
name string
}
type Manager struct {
User //匿名字段
title string
}
func (self *User) ToString() string {
return fmt.Sprintf("User: %p, %v", self, self)
}
func (self *Manager) ToString() string {
return fmt.Sprintf("Manager: %p, %v", self, self)
}
func main() {
m := Manager{User{1, "Tom"}, "Administrator"}
fmt.Println(m.ToString())
fmt.Println(m.User.ToString())
}
//输出
//Manager: 0xc420074180, &{{1 Tom} Administrator}
//User: 0xc420074180, &{1 Tom}
1.2 方法集
• 类型 T 方法集包含全部 receiver T 方法。
• 类型 *T 方法集包含全部 receiver T + *T 方法。
• 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
• 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
• 不管嵌入 T 或 *T,*S 方法集总是包含 T + *T 方法。
package main
import (
"fmt"
)
type S struct {
T
}
type T struct {
int
}
func (t T) testT() {
fmt.Println("如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T 方法")
}
func (t *T) testP() {
fmt.Println("如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 *T 方法")
}
func main() {
s1 := S{T{1}}
s2 := &s1
fmt.Printf("s1 is : %v\n", s1)
s1.testT()
s1.testP()
fmt.Printf("s2 is : %v\n", s2)
s2.testT()
s2.testP()
}
s1 is : {{1}}
如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T 方法
如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 *T 方法
s2 is : &{{1}}
如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T 方法
如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 *T 方法
1.3 表达式
两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。
package main
import "fmt"
type User struct {
id int
name string
}
func (self *User) Test() {
fmt.Printf("%p, %v\n", self, self)
}
func main() {
u := User{1, "Tom"}
u.Test()
mValue := u.Test //绑定实例
mValue() // 隐式传递 receiver
mExpression := (*User).Test
mExpression(&u) // 显式传递 receiver
}
//0xc42000a060, &{1 Tom}
//0xc42000a060, &{1 Tom}
//0xc42000a060, &{1 Tom}
method value 会复制 receiver
package main
import "fmt"
type User struct {
id int
name string
}
func (self User) Test() {
fmt.Println(self)
}
func main() {
u := User{1, "Tom"}
mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。
u.id, u.name = 2, "Jack"
u.Test() //修改了u,输出 {2 Jack}
mValue() //但mvalue不受影响,输出{1 Tom}
}
package main
import "fmt"
type User struct {
id int
name string
}
func (self *User) TestPointer() {
fmt.Printf("TestPointer: %p, %v\n", self, self)
}
func (self User) TestValue() {
fmt.Printf("TestValue: %p, %v\n", &self, self)
}
func main() {
u := User{1, "Tom"}
fmt.Printf("User: %p, %v\n", &u, u)
mv := User.TestValue
mv(u)
mp := (*User).TestPointer
mp(&u)
mp2 := (*User).TestValue // *User 方法集包含 TestValue。签名变为 func TestValue(self *User)。实际依然是 receiver value copy。
mp2(&u)
}
User: 0xc42000a060, {1 Tom}
TestValue: 0xc42000a0a0, {1 Tom}
TestPointer: 0xc42000a060, &{1 Tom}
TestValue: 0xc42000a100, {1 Tom}
1.4 自定义error
1.4.1 抛异常和处理异常
(1) 系统抛
package main
import "fmt"
// 系统抛
func test01() {
a := [5]int{0, 1, 2, 3, 4}
a[1] = 123
fmt.Println(a)
//a[10] = 11
index := 10
a[index] = 10
fmt.Println(a)
}
func getCircleArea(radius float32) (area float32) {
if radius < 0 {
// 自己抛
panic("半径不能为负")
}
return 3.14 * radius * radius
}
func test02() {
getCircleArea(-5)
}
//
func test03() {
// 延时执行匿名函数
// 延时到何时?(1)程序正常结束 (2)发生异常时 //发生异常时
defer func() {
// recover() 复活 恢复
// 会返回程序为什么挂了
if err := recover(); err != nil {
fmt.Println(err)
}
}()
getCircleArea(-5)
fmt.Println("这里有没有执行") //没执行
}
func test04() {
test03()
fmt.Println("test04")
}
func main() {
test04()
}
半径不能为负
test04
(2) 返回异常
package main
import (
"errors"
"fmt"
)
func getCircleArea(radius float32) (area float32, err error) {
if radius < 0 {
// 构建个异常对象
err = errors.New("半径不能为负")
return
}
area = 3.14 * radius * radius
return
}
func main() {
area, err := getCircleArea(-5)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(area)
}
}
(3) 自定义error
package main
import (
"fmt"
"os"
"time"
)
type PathError struct {
path string
op string
createTime string
message string
}
func (p *PathError) Error() string {
return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s", p.path,
p.op, p.createTime, p.message)
}
func Open(filename string) error {
file, err := os.Open(filename)
if err != nil {
return &PathError{
path: filename,
op: "read",
message: err.Error(),
createTime: fmt.Sprintf("%v", time.Now()),
}
}
defer file.Close()
return nil
}
func main() {
err := Open("/Users/5lmh/Desktop/go/src/test.txt")
switch v := err.(type) {
case *PathError:
fmt.Println("get path error,", v)
default:
}
}
2、面向对象
2.1 匿名字段(同上)
2.2 接口
请牢记接口(interface)是一种类型。定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。不关心属性(数据),只关心行为(方法)。
Go语言提倡面向接口编程。
接口是一个或多个方法签名的集合。
任何类型的方法集中只要拥有该接口'对应的全部方法'签名。
就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。
这称为Structural Typing。
所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。
当然,该类型还可以有其他方法。
接口只有方法声明,没有实现,没有数据字段。
接口可以匿名嵌入其他接口,或嵌入到结构中。
对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
只有当接口存储的类型和对象都为nil时,接口才等于nil。
接口调用不会做receiver的自动转换。
接口同样支持匿名字段方法。
接口也可实现类似OOP中的多态。
空接口可以作为任何类型数据的容器。
一个类型可实现多个接口。
接口命名习惯以 er 结尾。
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
1.接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
2.方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
3.参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
例子:
type writer interface{
Write([]byte) error
}
接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dog和cat类型的变量。
package main
import (
"fmt"
)
// Sayer 接口
type Sayer interface {
say()
}
type dog struct{}
type cat struct{}
// dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
}
// cat实现了Sayer接口
func (c cat) say() {
fmt.Println("喵喵喵")
}
func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := cat{} // 实例化一个cat
b := dog{} // 实例化一个dog
x = a // 可以把cat实例直接赋值给x
x.say() // 喵喵喵
x = b // 可以把dog实例直接赋值给x
x.say() // 汪汪汪
}
// 喵喵喵
// 汪汪汪
值接收者和指针接收者实现接口
值接收者可以接受值和指针,因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui
指针接收者只能接受指针
类型与接口的关系:
一个类型实现多个接口:dog可以实现say和move
多个类型实现同一接口:dog和cat都可以move,(像多态)
并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
package main
import (
"fmt"
)
// WashingMachine 洗衣机
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type dryer struct{}
// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// 海尔洗衣机
type haier struct {
dryer //嵌入甩干器
}
// 实现WashingMachine接口的wash()方法
func (h haier) wash() {
fmt.Println("洗刷刷")
}
func main() {
var h haier
var w WashingMachine
w = h
w.wash()
w.dry()
}
接口嵌套
//接口与接口间可以通过嵌套创造出新的接口
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// 接口嵌套
type animal interface {
Sayer
Mover
}
空接口:
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
package main
import (
"fmt"
)
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
//type:string value:pprof.cn
//type:int value:100
//type:bool value:true
空接口的应用:做函数参数,作为map的值
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
/map[age:18 married:false name:李白]
类型断言
func main() {
var x interface{}
x = "pprof.cn"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}