Go语言基础
介绍
基础
介绍
- 本文介绍Go语言中方法定义、方法调用、String方法、结构体嵌套方法调用、方法初始化变量、接口定义、接口声明、接口变量赋值、接口的嵌套、类型断言、匿名接口与空接口等相关知识。
基础
方法
- 方法是对类型定义的,且只能由该类型调用的函数。
- 通过 type 自定义类型(结构体或任何自定义的类型),可对此类型定义方法。* 方法具有特定接收者(附加的类型),接收者可以是指针或值类型。
- 函数不属于任何类型,方法指定特定的接收者类型。
// 方法的一般格式
func (t *T或T) MethodName(参数列表) (返回值列表) {
// 方法体
}
方法定义
- 方法必须是自定义类型,Go语言内置类型不支持用户对此类型拓展方法,使用 type 对内置类型自定义别名,自定义后的别名可以定义方法。
- 方法声明要与接收者参数的基类型声明放在同一个包内。
type Cpu struct {
Name string
Id int
}
// 接收者为值类型
func (c Cpu) GetName() string {
return c.Name
}
func (c Cpu) GetId() int {
return c.Id
}
// 接收者为指针类型
func (c *Cpu) PGetName() string {
return c.Name
}
func (c *Cpu) PGetId() int {
return c.Id
}
方法调用
- 定义类型对象,调用类型方法时使用其对象调用。
- 指针类型对象,调用接收者为值类型方法时,Go 编译器会自动将指针对象解引用为值调用方法。
- 值类型对象,调用接收者为指针类型方法时,Go 编译器会自动将值对象取引用为指针调用方法。
- 实际使用时若存在指针接收者,则所有方法使用指针接收者,否则都是用值接收者。
- 接收者为指针类型的方法,运行时必须确保接收者不为 nil。
package main
import "fmt"
type Cpu struct {
Name string
Id int
}
func (c Cpu) GetName() string {
return c.Name
}
func (c Cpu) GetId() int {
return c.Id
}
func (c *Cpu) PGetName() string {
return c.Name
}
func (c *Cpu) PGetId() int {
return c.Id
}
func main() {
// 定义结构体对象
cpu := Cpu{
"Intel",
10001,
}
fmt.Print("Name = ", (&cpu).PGetName()) // 常用写法 cpu.PPGetName()
fmt.Println(", Id = ", cpu.GetId())
// 定义结构体对象指针
cpu2 := &Cpu{
"AMD",
10002,
}
// 两种方式均可调用
fmt.Print("Name = ", (*cpu2).GetName())
fmt.Println(", Id = ", cpu2.PGetId())
}
输出结果
Name = Intel, Id = 10001
Name = AMD, Id = 10002
String方法
- 对于自定义结构体类型,通过实现 String 方法,就能调用 fmt.Println 函数输出结构体信息。
package main
import "fmt"
type Cpu struct {
Name string
Id int
}
func main() {
// 定义结构体对象
cpu := Cpu{
"Intel",
10001,
}
fmt.Print(&cpu)
}
func (c *Cpu) String() string {
ret := fmt.Sprintf("Name = %v, Age = %v", c.Name, c.Id)
return ret
}
输出结果
Name = Intel, Age = 10001
结构体嵌套方法调用
- 结构体嵌套有两种方式,匿名嵌套与命名嵌套。
- 命名嵌套时,通过嵌套结构体的对象可以调用嵌套结构体的方法。
- 匿名嵌套时,通过被嵌套结构体的对象可直接调用嵌套结构体的方法,若被嵌套结构体和嵌套结构体之间有同名方法,则无法调用到匿名嵌套结构体中的函数,此时就需要指定命名嵌套方式。
package main
import "fmt"
type Base struct {
name string
}
func (b Base) GetName() string {
return b.name
}
type Intel struct {
base Base
id int
}
func (i Intel) GetName() string {
ret := fmt.Sprintf("name = %v, id = %v", i.base.GetName(), i.id)
return ret
}
type Amd struct {
Base
id int
}
// 定义此方法,编译通过,运行时报错
// func (a Amd) GetName() string {
// ret := fmt.Sprintf("name = %v, id = %v", a.GetName(), a.id)
// return ret
// }
func main() {
// 定义结构体对象
a := Intel{
Base{"Intel"},
10001,
}
fmt.Println(a)
fmt.Println("a.GetName: ", a.GetName())
fmt.Println("a.base.GetName: ", a.base.GetName())
b := Amd{
Base{"AMD"},
10002,
}
fmt.Println(b)
fmt.Println("b.GetName: ", b.GetName())
}
输出结果
{{Intel} 10001}
a.GetName: name = Intel, id = 10001
a.base.GetName: Intel
{{AMD} 10002}
b.GetName: AMD
方法初始化变量
- 将方法可以作为类型初始化变量,然后使用此变量调用方法,可以存储,也可以作为形参传递或作为函数返回值。
- 方法表达式赋值时,若方法接收者为值类型,赋值时拷贝值类型,若方法接收者为指针类型,会自动解引用拷贝。
package main
import "fmt"
type Base struct {
name string
}
func (b Base) GetName() string {
return b.name
}
func (b *Base) SetName(nm string) {
b.name = nm
}
func Test(f func() string) {
fmt.Println(f())
}
func Test2(str string, f func(string)) {
f(str)
}
func main() {
// 定义结构体对象
a := Base{
"Intel",
}
// 定义结构体对象,调用接收者为值的方法
aa := a.GetName
fmt.Printf("aa type: %T, value: %v\n", aa, aa())
// 定义结构体对象,调用接收者为指针的方法
ab := a.SetName
fmt.Printf("ab type: %T", ab)
ab("AMD")
fmt.Printf(", value: %v\n", a.GetName())
// 方法作为形参值传递
Test(a.GetName)
Test2("Intel+", a.SetName)
Test(a.GetName)
}
输出结果
aa type: func() string, value: Intel
ab type: func(string), value: AMD
AMD
Intel+
接口
- Go语言使用组合实现面向对象的概念。接口是自定义类型,是对其他类型行为的抽象。将不同的类型绑定到一组公共的方法上,实现多态和灵活的设计。
- Go语言的接口设计是非侵入式的,接口定义者不需要关注什么类型实现此接口,接口实现者只需要实现对应接口,通过编译器编译时自动获取到某类型使用某接口。
- Go 语言中的接口是隐式实现的,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。可以通过接口实现多态。
- 可以存在空接口,Go语言中, 类型interface{}(any)每个类型都默认会实现,所以此类型可转换为任意类型。
type 接口类型名 interface {
方法名1(参数列表1) 返回值列表1
方法名2(参数列表2) 返回值列表2
...
}
接口定义
- 使用 interface 关键字定义,声明了一系列的函数签名(函数名、函数参数、函数返回值)。
type GameRole interface {
Attack(att int) error
Cure(cur int) error
Visit(msg string) error
}
接口声明
- 将变量名类型定义为接口名称,变量默认初始化为 nil。
package main
import "fmt"
type GameRole interface {
Attack(att int) error
Cure(cur int) error
Visit(msg string) error
}
func main() {
var a GameRole
fmt.Printf("a type: %T, value: %v\n", a, a)
}
输出结果
a type: , value:
接口变量赋值
-
接口变量赋值前提是需要实现接口,且接口的方法与实现接口的类型方法格式一样,而且接口中声明的所有函数均需要被实现。
-
当自定义类型实现了接口类型中声明的所有函数时,该类型的对象可以赋值给接口变量。
-
方法接收者都为值类型。
package main
import "fmt"
// 定义接口
type GameRole interface {
Attack(att int) error
Cure(cur int) error
Visit(msg string) error
}
// 定义结构体类型
type Warcraft struct {
name string
lifeBar int
}
// 实现所有接口方法
func (c Warcraft) Attack(att int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Attack: ", att)
return nil
}
func (c Warcraft) Cure(cur int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Cure: ", cur)
return nil
}
func (c Warcraft) Visit(msg string) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Visit: ", msg)
return nil
}
func main() {
var a GameRole
fmt.Printf("a type: %T, value: %v\n", a, a)
// 接收者都为值类型
var b GameRole = Warcraft{"Warcraft-2", 200}
fmt.Printf("b type: %T, value: %v\n", b, b)
// 方法调用
b.Attack(10)
b.Cure(5)
b.Visit("World")
}
a type: , value:
b type: main.Warcraft, value: {Warcraft-2 200}
Warcraft-2 , 200 , Attack: 10
Warcraft-2 , 200 , Cure: 5
Warcraft-2 , 200 , Visit: World
- 方法接收者都为指针类型。
package main
import "fmt"
type GameRole interface {
Attack(att int) error
Cure(cur int) error
Visit(msg string) error
}
type Warcraft struct {
name string
lifeBar int
}
func (c *Warcraft) Attack(att int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Attack: ", att)
return nil
}
func (c *Warcraft) Cure(cur int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Cure: ", cur)
return nil
}
func (c *Warcraft) Visit(msg string) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Visit: ", msg)
return nil
}
func main() {
var a GameRole
fmt.Printf("a type: %T, value: %v\n", a, a)
var b GameRole = &Warcraft{"Warcraft-2", 200}
fmt.Printf("b type: %T, value: %v\n", b, b)
b.Attack(10)
b.Cure(5)
b.Visit("World")
}
输出结果
a type: , value:
b type: *main.Warcraft, value: &{Warcraft-2 200}
Warcraft-2 , 200 , Attack: 10
Warcraft-2 , 200 , Cure: 5
Warcraft-2 , 200 , Visit: World
- 方法接收者即有值又有指针类型,接口变量赋值需要使用指针赋值方式。
- 一个类型可用同时实现多个接口,接口间彼此独立。
- 只要某个类型实现一个或多个接口声明的所有函数,就可以赋值给各自的接口类型。
package main
import "fmt"
// 定义接口类型
type GameRole interface {
Attack(att int) error
Cure(cur int) error
Visit(msg string) error
}
// 定义接口类型
type GameExit interface {
ExitGame() error
}
// 定义结构体
type Warcraft struct {
name string
lifeBar int
}
// 实现接口
func (c Warcraft) Attack(att int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Attack: ", att)
return nil
}
func (c Warcraft) Cure(cur int) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Cure: ", cur)
return nil
}
func (c *Warcraft) Visit(msg string) error {
fmt.Println(c.name, ", ", c.lifeBar, ", Visit: ", msg)
return nil
}
func (c Warcraft) ExitGame() error {
fmt.Println(c.name, ", ", c.lifeBar, ", ExitGame")
return nil
}
func main() {
var a GameRole
fmt.Printf("a type: %T, value: %v\n", a, a)
var b GameRole = &Warcraft{"Warcraft-2", 200}
fmt.Printf("b type: %T, value: %v\n", b, b)
b.Attack(10)
b.Cure(5)
b.Visit("World")
var c GameExit = Warcraft{"Warcraft-3", 200}
c.ExitGame()
}
输出结果
a type: , value:
b type: *main.Warcraft, value: &{Warcraft-2 200}
Warcraft-2 , 200 , Attack: 10
Warcraft-2 , 200 , Cure: 5
Warcraft-2 , 200 , Visit: World
Warcraft-3 , 200 , ExitGame
- 当接口之间存在子集关系(一个接口(A)包含另一个接口(B)中的所有声明函数)时,A接口变量可以赋值给B接口变量。若两个接口声明同样的函数签名,两个接口完全等价。
- 接口变量声明为什么类型就调用此类型的方法。
package main
import "fmt"
// 定义接口类型
type ExitALL interface {
GameExit() error
ViewExit() error
}
// 定义接口类型
type GameExit interface {
GameExit() error
}
// 定义接口类型
type CarGameExit interface {
GameExit() error
}
// 定义结构体
type AppExit struct {
name string
}
// 实现接口
func (c AppExit) GameExit() error {
fmt.Println(c.name, " game exit now")
return nil
}
func (c AppExit) ViewExit() error {
fmt.Println(c.name, " view exit now")
return nil
}
func main() {
var a ExitALL = AppExit{"TV"}
fmt.Printf("a type: %T, value: %v\n", a, a)
var b GameExit = AppExit{"SV"}
fmt.Printf("b type: %T, value: %v\n", b, b)
// GameExit 接口为 ExitALL 接口的子集
var c GameExit = a
fmt.Printf("c type: %T, value: %v\n", c, c)
// GameExit 和 CarGameExit 具有相同的接口,两者完全等价
var d CarGameExit = b
fmt.Printf("d type: %T, value: %v\n", d, d)
a.GameExit()
b.GameExit()
c.GameExit()
d.GameExit()
}
输出结果
a type: main.AppExit, value: {TV}
b type: main.AppExit, value: {SV}
c type: main.AppExit, value: {TV}
d type: main.AppExit, value: {SV}
TV game exit now
SV game exit now
TV game exit now
SV game exit now
接口的嵌套
- 接口间可以嵌套组合,将多个接口放到一个接口中。
package main
import "fmt"
// 定义接口类型
type FileRead interface {
Read(read *[]byte) (int, error)
}
type FileWrite interface {
Write(msg string) (int, error)
}
type FileClose interface {
Close() error
}
type FileOpen interface {
Open() error
}
// 定义接口嵌套
type FileOpt interface {
FileRead
FileWrite
FileOpen
FileClose
}
// 定义结构体
type File struct {
path string
handle *int
}
// 实现接口
func (c *File) Read(read *[]byte) (int, error) {
if c.handle == nil || len(*read) < 1 {
return -1, fmt.Errorf("handle == nil")
}
var lens int = 0
arr := [...]byte{'1', '2', '3', '4'}
if len(arr) > len(*read) {
lens = len(*read)
} else {
lens = len(arr)
}
fmt.Println("file handle: ", c.handle, " read byte size: ", lens)
*read = arr[:lens]
return lens, nil
}
func (c *File) Write(msg string) (int, error) {
if c.handle == nil {
return -1, fmt.Errorf("handle == nil")
}
fmt.Println("file handle: ", c.handle, " wirte msg: ", msg)
return len(msg), nil
}
func (c *File) Open() error {
c.handle = new(int)
*c.handle = 1
fmt.Println("Open ", c.path, " succeed")
return nil
}
func (c *File) Close() error {
if c.handle == nil {
return nil
}
c.handle = nil
fmt.Println("Close ", c.path, " succeed")
return nil
}
func main() {
// 声明接口变量
var a FileOpt = &File{"./AppFile.txt", nil}
fmt.Printf("a type: %T, value: %v\n", a, a)
// 调用Open方法
if err := a.Open(); err != nil {
fmt.Println("Open file failed")
return
}
// 延迟调用Close方法
defer a.Close()
var lens int = 0
// 调用Write方法
lens, _ = a.Write("WriteData")
fmt.Println("write data len: ", lens)
b := []byte{10, 20, 30, 40}
// 调用Read方法
lens, _ = a.Read(&b)
fmt.Printf("b type: %T, value: %v, read data len: %v\n", b, b, lens)
}
输出结果
a type: *main.File, value: &{./AppFile.txt }
Open ./AppFile.txt succeed
file handle: 0xc00000a0d0 wirte msg: WriteData
write data len: 9
file handle: 0xc00000a0d0 read byte size: 4
b type: []uint8, value: [49 50 51 52], read data len: 4
Close ./AppFile.txt succeed
类型断言
- 接口变量赋值时使用类型对象或将父集接口赋值给子集变量后,如果需要将接口变量重新还原为原来的类型,使用类型断言。语法:接口变量.(Type)。
package main
import "fmt"
// 定义接口类型
type ExitALL interface {
GameExit() error
ViewExit() error
}
// 定义接口类型
type GameExit interface {
GameExit() error
}
// 定义接口类型
type CarGameExit interface {
GameExit() error
}
// 定义结构体
type AppExit struct {
name string
id int
}
// 实现接口
func (c AppExit) GameExit() error {
fmt.Println(c.name, " game exit now")
return nil
}
func (c AppExit) ViewExit() error {
fmt.Println(c.name, " view exit now")
return nil
}
func main() {
var a ExitALL = AppExit{"TV", 10}
fmt.Printf("a type: %T, value: %v\n", a, a)
var b GameExit = AppExit{"SV", 10}
fmt.Printf("b type: %T, value: %v\n", b, b)
// GameExit 接口为 ExitALL 接口的子集
var c GameExit = a
fmt.Printf("c type: %T, value: %v\n", c, c)
m1, ok1 := a.(AppExit)
fmt.Println("m1: ", m1, ", ok1: ", ok1)
m2, ok2 := b.(*AppExit)
fmt.Println("m2: ", m2, ", ok2: ", ok2)
m3, ok3 := c.(ExitALL)
fmt.Println("m3: ", m3, ", ok3: ", ok3)
}
输出结果
a type: main.AppExit, value: {TV 10}
b type: main.AppExit, value: {SV 10}
c type: main.AppExit, value: {TV 10}
m1: {TV 10} , ok1: true
m2: , ok2: false
m3: {TV 10} , ok3: true
- 通过 switch-case 语句查询变量类型,执行对应的操作。语法:接口变量.(type),case分支中不允许写 fallthrough 关键字。
package main
import (
"fmt"
"reflect"
)
// 定义接口类型
type ExitALL interface {
GameExit() error
ViewExit() error
}
// 定义接口类型
type GameExit interface {
GameExit() error
}
// 定义接口类型
type CarGameExit interface {
GameExit() error
}
// 定义结构体
type AppExit struct {
name string
id int
}
// 实现接口
func (c AppExit) GameExit() error {
fmt.Println(c.name, " game exit now")
return nil
}
func (c AppExit) ViewExit() error {
fmt.Println(c.name, " view exit now")
return nil
}
func main() {
var a ExitALL = AppExit{"TV", 10}
fmt.Printf("a type: %T, value: %v\n", a, a)
var b GameExit = AppExit{"SV", 10}
fmt.Printf("b type: %T, value: %v\n", b, b)
// GameExit 接口为 ExitALL 接口的子集
var c GameExit = a
fmt.Printf("c type: %T, value: %v\n", c, c)
var e GameExit
var d GameExit = e
fmt.Printf("d type: %T, value: %v\n", d, d)
// 声明切片
var s []GameExit = []GameExit{a, b, c, d, e}
fmt.Println(s)
for i, v := range s {
switch v.(type) {
case AppExit:
fmt.Printf("index %v, AppExit value: %v\n", i, v)
case *AppExit:
fmt.Printf("index %v, *AppExit value: %v\n", i, v)
case ExitALL:
fmt.Printf("index %v, ExitALL value: %v\n", i, v)
case GameExit:
fmt.Printf("index %v, GameExit value: %v\n", i, v)
default:
fmt.Println("index ", i, " is other type: ", reflect.TypeOf(v))
}
}
}
输出结果
index 0, AppExit value: {TV 10}
index 1, AppExit value: {SV 10}
index 2, AppExit value: {TV 10}
index 3 is other type:
index 4 is other type:
匿名接口与空接口
- 将接口变量直接声明为接口的函数签名,称为匿名接口。
- 匿名接口的使用场景一般是初始化一次接口变量。将实现此接口的类型变量赋值给此匿名接口的变量。
package main
import "fmt"
type Exit struct {
}
func (c *Exit) Close() error {
fmt.Println("Exit Close")
return nil
}
func main() {
// 声明匿名接口变量
var a interface {
Close() error
} = &Exit{}
// 调用类型方法
a.Close()
}
输出结果
Exit Close
- Go语言中,定义 interface{} 称之为空接口,不包含任何函数签名,且空接口声明的变量可以赋值为任何类型的变量。声明函数参数类型为 interface{},用于接收任意类型的变量。在 Go1.18以后,添加了 空接口别名 any。
package main
import "fmt"
func main() {
var a interface{} = "interface"
var b interface{} = 10
var c interface{} = 3.14
var d any = [...]int{10, 20, 30}
fmt.Println("a = ", a, ", b = ", b, ", c = ", c, ", d = ", d)
}
输出结果
a = interface , b = 10 , c = 3.14 , d = [10 20 30]