面向对象编程(OOP):具体概念不讲了,学过java,py都知道,golang中没有类的概念,可以用结构体来模拟替代类。
Golang支持面向对象编程的特性,去掉了传统OOP语言的集成,方法重载,构造函数和析构函数,隐藏的this指针等。
结构体:
抽取事物相同特征和行为形成新的数据类型,就是一个结构体,通过结构体可以创建很多实例(类似py就是类创建多个实例);
上述是为了解决描述一个事物拥有的不同属性和方法在Golang中无法统一定义,例如年龄不应该为string型,可是为了int又需要额外定义一个数组或者其他方式保留,太蛋疼。
结构体定义:
★★★值类型,当声明一个结构体type定义时就已经分配了内存,什么都不传就是 {0和空等}
★★★
type (nbaPlayers) struct { // type,struct 关键字,结构体名字自己起,和python中的 Class 同理
Name string
Age int
color string
hobbie string
}
//上述定义完成后,下方即可使用结构体作为变量
var player1 nbaPlayers //定义完了,nbaPlayers就是个结构体,和int,string等一样
示例:
package main
import "fmt"
func main() {
type nbaPlayers struct {
Name string
Age int
color string
hobbie string
}
//定义方式1:
var player1 = nbaPlayers{"阿狗",23,"red","eatting"}
fmt.Println(player1) // {阿狗 23 red eatting}
//定义方式2: 使用 <实例>.<属性> 来读取相应的数值
var player2 nbaPlayers
player2.Name = "阿猫"
player2.Age = 20
player2.color = "blue"
player2.hobbie = "sleeping"
fmt.Println(player2) // {阿猫 20 blue sleeping}
//具体打印某个属性
fmt.Println(player2.Age) // 20
//具体还可以绑定nbaPlayers的方法(函数),后续再说
}
字段/属性/Fields
由于是值类型,实例产生后不赋值,则各种数据类型都为默认值:
值类型:
bool:false , string:"" , int::0 , [n]int:[ n个0 ]
引用类型:
ptr , slice , map 的零值都是nil,说明没有分配内存空间,需要make()后才可以使用
不同结构体变量的字段是独立互不影响的,因为是值类型,改变一个实例的一个字段不会影响到另一个实例。
package main
import "fmt"
func main() {
type nbaPlayers struct {
Name string
Age int
color string
hobbie string
Ptr *int
Slice []int
Map1 map[string]string
}
/*
默认下面三种引用类型都是nil的,可以使用if ptr == nil 成立则输出结果来判断
*/
var temp = 2
var player1 nbaPlayers
player1.Ptr = &temp
fmt.Println(player1.Ptr)
if player1.Slice == nil {
fmt.Println("OK")
}
player1.Slice = make([]int,2) //
player1.Slice[0] = 10
player1.Slice[1] = 20
for i := 0 ; i < 5 ; i++ {
player1.Slice = append(player1.Slice, i)
}
fmt.Println(player1.Slice)
}
结构体赋值的四种方式:
package main
import "fmt"
func main() {
type Pl struct {
Name string
Age int
}
//方式1
var p1 Pl = Pl{"dd",20,}
fmt.Println(p1)
//方式2
var p2 Pl
p2.Name = "ee"
p2.Age = 20
fmt.Println(p2)
//方式3:返回结构体指针
var p3 *Pl = new(Pl)
p3.Name = "ff" //设计者简化了写法,实际上p3都已经默认加了 (*p3)
(*p3).Age = 20
fmt.Println(*p3)
//方式4:返回结构指针2
var p4 *Pl = &Pl{}
p4.Name = "gg" //设计者简化了写法,实际上p4都已经默认加了 (*p3)
(*p4).Age = 20
fmt.Println(*p4)
}
结构体内存分配机制
结构体强转
var i integer = 10
var j int = 20
j = i //也会报错!必须强转 j = int(i)这样才可以,虽然i也是int但是它是integer...
结构体使用细节和原理
package main
import (
"encoding/json"
"fmt"
)
type Nbaplayer struct {
Name string `json:"name"` //序列化需要引入import "encoding/json"
Num int `json:"num"` //由于json函数中使用结构体参数表示Nbaplayer要在别的包被使用,所以必须变量大写,不然返回空
Abil string `json:"abil"` //使用`json:"<字段名称>"` 这样后续json字符串首字母变小写了{"name":"durant","num":35,"abil":"四肢长"}
}
func main() {
var player1 = Nbaplayer{"durant",35,"四肢长"} //实例化
jsonstring , _ := json.Marshal(player1) //序列化函数 json.Marshal(),返回值是一个序列化结果和一个err,忽略err值取字符串
fmt.Println(jsonstring) //是一个字节类型的串儿,无法阅读[123 34 78 97 109 101 3...]
fmt.Println(string(jsonstring)) //如果想看懂用string()强转即可,{"Name":"durant","Num":35,"Abil":"四肢长"}
/*
json返回结构体字段注定都是变量大写,因为Golang规定大写才能被其他包使用,所以需要使用,`json:"<字段名称>"`这个就是tag方式
可以把首字母大写转成小写,这个里面用到了反射,后续再讲,现在还没学到。
*/
}
方法
方法和函数的区别:
一句话概括:除了方法除了多了绑定(<随意相关参数> <绑定数据类型>)外,其他跟函数使用的方式一模一样。
类似,但是函数是 func <函数名>(参数) {…},而方法由于关联到数据类型 func (<随意相关参数> <绑定数据类型>) <函数名>(参数) {…} 区别就在这儿。
具体实例: type Cat struct {…} ; func (maomao Cat) eat() {…} //type了一个结构体,后面的方法只能由结构体类型的数据调用。
随意相关参数就是一个形参,有点类似python中的self。
详见下方代码:
package main
import "fmt"
type Persons struct {
Name string
Age int
}
func (p Persons) eat(<传参>) { //只能由某种数据类型的才可以调用的方法,其他实例不可以调用
p.Name = "阿猫"
fmt.Println("eat up lots of shit~",p.Name) //调用方法的时候使用的是值拷贝,即副本拷贝,方法内部变量的改变并不会改变main函数中的实例的值
}
func main() {
var p1 Persons = Persons{"阿狗",3} //创建一个实例
p1.eat() //实例使用一个方法,仅仅是Persons的生成的实例可以使用,调用函数所以输出阿猫
fmt.Println(p1.Name) //最后p1.Name还是阿狗
}
练习题:创建一个圆的属性和方法求面积
package main
import "fmt"
type Circle struct {
Radius float64
}
func (cir Circle) area(radius float64) float64 {
pi := 3.141592653
mianji := pi * radius * radius
return mianji
}
func main() {
var cir = Circle{10}
area1 := cir.area(cir.Radius) //这个cir是要传入func (cir Circle) area(radius float64) float64{}中的
fmt.Println("圆形的面积为:",area1)
}
方法注意事项和细节讨论
结构体类型是值类型,在方法调用中,遵守值类型传递,是值拷贝传递方式。
如果希望在方法中,改变结构体变量的值,则可以通过结构体指针的方式传输,结构体指针这样相比于庞大的结构体,传输值更小,效率更高,推荐!
看如下示例:
package main
import "fmt"
type Circle struct {
Radius float64
}
func (cir Circle) area1(radius float64) float64 { //没啥说的,值拷贝,即便定义了radius值,也不会影响main()中
pi := 3.141592653
mianji := pi * radius * radius
return mianji
}
func (cir *Circle) area2() float64 { //推荐使用这种方式传递结构体变量
pi := 3.141592653 //但是根据golang底层编译器设计,已经做了优化,等于所有地址,取值都不用了
(*cir).Radius = 20 //函数体内修改指针地址对应的值,main()的cri.Radius也会发生变化,写成cir.Radius也行,编译器优化
mianji := pi * (*cir).Radius * (*cir).Radius //mianji := pi * cir.Radius * cir.Radius 这样没有任何问题,cir = (*cir)
return mianji
}
func main() {
var cir = Circle{10}
area1 := cir.area1(cir.Radius)
fmt.Println("圆形的面积为:",area1)
area2 := (&cir).area2() //同上,area2 := cir.area2()也没有任何问题,cir = (&cir),仅仅限于此,为了安全还是都表明取址取值
fmt.Println("圆形的面积为:",area2)
fmt.Println(cir.Radius) //变为20了。
}
不仅仅是结构体,float64,int也可以有自己的方法,也可以反映值拷贝和地址拷贝
package main
import "fmt"
type Integer int
func (i Integer) Print() { //值拷贝,修改i的值不会影响到main的值
i++
fmt.Println("i= ",i)
}
func (i *Integer) change() { //地址拷贝,函数内修改直接影响main中的值
*i++
fmt.Println("i= ",*i)
}
func main() {
var i Integer = 10
i.print()
fmt.Println(i)
i.change()
fmt.Println(i)
}
方法与结构体,函数,变量一样,首字母大写则可在其他包中被调用。
如果一个类型实现了string()这个方法,那么fmt.Println()默认会调用这个变量的string()进行输出。这个确实不大好理解,但是大概知道什么意思
package main
import "fmt"
type Student struct {
Name string
Age int
}
func (stu *Student) String() string {
str := fmt.Sprintf("name = %v , age = %v",stu.Name,stu.Age) //也就解释了第五条
return str
}
func main() {
var stu = Student{"durant",35}
fmt.Println(&stu) //自动调用了String()方法后的输出 name = durant , age = 35
fmt.Println(stu) //完全是通过main中传递,没有调用任何方法 {durant 35}
}