Go面向对象说明
- Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
- Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,可以理解 Golang 是基于 struct 来实现 OOP 特性的。
- Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等
- Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现。
- Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过**接口(interface)**关联,耦合性低,也非常灵活。也就是说在Golang 中面向接口编程是非常重要的特性。
结构体
结构体是值类型,默认为值拷贝。除非使用指针进行拷贝,为引用,修改指针引用的变量,原结构体也会随之改变
结构体中的指针,切片,映射的默认值均为nil,未分配空间,如果要使用,应首先进行make
声明结构体
type 结构体名称 struct {
field1 type
field2 type
}
创建结构体变量和访问结构体字段
方法一:直接声明
var person Person
方法二:{ }
var person Person = Person{field,field}
方法三:&
var person *Person = new (Person)
方法四:&、{ }
var person *Person = &Person{field,field}
说明
-
第 3 种和第 4 种方式返回的是 结构体指针。
-
结构体指针访问字段的标准方式应该是:
(*结构体指针).字段名
(*person).Name = "tom"
-
但 go 做了一个简化,也支持
结构体指针.字段名
person.Name = "tom"
更加符合程序员使用的习惯,go 编译器底层 对person.Name 做了转化 (*person).Name。
使用细节
- 结构体的所有字段在内存中是连续的
- 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
- 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强制类型转换
4.struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
type Person struct{
Name string `ison:"name"`
Age int `json:"age"`
}
person := Person{"lzq",18}
//将变量序列化为JSON格式字串
//json.Marshal函数中使用反射
jsonStr,err := json.Marshal(person)
if err != nil{
fmt.Prinln("json处理错误",err)
}
fmt.Prinln("jsonStr",string(jsonStr))
方法
在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓名…),Person 结构体还有一些行为比如:可以说话、跑步…,通过学习,还可以做算术题。这时就要用方法才能完成。
Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct。
方法的声明和调用
type Person struct {
Name int
}
func (p Person) test() {
fmt.Println("test() name=",p.Name)
}
//调用
func main(){
var p Person
p.Name="lzq"
p.test()//调用方法
}
test( )方法和Person类型绑定
test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用
var dog Dog
dog.test()
test()//这两种调用方式都是错误的
方法可以传入参数、返回值
func(p Person) calculate(n int) res int
方法的调用和传参机制原理(重要!!)
方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。
如以下代码
func(p Person) calculate(n int) res int{
res:= 0
for i:=1;i<=n;i++{
res += i
}
return res
}
func main(){
var p Person
n:=2
res := calculate(n)
fmt.Prinln(res)
}
程序运行过程中会先为main函数创建一个栈区
main栈
p—>[lzq] Name
n—>[2]
res:=30
再调用calculate方法并传入实参
calculate栈
p—>[lzq] Name
n—>[2]
return 6
方法的注意事项和细节
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
- 修改结构体变量的值,可以通过结构体指针的方式来处理
func (p *Person) modify() {
p.Name ="xmy"
}//此处的p.Name等价于(*p).Name
var p Person
p.Name ="lzq"
3.Golang 中的方法作用在指定的数据类型上的(即和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法
//在其他数据类型中使用方法
package main
import (
"fmt"
)
type integer int
func (i integer) print() {
fmt.Println("i=",i)
}
func ( i *integer) change(){
*i+=1
}
func main() {
var i integer = 10
i.print()
i.change()
fmt.Println("i=",i)
}
4.方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
5.如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()进行输出
type Student struct{
Name string
Age int
}
//给*string实现方法String()
func (stu *Student) String() string{
str := fmt.Sprintf("Name=[%v] Age=[%v]",stu.Name,stu.Age)
return str
}
func main(){
stu := Student{
Name : "lzq",
Age : 18,
}
fmt.Println(&stu)
}
方法和函数的区别
-
调用方式不一样
- 函数的调用方式: 函数名(实参列表)
- 方法的调用方式: 变量.方法名(实参列表)
-
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
-
方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
上图揭示了方法传参的特点。
- 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
- 如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则是地址拷贝。