结构体的介绍
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中面向接口编程是非常重要的特性;
结构体与结构体变量的区别
将一类事物的特性提取出来,形成一个新的数据类型,就是一个结构体;
通过这个结构体,我们可以创建多个变量(实例/对象),事物可以是猫类,也可以是Person,Fish 或者某个工具类...
从某个结构体到变量,就是创建了一个该结构体变量,也可以说是定义一个该结构体变量;
结构体:是自定义的数据类型,代表一类事物(猫,人);
结构体变量【实例】是具体的,实际的,代表一个具体的变量;
结构体在内存中布局
结构体即值类型,可以直接修改属性,即变量直接指向空间,而不是指向一个地址,而再通过地址指向某空间;
结构体的声明和字段使用陷阱
type 标识符(结构体名称) struct {
field1 type
field2 type
}
示例:
type Student struct { // 结构体名字首字母大写既可以跨包使用
Name string
Age int
Address string
}
字段/属性 基本介绍:
从概念或叫法上看,结构体字段 = 属性 = field
字段是结构体的一个组成部分,一般是基本数据类型,数组,也可以是引用类型;
注意事项和细节说明:
在创建一个结构体变量后,如果没有给字段赋值,各字段都对应一个零值(默认值),规则同前面讲的一样:
布尔类型是 false,整型:0,字符串是:""
数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0,0,0]
指针,slice, 和map的零值都是null即nil,即还没有分配空间,需要先make才能使用。
示例:
// 结构体
type Person struct {
Name string
Age int
Scores [5]float64
ptr *int // 指针
slice []int // 切片
map1 map[string]string // map
}
// 实例化一个Person对象
// 结构体 类似 其他语言的class
var p1 Person
fmt.Println(p1) // { 0 [0 0 0 0 0] <nil> [] map[]}
if p1.ptr == nil {
fmt.Println("ok1")
}
if p1.slice == nil {
fmt.Println("ok2")
}
if p1.map1 == nil {
fmt.Println("ok3")
}
// 使用slice 一定要 make
p1.slice = make([]int, 10)
p1.slice[0] = 200
fmt.Println(p1.slice) // [200 0 0 0 0 0 0 0 0 0]
// 使用map 一定要make
p1.map1 = make(map[string]string)
p1.map1["k1"] = "v1"
fmt.Println(p1.map1) // map[k1:v1]
// 基本数据类型需要使用 new
p1.ptr = new(int) // new 内置函数对 值类型进行 分配内存 需要传类型参数
*p1.ptr = 10
fmt.Println(p1.ptr) // 0xc0000a0838
fmt.Println(*p1.ptr) // 10
fmt.Println(p1) // { 0 [0 0 0 0 0] 0xc0000a0838 [200 0 0 0 0 0 0 0 0 0] map[k1:v1]}
不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个。
示例:
type Monster struct {
Name string
Age int
}
var m1 Monster
m1.Name = "袋鼠"
m1.Age = 200
m2 := m1 // 完全复制了一份新的空间,然后把值也赋值给 m2 所以 修改m2 不会影响 m1
m2.Name = "花袋鼠"
fmt.Println(m1, m2) // {袋鼠 200} {花袋鼠 200}
创建结构体实例的四种方式:
创建结构体变量和访问结构体字段
方式1:直接声明
var per Person
方式2:{} 推荐
var per Person = Person{}
示例:
m := Monster{"杀无赦", 100}
fmt.Println(m) // {杀无赦 100}
方式3:
示例:
var m1 *Monster = new(Monster)
// 因为m1 是一个指针,因此标准的给字段赋值方式
// (*m1).Name = "老沙" 也可以写成 m1.Name = "沙师弟"
// 原因:go的设计者 为了程序员使用方便,底层会对 m1.Name = "沙师弟" 进行处理 // 会给 m1 加上 取值运算 (*m1).Name = "沙师弟"
(*m1).Name = "老沙"
m1.Name = "沙师弟"
(*m1).Age = 200
m1.Age = 300
fmt.Println(m1) // &{沙师弟 300}
fmt.Println(*m1) // {沙师弟 300}
方式4:类似方式三 都是结构体指针
示例:
// var p *Person = &Person{"smith", 30} 也可以赋初值
var p *Person = &Person{}
p.Name = "mary"
p.Age = 30
fmt.Println(*p) // {mary 30 }
结构体使用细节
结构体的所有字段在内存中是连续的;
结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字,,个数,和类型)
示例:
type A struct {
num int
}
type B struct {
num int
}
var a A
var b B
a = A(b) // 可以转换,前提:结构体的字段完全一样;
结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转。
struct的每个字段上,可以写上一个tag(Domain string `json:"domain"` -->tag),该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化;(结构体的序列化:使用的包:"encoding/json" , 方法:json.Marshal(结构体实例),返回的是[]byte 切片,所以需要强转为str--> string([]byte) ---> 变为字符串)