文章目录
GO基本语法学习之路 Day2
1. 面向对象的封装
GO语言中没有类的概念,也不支持类的继承等面向对象的概念。GO语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
结构体的定义
// 结构体的定义
type hero struct {
name string
ad int
level int
}
如果类名首字母大写,表示其他包也能够访问,和函数一样,即全局类;
如果类的属性首字母大写,表示对其他包是public
的,否则则是private
的,只能自己包使用
如果类的方法首字母大写,表示当前方法在其他包也能访问,这一点都是通用的
通过上面也能看出,go的封装是针对包来封装的。
结构体方法的创建
func (this hero) show() {
fmt.Println("NAME = ", this.name)
fmt.Println("ad = ", this.ad)
fmt.Println("level = ", this.level)
}
// 结构体的方法
func (this hero) GetName() string {
return this.name
}
func (this hero) SetName(name string) {
this.name = name
}
创建一个对象:这里类似于C++中的初始化构造,大括号里面加入键值对,当然也可以省略键值对
func main() {
// 创建一个对象
h := hero{name:"JehanRio", ad:21, level:1}
// h := hero{"JehanRio", 21, 1} // 这样也可以
h.show()
}
/*
运行结果:
NAME = JehanRio
ad = 21
level = 1
*/
需要注意的是,this会对对象进行拷贝,所以你修改的数据是无法做到的,除非把this换为指针,即func(this *hero) SetName
func (this* hero) SetName(name string) {
this.name = name
}
func main() {
// 创建一个对象
h := hero{name:"JehanRio", ad:21, level:1}
h.show()
h.SetName("Bjergsen")
h.show()
}
2. 面像对象继承
与类不同,Go语言中没有类的概念,也没有类的继承。但可以通过嵌套结构体来实现类似继承的效果。通过在一个结构体中嵌套另一个结构体,可以继承嵌套结构体的字段和方法,俗称套娃。。。
不过我觉得更像是组合,而不是继承。
同时,子类也可以重写父类方法
package main
import "fmt"
type Human struct {
name string
sex string
}
func (this *Human) Eat() {
fmt.Println("Human Eat...")
}
func (this *Human) Walk() {
fmt.Println("Human Walk...")
}
type SuperMan struct {
Human // Superman继承了Human的方法
level int
}
// 重定义父类方法
func (this *SuperMan) Eat() {
fmt.Println("SuperMan Eat...")
}
func (this *SuperMan) Fly() {
fmt.Println("SuperMan Fly...")
}
func main() {
h := Human{name:"JehanRio", sex:"man"}
h.Eat()
h.Walk()
fmt.Println("____________________________")
s := SuperMan{Human{"JehanRio", "man"}, 99}
s.Walk()
s.Eat()
s.Fly()
}
3. 多态interface
相当于C++的抽象类+子类。在GO里面就相当于定义了全局的一些虚函数。
package main
import "fmt"
// 本质是一个指针
type animalIF interface {
sleep()
getColor() string
getType() string
}
// 具体的类
type cat struct {
color string
}
func (this *cat) sleep() {
fmt.Println("cat is sleeping")
}
func (this *cat) getColor() string {
return this.color
}
func (this *cat) getType() string {
return "cat"
}
// 具体的类
type dog struct {
color string
}
func (this *dog) sleep() {
fmt.Println("dog is sleeping")
}
func (this *dog) getColor() string {
return this.color
}
func (this *dog) getType() string {
return "dog"
}
func showAnimal(animal animalIF) {
animal.sleep() // 多态调用
fmt.Println("color : ", animal.getColor())
fmt.Println("type : ", animal.getType())
}
func main() {
// var animal animalIF
// animal = &cat{"red"} // interface本身是一个指针,必须传地址
// animal.sleep() // 调用cat的sleep
c := cat{"red"}
d := dog{"yellow"}
showAnimal(&c)
showAnimal(&d)
}
任何语言的多态思想都是通用的,父类 = new 子类
这种,即子类能转换为父类。
所以go的多态基本要素为:
- 有一个父类(有接口)
- 有子类(实现父类的全部接口方法)
- 父类类型的变量(指针)=(指针)子类的具体数据变量
4. 通用万能类型与类型断言
即interface{}
,常用类型都实现了interface,和Python的Object有点像。
这里还出现了类型断言,他可以判断interface的底层类型是什么,具体用法看代码。
package main
import "fmt"
func myFunc(arg interface{}) {
fmt.Println("myFunc is called")
fmt.Println(arg)
// interface{} 该如何区分 此时引用的底层数据类型是什么?
// 给interface{} 提供了“类型断言”的机制
value, ok := arg.(string) // 会返回两个值
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}
type book struct {
author string
}
func main() {
b := book{"JehanRio"}
myFunc(b)
myFunc("3.14")
}
5. 变量的内置pair结构
GoLang中的变量组成:
go里面的断言就是类型转换,转换的时候会把type和value一起变换,变换方法为w = r.(Writer)
断言的时候分两步:
- 得到动态类型 type
- 判断 type 是否实现了目标接口
下面的例子中,我们的变量b,类型是Book类型,是一个接口,而这个类型实现了两个方法,分别是ReadBook()
和WriteBook()
,所以在断言时,才能从reader类型变为Writer类型(若将WriteBook()的实现注释掉,则会断言失败!)
type reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
type Book struct {
}
func (this *Book) ReadBook() {
fmt.Println("Read a book")
}
func (this *Book) WriteBook() {
fmt.Println("write a book")
}
func main() {
b := &Book{} // b: pair<type:Book, value:Book{}地址> 相当于子类,需要使用地址,才能被父类接收
var r reader // r: pair<type:, value:> 一开始为空
r = b
r.ReadBook()
var w Writer
w = r.(Writer) // 此处的断言为什么会成功? 断言有两步:1. 得到动态类型 type,2. 判断 type 是否实现了目标接口。这里断言成功是因为 type 是 Book,而 Book 实现了 Writer 接口
w.WriteBook()
}
7. 区分一下类型断言和类型转换
类型断言
var greeting interface{} = "hello world"
greetingStr := greeting.(string)
类型转换
greeting := []byte("hello world")
greetingStr := string(greeting)
最明显的不同是它们有着不同的语法: variable.(type)
vs type(variable)
首先,类型断言只能作用在接口上,以检查其是否实现了期望的接口或者具体的类型,类型断言有两种用法:
<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言
<目标类型的值> := <表达式>.( 目标类型 ) //非安全类型断言
总的来说,类型转换就是从一个确定类型转换为另一个确定类型;而类型断言就是将一个未知类型(interface{})转换为一个确定类型。
8. 反射reflect机制
interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。
reflect的基本功能TypeOf和ValueOf
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero
func ValueOf(i interface{}) Value {...}
//ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value,例子如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("type: ", reflect.TypeOf(num))
fmt.Println("value: ", reflect.ValueOf(num))
}
运行结果:
type: float64
value: 1.2345
说明
- 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
- 转换的时候,要区分是指针还是值
- 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
9. 结构体标签
作用是在别的包在使用你的包的时候,能进行解释。
package main
import (
"fmt"
"reflect"
)
type resume struct {
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}
func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
taginfo := t.Field(i).Tag.Get("info")
tagdoc := t.Field(i).Tag.Get("doc")
fmt.Println("info: ", taginfo, "tag:", tagdoc)
}
}
func main() {
var re resume
findTag(&re)
}