接口的注意事项
【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 mainimport ( "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 mainimport ( "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{})的作用是可以存放任意类型的值,因为所有类型都实现了空接口。常见用途有:
- 通用容器:比如 [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{},可以存储任意类型。
- 函数参数:当函数参数类型为 interface{} 时,可以接收任何类型的实参,实现通用处理。
- 类型断言与反射:通过类型断言或反射,可以在运行时判断和操作实际类型。
总结: 空接口让 Go 代码具备一定的“泛型”能力,适合需要存储或处理多种类型数据的场景
方法的注意事项
如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
以后定义结构体的话,常定义String()作为输出结构体信息的方法,在fmt.Println会自动调用
package mainimport "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 mainimport "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 mainimport "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 mainimport "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】创建不同的包:

你遇到的错误:
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(推荐)
-
在 [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
-
然后在 [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 mainimport ( "fmt" "demo11/model" // 引入跨包的Student结构体)
func main() { //跨包创建结构体Student的实例 //var s model.Student = model.Student{"丽丽", 10} s := model.Student{"丽丽",10,} fmt.Println(s)}发现:如果结构体首字母大写的话,在其他包下可以访问
但是:如果结构体的首字母小写?

解决:结构体首字母小写,挎包访问没问题—>工厂模式
package model
type student struct { Name string Age int}
//工厂模式func NewStudent(n string,a int) *student { return &student{n,a}}package mainimport ( "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)}封装
package mainimport ( "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的值,输出整个结构体}继承的引入
-
继承的引入:
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其他的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

-
代码引入:
package mainimport ("fmt")//定义动物结构体type Animal struct {Age intweight float32}//给Animal结构体添加一个方法:喊叫func (a *Animal) Shout() {fmt.Println("我可以大喊大叫")}//给绑定方法:自我展示:func (a *Animal) showInfo() {fmt.Println("年龄:", a.Age, "体重:", a.weight)}//定义结构体:cattype Cat struct {// 继承AnimalAnimal}//给Cat绑定特有方法:func (c *Cat) scratch() {fmt.Println("我是猫,我可以挠人")}func main() {cat := &Cat{}cat.Animal.Age = 3cat.Animal.weight = 4.5cat.Animal.Shout()cat.Animal.showInfo()cat.scratch()} -
继承的优点
提高代码的复用性、扩展性
继承注意事项
-
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。
package mainimport ("fmt")//定义动物结构体type Animal struct {Age intweight float32}//给Animal结构体添加一个方法:喊叫func (a *Animal) Shout() {fmt.Println("我可以大喊大叫")}//给绑定方法:自我展示:func (a *Animal) showInfo() {fmt.Println("年龄:", a.Age, "体重:", a.weight)}//定义结构体:cattype Cat struct {// 继承AnimalAnimal}//给Cat绑定特有方法:func (c *Cat) scratch() {fmt.Println("我是猫,我可以挠人")}func main() {cat := &Cat{}cat.Animal.Age = 3cat.Animal.weight = 4.5cat.Animal.Shout()cat.Animal.showInfo()cat.scratch()} -
匿名结构体字段访问可以简化。
func main() {cat := &Cat{}cat.Animal.Age = 3cat.Animal.weight = 4.5cat.Animal.Shout()cat.Animal.showInfo()cat.scratch()}等价于
func main() {cat := &Cat{}cat.Age = 3cat.weight = 4.5cat.Shout()cat.showInfo()cat.scratch()}cat.Age --->cat对应的结构体中找是否有Age字段,如果有直接使用,如果没有就去找嵌入的结构体类型中的Age
-
当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
package mainimport ("fmt")//定义动物结构体type Animal struct {Age intweight float32}//给Animal结构体添加一个方法:喊叫func (a *Animal) Shout() {fmt.Println("我可以大喊大叫")}//给绑定方法:自我展示:func (a *Animal) showInfo() {fmt.Println("年龄:", a.Age, "体重:", a.weight)}//定义结构体:cattype Cat struct {// 继承AnimalAnimalAge 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.5cat.Age = 3// 这里的cat.Age会覆盖Animal中的Age,就近调用cat.showInfo()// 调用Cat的showInfo方法,就近调用cat.Animal.Age = 20cat.Animal.showInfo() // 调用Animal的showInfo方法} -
Golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。
package mainimport ("fmt")type A struct {a intb string}type B struct {c intd string}type C struct {AB}func main() {//构建C的实例c := C{A{10,"aaa"},B{20,"bbb"}}fmt.Println(c)} -
如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
package mainimport ("fmt")type A struct {a intb string}type B struct {c intd stringa int}type C struct {AB}func main() {//构建C的实例c := C{A{10,"aaa"},B{20,"bbb",50}}fmt.Println(c.b)fmt.Println(c.A.a)} -
结构体的匿名字段是基本数据类型。
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)}-
嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。
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)} -
嵌入匿名结构体的指针也是可以的。
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)} -
结构体的字段可以是结构体类型的。(组合模式)
type D struct {a int//嵌入A组合模式b stringc B //嵌入B组合模式}func main() {//构建C的实例d := D{10,"ooo",B{66,"bbb",99}}fmt.Println(d)fmt.Println(d.c.d)
接口的引入
【1】代码入门:
package mainimport ( "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】案例:
【3】接口体现多态特征
1)多态参数:s 叫多态参数
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 mainimport ( "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 mainimport ( "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 里还要自己写断言。
部分信息可能已经过时









