1.go语言面向对象说明
● golang也支持面向对象编程(OOP),但是和传统的面向对象有区别,并不是纯粹的面向对象.所以go支持面向对象编程特性是比较准确的
● golang没有类(Class),go的结构体(struct)和其它语言的类(Class)有同等地位
● golang面向对象编程非常简洁,去掉了传统的OOP语言的方法重载,构造函数和析构函数,隐藏的this指针等
● golang仍有面向对象编程的继承,封装和多态的特性,只是实现方式和其它OOP语言不一样,比如继承:golang没有extends关键字,继承是通过匿名字段来实现
● golang面向对象(OOP)很优雅,OOP本身就是语言类型系统的一部分,通过接口(interface)关联,耦合性低,也非常灵活
2.结构体
2.1 结构体与结构体变量的关系图
2.2 快速入门
package main
import "fmt"
type Cat struct {
Name string
Age int
Color string
Hobby string
}
func main(){
//创建一个Cat变量
var Cat1 Cat // var a int
Cat1.Name = "小白"
Cat1.Age = 10
Cat1.Color = "白色"
Cat1.Hobby = "吃鱼"
fmt.Println(Cat1)//{小白 10 白色 吃鱼}
fmt.Println("name:",Cat1.Name)//name: 小白
}
2.3 如何声明结构体
● 基本语法:
type 结构体名称 struct {
field1 type
field2 type
}
● 基本介绍
○ 结构体字段=属性=field
○ 字段是结构体的一个组成部分,一般是基本数据类型,数组,也可是引用类型
● 注意事项
○ 字段声明语法同变量
○ 字段的类型可以为:基本类型,数组或引用类型
○ 不同结构体变量的字段是独立,互不影响的,一个结构体的字段更改,不会影响到另外一个,结构体是值传递
○ 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)
■ 布尔是false,数值是0,字符串是""
■ 数组类型的默认值和它的元素类型相关
■ 指针,slice,map的零值都是nil,既没有分配空间
package main
import "fmt"
type Cat struct {
Name string
Age int
Color string
Hobby string
slice []int
map1 map[string]string
}
func main(){
//创建一个Cat变量
var Cat1 Cat // var a int
Cat1.Name = "小白"
Cat1.Age = 10
Cat1.Color = "白色"
Cat1.Hobby = "吃鱼"
Cat1.slice = make([]int,10)
Cat1.slice[0]=100
Cat1.map1 = make(map[string]string)
Cat1.map1["no1"] = "tom"
}
2.4 创建结构体方式
方式一:var Cat1 Cat
上面的方式
var Cat1 Cat
方式二:varCat2 Cat = Cat{}
varCat2 Cat = Cat{}
Cat2.Name = "小花"
fmt.Println(Cat2)//{小花 0 [] map[]}
或者varCat2 Cat = Cat{"小花",20等}
方式三:varCat3 *Cat = new(Cat)
varCat3 *Cat = new(Cat)
//标准写法
(*Cat3).Name = "小花"
//go的设计者为了方便,底层对Cat3.Name = "小花~~"进行了处理
Cat3.Name = "小花~~"
fmt.Println(Cat3)//&{小花~~ 0 [] map[]}
方式四:varCat4 *Cat = &Cat{}
varCat4 *Cat = &Cat{}
(*Cat4).Name = "小黑"
Cat4.Name = "小黑~~"
fmt.Println(Cat4)//&{小黑~~ 0 [] map[]}
说明:
○ 第三种和第四种返回的是 结构体指针
○ 结构体指针访问字段的标准格式为:(*Cat4).Name = “小黑”,但是go做了简化,也支持Cat4.Name = “小黑~~”,底层进行了转换
○ fmt.Println(Cat4.Name)这样写是错误的,因为.优先级高于
2.5 结构体使用注意事项
● 结构体的所有字段在内存中都是连续的
● 结构体使用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字,个数和类型),需要强转
● 结构体进行type重新定义后(相当于取别名),golang认为是新的数据类型,但是可以强转
● struct的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
3.方法
golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是struct
3.1 快速入门
package main
import "fmt"
type Person struct{
Name string
Age int
}
type Dog struct{
Name string
Age int
}
func (person Person) test(){
fmt.Println(person.Name)
}
func main(){
var p1 Person
p1.Name = "tom"
p1.Age = 20
p1.test()//tom 正确
test()//错误
var D1 Dog
D1.Name = "haha"
//如果别的类型调用该方法会报错
D1.test()//D1.test undefined (type Dog has no field or method test) 错误
}
说明:
● test方法和Person类型绑定
● test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
● func (person Person) test() 。。。。 person表示哪个Person变量调用,person就是这个Person变量的副本
3.2 方法的声明/定义
func (recevier type) methodName (参数列表) (返回值列表){
方法体
return 返回值
}
● 参数列表:表示方法输入
● recevier type:表示这个方法和type这个类型进行绑定,或着说该方法作用域type类型上
● recevier type:type可以是结构体,也可以是其他的自定义类型
● recevier:就是type类型的一个变量
● 返回值列表:表示返回的值,可以多个
● return语句不是必须的
3.3 方法的注意事项
● 结构体类型是值类型,在方法调用中,遵守值类型的传递机制
● 如果希望在方法中修改结构体变量的值,可以通过结构体指针的方式来处理
func (person *Person) update(str string){
//(*person).Name = str //标准写法
person.Name = str //也可以这样写,编译器底层做了优化
}
func main(){
var p1 Person
p1.Name = "tom"
p1.Age = 20
p1.test()//tom
//标准写法
//(&p1).update("tom~~~") //标准写法
p1.update("tom~~")也可以这样写,编译器底层做了优化
fmt.Println(p1)
● golang中的方法作用在指定的数据类型上的,因此定义类型都可以有方法,而不仅仅是struct,比如int ,float32等都可以有方法
● 比如int 要type myInt int,不然报错
package main
import "fmt"
func (i int) test(){
fmt.Println(i)
}
func main(){
i := 10
i.test()//报错 i.test undefined (type int has no field or method test)
}
package main
import "fmt"
type myInt int
func (i myInt) test(){
fmt.Println(i)
}
func main(){
var i myInt =10
i.test()//10
}
● 方法的访问的范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写可以在本包和其他包访问
● 如果一个类型实现了String()方法,那么fmt.Prinltn默认会调用这个变量的String()进行输出
4.方法和函数的比较
● 调用方式不一样
○ 函数的调用方式:函数名(实参列表)
○ 方法的调用方式:变量名.方法名(实参列表)
● 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
package main
import "fmt"
type Person struct{
Name string
}
//函数
func test01(p Person){
fmt.Println(p.Name)
}
func main(){
p := Person{"tom"}
test01(p)//正确
test01(&p)//错误 cannot use &p (type *Person) as type Person in argument to test01
}
● 对于方法(如struct的方法),接收者为值类型,可以直接用指针类型的变量调用方法,反过来也一样
package main
import "fmt"
type Person struct{
Name string
}
//函数
func test01(p Person){
fmt.Println(p.Name)
}
//方法
func (p Person) test02(){
p.Name = "jack"
}
func (p *Person) test03(){
p.Name = "jack"
}
func main(){
p := Person{"tom"}
//函数
test01(p)//正确 tom
//test01(&p)//错误 cannot use &p (type *Person) as type Person in argument to test01
//方法
p.test02()
fmt.Println(p)//{tom} 正确
//函数
// p.test02()//正确
// (&p).test02()//正确 因为方法是值传递,所以编译器底层会优化为p.test02(),但是不会改变Name的值
// fmt.Println(p)//{tom} 正确
//(&p).test03()//正确
p.test03()//正确 因为方法是引用传递,所以编译器底层会优化为(&p).test03(),Name的值会改变
fmt.Println(p)//{jack}
}
总结:
● 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看的是这个方法和哪个类型绑定
● 比如 p Person 是值拷贝 , p *Person地址拷贝
5.面向对象编程应用实例
步骤:
(1)声明结构体,确定结构体名
(2)编写结构体的字段
(3)编写结构体的方法
6.工厂模式
golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题
6.1 需求
一个结构体:
package model
type Student struct{
Name string.....
}
因为这里的Student的首字母是大写的,如果我们想在其它包创建Student的实例,比如main包,引入model包后,就可以直接创建Student结构体的变量
问题:如果Student首字母是小写的,就不行了,怎么办。。。。。使用工厂模式来解决
6.2 工厂模式解决问题
使用工厂模式实现跨包创建结构体实例案例
(1)结构体名为大写,在其它包使用(完全正确)
如果model包的结构体变量首字母大写,引入后,直接使用,没有问题
● 在model定义结构体
● 在main中创建实例,Student首字母是大写,完全正确
● 结果:{tom 88.8}
(2)结构体名为小写,在其他包使用(报错)
如果model包结构体首字母为小写,引入后不能直接使用,可以使用工厂模式
修改student.go代码
package model
//定义结构体
type student struct{
Name string
Score float64
}
//因为student首字母为小写,只能在该包下使用
//通过工厂模式解决
func NewStudent(n string, s float64) *student{
return &student{
Name : n,
Score : s,
}
}
main.go 代码
package main
import (
"fmt"
"go_code/Project01/factory/model"
)
func main(){
//创建Student实例
// var stu = model.Student{
// Name: "tom",
// Score : 88.8,
// }
//student首字母为小写,调用工厂模式
var stu = model.NewStudent("tom",90.5)
fmt.Println(stu)//&{tom 90.5} 返回的是一个地址
fmt.Println(*stu)//{tom 90.5} 取值
}
(3)结构体名大写,字段小写(报错)
如果Name首字母变成小写,则报错
修改student.go代码
package model
//定义结构体
type student struct{
name string
Score float64
}
//因为student首字母为小写,只能在该包下使用
//通过工厂模式解决
func NewStudent(n string, s float64) *student{
return &student{
name : n,
Score : s,
}
}
//name首字母为小写
func (s *student) GetName() string{
return s.name
}
main.go
package main
import (
"fmt"
"go_code/Project01/factory/model"
)
func main(){
//创建Student实例
// var stu = model.Student{
// Name: "tom",
// Score : 88.8,
// }
//student首字母为小写,调用工厂模式
var stu = model.NewStudent("tom",90.5)
fmt.Println("name=",stu.GetName(),"Score=",stu.Score)//name= tom Score= 90.5
}