1. 简介
- go 没有面向对象语法等的要求
- go 语言对于面向对象的设计非常简洁而优雅
- 没有封装(让数据更加安全,例如年龄不能是负的)、继承(减少代码冗余,父类和子类)、多态(可以产出不同的实例)这些概念,但同样通过别的方式实现这些特性
- 封装:通过方法实现
- 继承:通过匿名字段实现
- 多态:通过接口实现
2. 匿名字段
go 结构体中支持只提供类型而不写字段名的方式,也就是匿名字段,也称为嵌入字段
package main
import "fmt"
// 定义一个存储人的结构体
type Person struct {
name string
sex int
age int
}
// 定义一个存储学生的结构体,因为学生是是具有人结构体中的字段特性
// 不需要重新声明,直接继承人这个结构体的字段就可以
// 不能存在多个相同的匿名字段
type Student struct {
Person // 继承人结构体, 匿名字段
id int
address string
}
func main() {
// 初始化
stu01 := Student{Person{"zs", 0, 18}, 1, "bj"}
fmt.Println(stu01)
stu02 := Student{id: 1, address: "bj"}
fmt.Println(stu02)
stu03 := Student{Person: Person{name:"ls", sex:0, age:1}}
fmt.Println(stu03)
// 取值
// 取Person的name (如果子结构体没有和父结构体同名字段,以下两种方式都可以)
fmt.Println(stu01.name)
fmt.Println(stu01.Person.name)
fmt.Println(stu01.id)
}
同名字段的情况
package main
import "fmt"
// 定义一个存储人的结构体
type Person struct {
name string
sex int
age int
}
// 定义一个存储学生的结构体,因为学生是是具有人结构体中的字段特性
// 不需要重新声明,直接继承人这个结构体的字段就可以
type Student struct {
Person // 继承人结构体, 匿名字段
id int
address string
// 同名字段
name string
}
func main() {
var stu Student
// 给自己的name字段赋值
stu.name = "zs"
// 给父的name字段进行赋值
stu.Person.name = "ls"
// 取自己的name字段的值(就近原则,不会取到父的name)
fmt.Println(stu.name)
// 取父的name字段的值
fmt.Println(stu.Person.name)
}
所有的内置类型和自定义类型都是可以作为匿名字段去使用
package main
import "fmt"
// 自定义一个类型
type myint int
type student struct {
int // 内置类型匿名字段
myint // 自定义类型匿名字段
}
func main() {
// 初始化
s := student{1,2}
// 取值
fmt.Println(s.int)
fmt.Println(s.myint)
}
指针类型匿名字段
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
type Student struct {
*Person
id int
addr string
}
func main() {
stu := Student{&Person{"zs", 18, 1}, 3, "bj"}
fmt.Println(stu)
fmt.Println(stu.name)
fmt.Println(stu.Person.name)
fmt.Println(stu.id)
}
3. 方法
- 在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数
- 这种带有接收者的函数,我们称为方法,本质上,一个方法则是一个和特殊类型关联的函数
- 方法的语法如下
func (接收参数名 接收类型) 方法名(参数列表)(返回值)
- 可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法
基础类型作为接收者
package main
import "fmt"
type myint int
// 面向过程思维写法
func add(a, b myint) myint {
return a + b
}
// 面向对象接口思维写法
func (a myint) add(b myint) myint {
return a + b
}
func main() {
var a myint = 1
var b myint = 2
// 面向过程
c := add(a,b)
fmt.Println(c)
// 面向思维
d := a.add(b)
fmt.Println(d)
}
结构体作为接收者
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
func (p Person) print() {
fmt.Println(p.name, p.age, p.sex)
}
func main() {
p := Person{"zs", 18, -0}
p.print()
}
值语义和引用语义
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
// 引用语义
func (p *Person) setValue001() {
p.name = "123"
}
// 值语义
func (p Person) setValue002() {
p.name = "abc"
}
func main() {
p := Person{"zs", 18, 0}
fmt.Println(p)
// 值语义, 不会改变原结构体
p.setValue002()
fmt.Println("值语义",p)
// 引用语义,会改变原结构体
p.setValue001()
fmt.Println("引用语义",p)
}
方法继承
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
type Student struct {
Person
id int
addr string
}
func (p *Person) printInfo() {
fmt.Println("person", p.name, p.age, p.sex)
}
func main() {
p := Person{"zs", 18, 1}
p.printInfo()
s := Student{Person{"ls", 22, 0}, 1, "bj"}
// 子调用父继承来的方法
s.printInfo()
}
方法重写
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
func (p *Person) printInfo() {
fmt.Println("person", p.name, p.age, p.sex)
}
type Student struct {
Person
id int
addr string
}
// 重写父的printInfo方法
func (s *Student) printInfo() {
fmt.Println("student", s.name, s.age, s.sex, s.id, s.addr)
}
func main() {
p := Person{"zs", 18, 1}
p.printInfo()
s := Student{Person{"ls", 22, 0}, 1, "bj"}
// 子调用重写父来的方法
s.printInfo()
}
方法值和方法表达式
package main
import "fmt"
type Person struct {
name string
age int
sex int
}
func (p *Person) printInfo() {
fmt.Println("person", p.name, p.age, p.sex)
}
func main() {
p := Person{"zs", 18, 1}
p.printInfo()
// 方法值
pFunc01 := p.printInfo
pFunc01()
// 方法表达式
pFunc02 := (*Person).printInfo
pFunc02(&p)
}
1. 包和封装
- 方法首写字母大写可以被外部包调用
- 方法首写字母小写是私有方法只能在当前包内调用
- 为结构体定义的方法必须放在同一个包内,可以是不同的文件
5. init 函数以及执行顺序
一个包里可以有 0 个或多个 init 函数,在程序启动时自动调用
package main
import "fmt"
var a int = 10
var b int = 20
func init() {
fmt.Println("a: ", a)
fmt.Println("init01")
}
func init() {
fmt.Println("b: ", b)
fmt.Println("init02")
}
func main() {
fmt.Println("main")
}
go 程序初始化顺序
main包 --> import --> 全局const --> 全局var --> init() --> main()
如果一个 main 包引入了别的包,初始化顺序是先初始化被引用的包
6. 接口
-
go 语言中,接口(
interface
)是一个自定义类型,描述了一系列方法的集合 -
接口不能被实例化
-
接口定义语法
type 接口名 interface{}
- ps: 接口命名习惯以
er
结尾
- ps: 接口命名习惯以
接口的定义与实现
package main
import "fmt"
// 定义一次接口
type Humaner interface {
// 定义接口的方法
Say()
}
// 定义学生信息结构体
type Student struct {
name string
age int
score float64
}
// Student结构体实现Humaner接口
func (stu *Student) Say() {
fmt.Println("学生...: ", stu.name)
}
type Teacher struct {
name string
age int
subject string
}
func (tea *Teacher) Say() {
fmt.Println("老师...: ", tea.name)
}
type myString string
func (mystr myString) Say() {
fmt.Println("myString...: ", mystr)
}
// 定义一个接收Humaner接口类型参数的统一函数
func WhoSay(s Humaner) {
s.Say()
}
func main() {
stu01 := &Student{"zs", 18, 99.8}
tea01 := &Teacher{"ls", 20, "计算机"}
var mystr myString = "hello"
// 各自调用自己的方法
stu01.Say()
tea01.Say()
mystr.Say()
// 使用统一接口调用
WhoSay(stu01)
WhoSay(tea01)
WhoSay(mystr)
// 已经实现接口的类型也可以是那个接口类型
x := make([]Humaner, 3)
x[0], x[1], x[2] = stu01, tea01, mystr
for _, v := range x {
v.Say()
}
}
接口继承
package main
import "fmt"
// 定义一次接口
type Humaner interface {
// 定义接口的方法
Say()
}
// 定义继承Humaner的接口
type Personer interface {
// 相当于写了Say()
Humaner
Sing(lyrics string)
}
// 定义学生信息结构体
type Student struct {
name string
age int
score float64
}
// Student结构体实现Humaner接口
func (stu *Student) Say() {
fmt.Println("学生...: ", stu.name)
}
func (stu *Student) Sing(lyrics string) {
fmt.Println(lyrics)
}
// 定义一个接收Humaner接口类型参数的统一函数
func WhoSay(s Humaner) {
s.Say()
}
func main() {
stu01 := &Student{"zs", 18, 99.8}
var p Personer
// 结构体对象赋值给接口对象
p = stu01
// 接口调用方法
p.Say()
p.Sing("hello world")
}
接口类型变量
可以存储任何实现了该接口所有方法的对象类型
package main
import "fmt"
type Animal interface {
Talk()
Eat()
Name() string
}
type Dog struct {
name string
}
func (d *Dog) Talk() {
fmt.Println("汪汪汪...")
}
func (d *Dog) Eat() {
fmt.Println("吃狗x")
}
func (d *Dog) Name() string {
fmt.Println(d.name)
return d.name
}
type Pig struct {
name string
}
func (p *Pig) Talk() {
fmt.Println("哼哼哼...")
}
func (p *Pig) Eat() {
fmt.Println("吃猪x")
}
func (p *Pig) Name() string {
fmt.Println(p.name)
return p.name
}
func main() {
// 创建两个实现了Animal 接口的对象
dog01 := &Dog{"旺财"}
pid01 := &Pig{"佩奇"}
// 定义接口类型变量
var a Animal
// 可以直接将实现了Animal 接口的对象赋值给Animal接口类型
a = dog01
a.Eat()
a.Name()
a.Talk()
a = pid01
a.Eat()
a.Name()
a.Talk()
}
接口类型和指针类型
值类型实现接口,指针类型可以存进去; 但指针类型实现接口,值类型存不进去
值类型实现接口,指针类型可以存进去
package main
import "fmt"
type Animal interface {
Talk()
Eat()
Name() string
}
type Dog struct {
name string
}
func (d Dog) Talk() {
fmt.Println("汪汪汪...")
}
func (d Dog) Eat() {
fmt.Println("吃狗x")
}
func (d Dog) Name() string {
fmt.Println(d.name)
return d.name
}
func main() {
var dog01 *Dog = &Dog{"旺财"}
var a Animal
a = dog01
a.Eat()
a.Name()
a.Talk()
}
指针类型实现接口,值类型存不进去
package main
import "fmt"
type Animal interface {
Talk()
Eat()
Name() string
}
type Dog struct {
name string
}
func (d *Dog) Talk() {
fmt.Println("汪汪汪...")
}
func (d *Dog) Eat() {
fmt.Println("吃狗x")
}
func (d *Dog) Name() string {
fmt.Println(d.name)
return d.name
}
func main() {
var dog01 Dog
var a Animal
// 若类变量存储在接口变量中
// 若传值类型,不能获取变量地址,d取不到地址
// 寻址问题不通过
// 编译不通过
a = dog01
}
同一个类型可以实现多个接口
package main
import "fmt"
type Animal interface {
Talk()
Eat()
Name() string
}
type Animal2 interface {
Run()
}
type Dog struct {
name string
}
func (d *Dog) Talk() {
fmt.Println("汪汪汪...")
}
func (d *Dog) Eat() {
fmt.Println("吃狗x")
}
func (d *Dog) Name() string {
fmt.Println(d.name)
return d.name
}
// Dog结构体实现第二个接口
func (d *Dog) Run() {
fmt.Println("run")
}
func main() {
dog01 := &Dog{}
var a01 Animal
var a02 Animal2
a01 = dog01
a02 = dog01
a01.Talk()
a02.Run()
}
接口是可以嵌套的
package main
type Animal interface {
Talk()
Eat()
Name() string
}
type Animal2 interface {
Run()
}
// 继承前两个接口
type Animal3 interface {
Animal
Animal2
}
func main() {
}
空接口
args ...interface{}
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
类型查询
comma-ok
断言switch
测试
comma-ok 断言
package main
//comma-ok断言
import "fmt"
type Person struct {
name string
age int
}
func main() {
// 定义空接口切片
t := make([]interface{}, 3)
t[0] = 1
t[1] = "hello"
t[2] = &Person{}
// 类型断言: value,ok := 元素.(Type)
// value是变量值,ok是布尔,是不是这个类型
for i, v := range t {
if value, ok := v.(int); ok {
fmt.Printf("index[%d]是int类型, value: %d
", i, value)
}else if value, ok := v.(string); ok {
fmt.Printf("index[%d]是string类型, value: %s
", i, value)
} else {
fmt.Printf("其他类型
")
}
}
}
switch 测试
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
// 定义空接口切片
t := make([]interface{}, 3)
t[0] = 1
t[1] = "hello"
t[2] = &Person{}
for i, v := range t {
switch value := v.(type) {
case int:
fmt.Printf("index[%d]是int类型, value: %d
", i, value)
case string:
fmt.Printf("index[%d]是string类型, value: %s
", i, value)
default:
fmt.Printf("其他类型
")
}
}
}