Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
4684 字
23 分钟
面向对象

接口的注意事项#

【1】接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。

//直接用接口创建实例,出错:
//var s SayHello
//s.sayHello()
var s SayHello = c
s.sayHello()
}

【2】只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

//自定义数据类型:
type intager int
func(i intager) sayHello(){
fmt.Println("say hi", i)
}
func main() {
var i intager = 10
var s SayHello = i //接口类型的变量可以接收任何实现了该接口的类型

【3】一个自定义类型可以实现多个接口

package main
import (
"fmt"
)
type Ainterface interface {
a()
}
type Binterface interface {
b()
}
type Stu struct {
}
func (s Stu) a(){
fmt.Println("aaaa")
}
func (s Stu) b(){
fmt.Println("bbbb")
}
func main() {
var s Stu
var a Ainterface = s
var b Binterface = s
a.a()
b.b()
}

【4】一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。

package main
import (
"fmt"
)
type Cinterface interface {
c()
}
type Binterface interface {
b()
}
type Ainterface interface {
Binterface
Cinterface
a()
}
type Stu struct {
}
func (s Stu) a(){
fmt.Println("a")
}
func (s Stu) b(){
fmt.Println("b")
}
func (s Stu) c(){
fmt.Println("c")
}
func main() {
var s Stu
var a Ainterface = s
a.a()
a.b()
a.c()
}

【5】interface类型默认是一个指针(引l用类型),如果没有对interface初始化就使用,那么会输出nil

【6】

type E interface {
}
func main() {
var s Stu
var a Ainterface = s
a.a()
a.b()
a.c()
var e E = s
fmt.Println(e)
var e2 interface{} = s
fmt.Println(e2)
var num float64 = 9.3
var e3 interface{} = num
fmt.Println(e3)
}

空接口(interface{})的作用是可以存放任意类型的值,因为所有类型都实现了空接口。常见用途有:

  1. 通用容器:比如 [fmt.Println](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)、map[string]interface{}、[]interface{},可以存储任意类型。
  2. 函数参数:当函数参数类型为 interface{} 时,可以接收任何类型的实参,实现通用处理。
  3. 类型断言与反射:通过类型断言或反射,可以在运行时判断和操作实际类型。

总结: 空接口让 Go 代码具备一定的“泛型”能力,适合需要存储或处理多种类型数据的场景

方法的注意事项#

如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

以后定义结构体的话,常定义String()作为输出结构体信息的方法,在fmt.Println会自动调用

package main
import "fmt"
type Student struct {
Name string
Age int
}
func (s *Student) String() string {
str := fmt.Sprintf("Name: %v, Age: %v", s.Name, s.Age)
return str
}
func main() {
stu := Student{
Name: "丽丽",
Age : 20,
}
fmt.Println(&stu)
}

方法和函数的区别#

【1】绑定指定类型:

方法:需要绑定指定数据类型

函数:不需要绑定数据类型

【2】调用方法不一样:

函数的调用方式:

​ 函数名(实参列表)

方法的调用方式:

​ 变量.方法名(实参列表)

package main
import "fmt"
type Student struct {
Name string
}
//定义方法
func (s Student) test01() {
fmt.Println(s.Name)
}
//定义函数
func method01(s Student) {
fmt.Println(s.Name)
}
func main() {
//调用函数:
var s Student = Student{"丽丽"}
method01(s)
//方法调用:
s.test01()
}

【3】对于函数来说,参数类型对应是什么就要传入什么。

package main
import "fmt"
type Student struct {
Name string
}
//定义函数:
func method01(s Student) {
fmt.Println(s.Name)
}
func method02(s *Student) {
fmt.Println((*s).Name)
}
func main() {
var s Student = Student{"丽丽"}
method01(s)
//method01(&s) // 不能传递指针类型的参数
method02(&s) // 可以传递指针类型的参数
//method02(s) // 不能传递值类型的参数
}

【4】对于方法来说,接收者为值类型,可以传入指针类型,接受者为指针类型,可以传入值类型。

package main
import "fmt"
type Student struct {
Name string
}
//定义方法
func (s Student) test01() {
fmt.Println(s.Name)
}
func (s *Student) test02() {
fmt.Println((*s).Name)
}
func main() {
var s Student = Student{"丽丽"}
s.test01()
(&s).test01() //虽然用指针调用方法,但方法接收者是值类型,所以还是值类型的接收者
(&s).test02()
s.test02() //虽然用值类型调用方法,但方法接收者是指针类型,所以还是指针类型的接收者
}

创建结构体实例时指定字段值#

【1】方式1:按照顺序赋值操作

缺点:必须按照顺序有局限性

【2】方式2:按照指定类型

【3】方式3:想要返回结构体的指针类型

package main
import "fmt"
type Student struct {
Name string
Age int
}
func main() {
//方式1:按照顺序赋值操作
var s1 Student = Student{"小李", 19}
fmt.Println(s1)
//方式2:按照指定类型赋值操作
var s2 Student = Student{
Name: "丽丽",
Age: 20,
}
fmt.Println(s2)
//方式3:想要返回结构体的指针类型
var s3 *Student = &Student{"明明", 26}
fmt.Println(*s3)
var s4 *Student = &Student{
Name: "娜娜",
Age: 29,
}
fmt.Println(*s4)
}

跨包创建结构体实例#

【1】创建不同的包:

image

你遇到的错误:

main.go:4:2: package demo11/model is not in std (C:\Program Files\Go\src\demo11\model)

这是因为 Go 的包导入路径是从 GOPATH 或 Go module 的根目录开始的。你现在的目录结构是:

demo11/

main/

​ main.go

model/

​ student.go

而你在 [main.go](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 里这样导入:

import (

​ “demo11/model”

)

但你没有使用 Go Modules(没有 go.mod 文件),Go 默认会去 $GOPATH/src 下找 [model](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html),但你的代码实际在本地文件夹下。

解决方法:

方法一:使用 Go Modules(推荐)

  1. 在 [demo11](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 目录下初始化 Go Modules: cd d:\workstation\code\gocode\demo11

    go mod init demo11

  2. 然后在 [demo11](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 目录下运行: go run ./main/main.go

这样 import “demo11/model” 就能找到 [student.go](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 了。


方法二:不用 Go Modules

把 [main.go](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 和 [student.go](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 放在同一个包下(比如都放在 [demo11](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 目录下),或者把 model 目录放到 $GOPATH/src/demo11/model 下。


推荐用方法一,现代 Go 项目都用 Go Modules。


你的 [main.go](vscode-file://vscode-app/c:/Users/xuee/AppData/Local/Programs/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 文件参考:main/main.go 你的 model 文件参考:model/student.go

【2】student.go:

package model
type Student struct {
Name string
Age int
}

首字母S大写,可以在其他包下访问

【3】main.go:

package main
import (
"fmt"
"demo11/model" // 引入跨包的Student结构体
)
func main() {
//跨包创建结构体Student的实例
//var s model.Student = model.Student{"丽丽", 10}
s := model.Student{"丽丽",10,}
fmt.Println(s)
}

发现:如果结构体首字母大写的话,在其他包下可以访问

但是:如果结构体的首字母小写?

image

解决:结构体首字母小写,挎包访问没问题—>工厂模式

package model
type student struct {
Name string
Age int
}
//工厂模式
func NewStudent(n string,a int) *student {
return &student{n,a}
}
package main
import (
"fmt"
"demo11/model" // 引入跨包的Student结构体
)
func main() {
//跨包创建结构体Student的实例
//var s model.Student = model.Student{"丽丽", 10}
//s := model.student{"丽丽",10,}
//fmt.Println(s)
s := model.NewStudent("娜娜",20)
fmt.Println(*s)
}

封装#

main.go
package main
import (
"fmt"
"demo12/model" // 引入跨包的Person结构体
)
func main() {
//创建person结构体实例
p := model.NewPerson("小明")
p.SetAge(20)
fmt.Println(p.Name)// 调用Name字段,直接访问
fmt.Println(p.GetAge())// 调用GetAge方法获取age字段的值
fmt.Println(*p)// 调用p的值,输出整个结构体
}

继承的引入#

  1. 继承的引入:

    当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其他的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

    image

  2. 代码引入:

    package main
    import (
    "fmt"
    )
    //定义动物结构体
    type Animal struct {
    Age int
    weight float32
    }
    //给Animal结构体添加一个方法:喊叫
    func (a *Animal) Shout() {
    fmt.Println("我可以大喊大叫")
    }
    //给绑定方法:自我展示:
    func (a *Animal) showInfo() {
    fmt.Println("年龄:", a.Age, "体重:", a.weight)
    }
    //定义结构体:cat
    type Cat struct {
    // 继承Animal
    Animal
    }
    //给Cat绑定特有方法:
    func (c *Cat) scratch() {
    fmt.Println("我是猫,我可以挠人")
    }
    func main() {
    cat := &Cat{}
    cat.Animal.Age = 3
    cat.Animal.weight = 4.5
    cat.Animal.Shout()
    cat.Animal.showInfo()
    cat.scratch()
    }
  3. 继承的优点

    提高代码的复用性、扩展性

继承注意事项#

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。

    package main
    import (
    "fmt"
    )
    //定义动物结构体
    type Animal struct {
    Age int
    weight float32
    }
    //给Animal结构体添加一个方法:喊叫
    func (a *Animal) Shout() {
    fmt.Println("我可以大喊大叫")
    }
    //给绑定方法:自我展示:
    func (a *Animal) showInfo() {
    fmt.Println("年龄:", a.Age, "体重:", a.weight)
    }
    //定义结构体:cat
    type Cat struct {
    // 继承Animal
    Animal
    }
    //给Cat绑定特有方法:
    func (c *Cat) scratch() {
    fmt.Println("我是猫,我可以挠人")
    }
    func main() {
    cat := &Cat{}
    cat.Animal.Age = 3
    cat.Animal.weight = 4.5
    cat.Animal.Shout()
    cat.Animal.showInfo()
    cat.scratch()
    }
  2. 匿名结构体字段访问可以简化。

    func main() {
    cat := &Cat{}
    cat.Animal.Age = 3
    cat.Animal.weight = 4.5
    cat.Animal.Shout()
    cat.Animal.showInfo()
    cat.scratch()
    }

    等价于

    func main() {
    cat := &Cat{}
    cat.Age = 3
    cat.weight = 4.5
    cat.Shout()
    cat.showInfo()
    cat.scratch()
    }

    cat.Age --->cat对应的结构体中找是否有Age字段,如果有直接使用,如果没有就去找嵌入的结构体类型中的Age

  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

    package main
    import (
    "fmt"
    )
    //定义动物结构体
    type Animal struct {
    Age int
    weight float32
    }
    //给Animal结构体添加一个方法:喊叫
    func (a *Animal) Shout() {
    fmt.Println("我可以大喊大叫")
    }
    //给绑定方法:自我展示:
    func (a *Animal) showInfo() {
    fmt.Println("年龄:", a.Age, "体重:", a.weight)
    }
    //定义结构体:cat
    type Cat struct {
    // 继承Animal
    Animal
    Age int
    }
    func (c *Cat) showInfo() {
    fmt.Println("----年龄:", c.Age, "----体重:", c.weight)
    }
    //给Cat绑定特有方法:
    func (c *Cat) scratch() {
    fmt.Println("我是猫,我可以挠人")
    }
    func main() {
    // cat := &Cat{}
    // cat.Age = 3
    // cat.weight = 4.5
    // cat.Shout()
    // cat.showInfo()
    // cat.scratch()
    cat := &Cat{}
    cat.weight = 4.5
    cat.Age = 3// 这里的cat.Age会覆盖Animal中的Age,就近调用
    cat.showInfo()// 调用Cat的showInfo方法,就近调用
    cat.Animal.Age = 20
    cat.Animal.showInfo() // 调用Animal的showInfo方法
    }
  4. Golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。

    package main
    import (
    "fmt"
    )
    type A struct {
    a int
    b string
    }
    type B struct {
    c int
    d string
    }
    type C struct {
    A
    B
    }
    func main() {
    //构建C的实例
    c := C{A{10,"aaa"},B{20,"bbb"}}
    fmt.Println(c)
    }
  5. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。

    package main
    import (
    "fmt"
    )
    type A struct {
    a int
    b string
    }
    type B struct {
    c int
    d string
    a int
    }
    type C struct {
    A
    B
    }
    func main() {
    //构建C的实例
    c := C{A{10,"aaa"},B{20,"bbb",50}}
    fmt.Println(c.b)
    fmt.Println(c.A.a)
    }
  6. 结构体的匿名字段是基本数据类型。

type C struct {
A
B
int
}
func main() {
//构建C的实例
c := C{A{10,"aaa"},B{20,"bbb",50},888}
fmt.Println(c.b)
fmt.Println(c.A.a)
fmt.Println(c.int)
}
  1. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。

    func main() {
    //构建C的实例
    c := C{
    A{
    a:10,
    b:"aaa",
    },
    B{
    c:20,
    d:"bbb",
    a:50},
    888}
    fmt.Println(c.b)
    fmt.Println(c.A.a)
    fmt.Println(c.int)
    }
  2. 嵌入匿名结构体的指针也是可以的。

    func main() {
    //构建C的实例
    c := C{
    A{
    a:10,
    b:"aaa",
    },
    B{
    c:20,
    d:"bbb",
    a:50},
    888}
    fmt.Println(c.b)
    fmt.Println(c.A.a)
    fmt.Println(c.int)
    c1:= C1{&A{10,"aaa"},&B{20,"bbb",50},888}
    fmt.Println(*c1.A)
    fmt.Println(*c1.B)
    }
  3. 结构体的字段可以是结构体类型的。(组合模式)

    type D struct {
    a int//嵌入A组合模式
    b string
    c B //嵌入B组合模式
    }
    func main() {
    //构建C的实例
    d := D{10,"ooo",B{66,"bbb",99}}
    fmt.Println(d)
    fmt.Println(d.c.d)

接口的引入#

【1】代码入门:

package main
import (
"fmt"
)
//接口的定义:定义规则、定义规划,定义某种能力:
type SayHello interface {
sayHello()
}
//接口的实现:定义一个结构体:
//中国人:
type Chinese struct {
}
//实现接口的方法--->具体的实现:
func (peroson Chinese) sayHello() {
fmt.Println("你好")
}
//接口的实现:定义一个结构体:
//美国人:
type American struct {
}
//实现接口的方法--->具体的实现:
func (peroson American) sayHello() {
fmt.Println("hi")
}
//定义以一个函数,用来各国人打招呼,
//这个函数的参数是一个接口类型
func greet(s SayHello) {
s.sayHello()
}
func main() {
//定义一个中国人
c:= Chinese{} //定义一个中国人
//定义一个美国人
a:= American{} //定义一个美国人
greet(c) //调用函数,传入中国人
greet(a) //调用函数,传入美国人
}

【2】总结: (1)接口中可以定义一组方法,但不需要实现,不需要方法体。并且接口中不能包含任何变量。到某个自定义类型要使用的时候(实现 接口的时候),再根据具体情况把这些方法具体实现出来。

(2)实现接口要实现所有的方法才是实现。

(3)Golang中的接口,不需要显式的实现接口。Golang中没有implement关键字。 (Golang中实现接口是基于方法的,不是基于接口的) 例如: A接口a,b方法 B接口a,b方法 C结构体实现了ab方法,那么C实现了A接口,也可以说实现了B接口(只要实现全部方法即可,和实际接口耦合性很低,比Java松散 得多)

(4)接口目的是为了定义规范,具体由别人来实现即可。

多态#

【1】基本介绍 变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

【2】案例:

image 【3】接口体现多态特征 1)多态参数:s 叫多态参数

image 2)多态数组:

比如:定义SayHello数组,存放中国人结构体、美国人结构体:

func main() {
//定义一个SayHello类型的数组,里面存放American、Chinese结构体变量:
var arr [3]SayHello
arr[0] = American{name: "Tom"}
arr[1] = Chinese{name: "张三"}
arr[2] = Chinese{name: "李四"}
fmt.Println(arr)

断言#

【1】什么是断言? Go语言里面有一个语法,可以直接判断是否是该类型的变量:value,ok=element.(T),这里value就是变量的值,ok是一个bool类 型,element是interface变量,T是断言的类型。 【2】断言的案例引入:

package main
import (
"fmt"
)
//接口的定义:定义规则、定义规划,定义某种能力:
type SayHello interface {
sayHello()
}
//接口的实现:定义一个结构体:
//中国人:
type Chinese struct {
name string
}
//实现接口的方法--->具体的实现:
func (peroson Chinese) sayHello() {
fmt.Println("你好")
}
//中国人特有的方法:
func (peroson Chinese) niuYangGe() {
fmt.Println("东北文化")
}
//接口的实现:定义一个结构体:
//美国人:
type American struct {
name string
}
//实现接口的方法--->具体的实现:
func (peroson American) sayHello() {
fmt.Println("hi")
}
//定义以一个函数,用来各国人打招呼,
//这个函数的参数是一个接口类型
func greet(s SayHello) {
s.sayHello()
//如果是中国人,还可以调用中国人特有的方法
//断言
var ch Chinese = s.(Chinese)//看s是否可以转成Chinese类型并且赋给ch变量
ch.niuYangGe()
}
//自定义数据类型:
type intager int
func(i intager) sayHello(){
fmt.Println("say hi", i)
}
func main() {
//定义一个中国人
c:= Chinese{} //定义一个中国人
//定义一个美国人
//a:= American{} //定义一个美国人
greet(c) //调用函数,传入中国人
//greet(a) //调用函数,传入美国人
}

解决第二个返回值:

package main
import (
"fmt"
)
//接口的定义:定义规则、定义规划,定义某种能力:
type SayHello interface {
sayHello()
}
//接口的实现:定义一个结构体:
//中国人:
type Chinese struct {
name string
}
//实现接口的方法--->具体的实现:
func (peroson Chinese) sayHello() {
fmt.Println("你好")
}
//中国人特有的方法:
func (peroson Chinese) niuYangGe() {
fmt.Println("东北文化")
}
//接口的实现:定义一个结构体:
//美国人:
type American struct {
name string
}
//实现接口的方法--->具体的实现:
func (peroson American) sayHello() {
fmt.Println("hi")
}
//定义以一个函数,用来各国人打招呼,
//这个函数的参数是一个接口类型
func greet(s SayHello) {
s.sayHello()
//如果是中国人,还可以调用中国人特有的方法
//断言
ch,flag := s.(Chinese)//看s是否可以转成Chinese类型并且赋给ch变量
if flag {
ch.niuYangGe() //如果可以转成Chinese类型,就调用中国人特有的方法
}else{
fmt.Println("美国人不会扭秧歌")
}
}
//自定义数据类型:
type intager int
func(i intager) sayHello(){
fmt.Println("say hi", i)
}
func main() {
//定义一个中国人
c:= Chinese{} //定义一个中国人
//定义一个美国人
//a:= American{} //定义一个美国人
greet(c) //调用函数,传入中国人
//greet(a) //调用函数,传入美国人
}

更简略的语法:

if ch,flag := s.(Chinese);flag {
ch.niuYangGe() //如果可以转成Chinese类型,就调用中国人特有的方法
}else{
fmt.Println("美国人不会扭秧歌")
}

【3】TypeSwitch的基本用法 TypeSwitch是Go语言中一种特殊的switch语句,它比较的是类型而不是具体的值。它判断某个接口变量的类型,然后根据具体类型再做相应 处理。

switch v := s.(type) {
case Chinese:
v.niuYangGe()
case American:
v.disco()
}
// switch s.(type) {
// case Chinese:
// ch := s.(Chinese)
// ch.niuYangGe()
// case American:
// am := s.(American)
// am.disco()
// }
}
//自定义数据类型:
type intager int
func(i intager) sayHello(){
fmt.Println("say hi", i)
}
func main() {
//定义一个中国人
c:= Chinese{} //定义一个中国人
//定义一个美国人
//a:= American{} //定义一个美国人
greet(c) //调用函数,传入中国人
//greet(a) //调用函数,传入美国人
}

第一种写法其实就是类型分支(type switch)自带类型断言,你不用手动写断言,Go 会自动帮你把接口变量 s 转换成对应的具体类型,并赋值给 v。

switch v := s.(type) {
case Chinese:
// 这里的 v 已经是 Chinese 类型,无需再断言
v.niuYangGe()
case American:
v.disco()
}

第一种写法不需要你手动断言,case 里的 v 已经是具体类型了,可以直接用。 而第二种写法 case 里还要自己写断言。

面向对象
https://mikufun.dpdns.org/posts/面向对象/
作者
Roxy-DD
发布于
2025-12-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00