结构体
- 结构体也是一种类型,它可以存储不同的数据类型,定义在函数外部:
type 结构体名 struct { }
type Student struct { id int name string sex byte }
- 结构体是一种数据存储格式,不能在声明结构体时初始化成员,每个成员的值就是所属类型的默认值;
var stu Student fmt.Println(stu) // {0 "" 0}
- 结构体的赋值
- 通过结构体变量为其成员赋值;
var stu Student stu.id = 10020 stu.name = "Mack" stu.sex = 1 fmt.Println(stu) // {10020 Mack 1}
- 结构体的成员顺序是不变的,匿名初始化时必须初始化结构体成员的所有成员,而指定名称的初始化则不需要;
var stu Student = Student{10020, "Mack", 1} fmt.Println(stu) // {10020 Mack 1} var stu Student = Student{name: "Mack"}
- 结构体的传递是值传递,如果结构体的成员都支持
==、!=
,那么该结构体也支持==、!=
stu := Student{10020, "Mack", 1} m := stu m.id = 2 fmt.Println(stu) // {10020 Mack 1} fmt.Println(m) // {2 Mack 1} fmt.Println(m==stu) // false
- 结构体与数组、切片、字典
//结构体数组 var arr [5]Student arr3 := [2]Student{{100, "Mack", 1}, {200, "Rose", 0}} //结构体切片 s := []Student{{100, "Mack", 1}, {200, "Rose", 0}} //结构体Map m := make(map[string]Student) //key为string型,value为结构体Student m["10010"] = {100, "Mack", 1} //结构体切片Map ms := make(map[string][]Student) //key为string型,value为切片,且切片元素类型为结构体Student ms["10012"] = append(ms["10012"], Student{1, "Mack", 1}) fmt.Println(ms) //map[10012:[{1 Mack 1}]]
- 结构体本身在定义时存储在数据区,其中的成员则根据数据类型存储在不同区域,如数组栈区,切片在堆区。
指针
指针用于存储变量的内存地址,指针的类型与变量的类型保持一致;
- 定义:
var p *类型
,一级指针,指针的默认值为nil(空指针)
var p *int fmt.Println(p) //
var num int = 1 p = &num fmt.Println(p) // 0xc00000a0c0 fmt.Printf("%p", &num) // 0xc00000a0c0 -
*
取值运算符,用于从内存地址中存储的值,通过指针还可以修改内存中的值;num := 1 p := &num fmt.Println(*p) // 1 *p = 22 fmt.Println(num) // 22
- 空指针指向内存编号为
0
的空间,但是内存编号0-255
被系统占用,不允许用户进行读写操作;var p *int fmt.Println(*p) // runtime error *p = 1 //runtime error
- 野指针:指向一块未知的空间,与空指针类似,都不允许进行读写操作;
var p *int p = 0x04803c60 *p = 1 //runtime error
- 数组指针
- 数组存储在一块连续的内存空间,数组指针指向数组的首地址;
var arr [5]int = [5]int{1,2,3,4,5} var p *[5]int = &arr fmt.Println(p) // &[1 2 3 4 5] 不会直接打印数组指针指向的地址 fmt.Printf("%p", &arr) // 0xc00006c060 fmt.Printf("%p", &arr[0]) // 0xc00006c060 fmt.Printf("%p", p) // 0xc00006c060 fmt.Printf("%p", &arr[1]) // 0xc00006c068
- 数组指针操作数据元素:
(*)p[0]、p[0]
fmt.Println(*p) // [1 2 3 4 5] (*p)[0] = 100 // [] 操作符的优先级高于 * //Go语言做了优化,使用简写形式 p[1] = 200 fmt.Println(arr) // [100 200 3 4 5] len(p) // 5 通过指针变量获取数组长度
- 切片数组
- 与数组名不同,切片名本身就是一个地址,且这个地址就是切片存储的堆区内存地址;
slice := []int{1,2,3,4,5} fmt.Printf("%p", slice) // 0xc00006c060 fmt.Printf("%p", &slice[0]) // 0xc00006c060 p := &slice fmt.Printf("%p", p) // 0xc00004c420
-
&slice
获取的是栈区中slice
变量的地址,而不是切片存储在堆区的地址! - 通过指针操作切片时的查找过程:
p -> slice变量地址 -> slice指向的堆区地址
(*p)[0] = 200 fmt.Println(slice) // [200 2 3 4 5] fmt.Println(*p) // [200 2 3 4 5]
- Go没有对指针访问切片做优化,仍需要使用取地址操作符
*
- 这种没有直接指向堆区地址的指针,称为二级指针!
- 既然指针指向的是切片变量的地址,那么对指针使用
append()
导致切片扩容时,切片变量指向的堆区地址也会随之变化!
slice := []int{1,2,3} fmt.Printf("%p", slice) // 0xc00006c060 p := &slice *p = append(*p, 6, 7, 8, 9) fmt.Printf("%p", slice) // 0xc00008a000 fmt.Println(slice) // [1 2 3 6 7 8 9]
- 手动创建指针空间:
new(数据类型)
var p *int p = new(int) // 为指针变量在堆区创建一块内存空间 fmt.Println(p) // 0xc000056080 fmt.Println(*p) // 0
- 开辟的空间位于堆区,存储的值是数据类型的默认值,并返回内存地址;
- Go语言中无需关心内存空间的释放,交由系统管理.
- 指针的传递是地址传递,形参可以改变实参的值;
- 指针数组/切片:元素为指针的数组/切片
a, b := 1, 2 var arr [2]*int arr[0] = &a arr[1] = &b fmt.Println(arr) // [0xc00000a098 0xc00000a0b0] var slice []*int slice = append(slice, &a, &b) fmt.Println(slice) // [0xc00000a098 0xc00000a0b0]
- 结构体指针:也是指向结构体的首地址,还可以简化操作结构体成员
type Student struct { id int name string } func main() { var stu Student = Student{1, "Alpa"} var p *Student p = &stu fmt.Printf("%p\n", &stu.id) // 0xc000004480 fmt.Printf("%p\n", p) // 0xc000004480 p.name = "Babel" // (*p).name = "Babel" fmt.Println(stu) // {1 Babel} }
- 多级指针
a := 10 p := &a // 一级指针 P2 := &p // 二级指针 P3 := &p2 // 三级指针
内存模型
- 内存是一块连续的一维数组空间:
低地址 -> 高地址
,其中0 - 255
被系统所占用; - 对于一个应用程序来说,内存可以简单分为四个区域,从低地址到高地址依次为:代码区、数据区、堆区、栈区;
- 代码区:存储计算指令信息,只读
- 数据区:又可分为常量区、初始化数据区,未初始化数据区,其中常量区不允许显示内存地址,而结构体就位于未初始化数据区;
- 堆区:
切片数据、string类型数据、new()...
- 栈区:
局部变量、函数调用时的信息
- 它们之间并不是相邻的,中间还有一些小的区域,其中最高地址段分配给了注册表。
面向对象
Go语言中没有类的概念,但可以将结构体比作类,结构体成员比较类的属性、方法;
Go语言中也没有继承,但可以通过 匿名组合
来实现继承的效果。
- 匿名字段,又叫匿名组合:用一个结构体的名称作为另一个结构体的成员;
- 在初始化子结构体时,可以直接访问父结构体的成员;
type Person struct { name string age int } type Student struct { Person id int score float32 } func main() { var stu Student stu.id = 101 stu.name = "Mack" //直接访问父结构体成员 stu.Person.age = 20 //间接访问 stu.score = 60.5 fmt.Println(stu) //{{Mack 20} 101 60.5} }
- 定义时的初始化
var stu Student = Student{Person{"Jack", 17}, 102, 87}
- 但是,如果子结构体和父结体有同名成员,则采用就近原则:初始化父结构体成员时,必须指定父结构体名称;
type Person struct { name string age int } type Student struct { Person id int name string score float32 } func main() { var stu Student stu.id = 101 stu.name = "Mack" //子结构体自己的name stu.Person.name = "Jason" //父结构的name stu.age = 13 stu.score = 60.5 fmt.Println(stu) //{{Jason 13} 101 Mack 60.5} }
- 指针匿名字段:继承父结构体的指针
type Person struct { name string age int } type Student struct { *Person id int score float32 }
- 指针的默认值是
nil
,称为空指针,无法直接访问
var stu Student stu.name = "Mack" stu.Person.name = "Jason" //runtime error: invalid memory address or nil...
-
new()
初始化指针变量
var stu Student stu.Person = new(Person) stu.Person.name = "Mack" stu.age = 20 fmt.Println(stu.name, stu.age) // Mack 20
- 亦或者:先初始化父结构体,再把地址赋与子结构体的指针;
var p = Person{"Mack", 14} var stu Student stu.Person = &p //定义时初始化 var stu2 = Student{ &Person{"Mack", 14}, 103, 78.5 }
- 指针的默认值是
- 支持多重继承,但自己不允许继承自己,却可以继承自己的结构体指针,这也是链表的实现原理;
type Person struct { *Person name string age int }
方法
- Go中的方法和函数是有明显区别的
func (方法接收者) 方法名(参数列表) 返回值类型 { 方法体 }
- 根据数据类型绑定方法,方法接收者也可以为方法传递参数;
type Integer int //为 int 定义别名 func (a Integer) Test(b Integer) Integer { return a+b } func main() { var c Integer = 3 r := c.Test(3) fmt.Println(r) // 6 }
- Go语言不允许方法直接使用数据类型,必须定义别名!
- 方法接收者是一种类型,为这种类型绑定了方法之后,此类型的变量都具有该方法!
- 结构体也是在为
struct
起别名,那么就可以为结构体绑定方法;type Student struct { id int name string age int } func (s Student) print() { fmt.Println(s.name) } func (s Student) edit(name string) { s.name = name s.print() // 调用另一个方法 } func main() { s := Student{ 101, "Mart", 20} s.edit("Jose") // Jose }
- 结构体指针访问成员时,支持简化操作,访问方法时也支持简化操作;
func (s *Student) edit(name string) { s.name = name s.print() // (*s).edit("Jose") } func main() { s := Student{ 101, "Mart", 20} s.edit("Jose") // (&s).edit("Jose") var s2 *Student //空指针 s2 = new(Student) // 为指针s2 开启空间 s2.edit("Jose") // Jose }
- 考虑到结构体是值传递,所以都应该使用结构体指针作为接收者;
- 结构体中不允许出现同名方法,即使一个方法接收者是结构体名
Student
、另一个接收者是结构体指针*Student
,也不允许!
- 方法继承:匿名字段也可以实现方法的继承,同名方法也遵循就近原则;
- 方法名允许与函数名重名,但在同级别文件中可以直接调用函数,却不能调用方法,必须先定义结构体;
- 方法的本质还是函数,类型都是
func()
,也都存储在代码区。
接口
- 定义
type 接口名 interface { 方法列表 //只有声明,没有具体实现 }
- 对象(结构体)必须实现所有的接口方法,接口指向对象的内存地址,通过接口调用对象实现的方法,这就是多态;
type Humaner interface { say() } type Student struct { name string age int } func (s *Student) say() { fmt.Printf("Student: %s", s.name) } type Teacher struct { name string age int } func (t *Teacher) say() { fmt.Printf("Teacher: %s", t.name) } func main() { var stu Student = Student{ "Mack", 20 } var h Humaner h = &stu h.say() // Student: Mack h = &Teacher{ "Eason", 46 } h.say() // Teacher: Eason }
- 接口也可以通过匿名字段实现继承,且接口中的形参可以省略参数名;
type Humaner interface { Say() } type Male interface { Humaner Dance(string) //形参可以省略参数名 } type Student struct { name string } func (s *Student) Say() { fmt.Printf("%s say", s.name) } func (s *Student) Dance(label string) { fmt.Printf("dance: %s", label) } func main() { var m Male m = &Student{ "Mack" } m.Say() // Mack say m.Dance("AAA") // dance: AAA var h Humaner h = &Student{ "Eason" } h.Say() // Eason say }
- 接口的转换:允许父接口指向子接口,那么父接口在调用方法时,指向的是针对子接口的实现方法;
h = m h.Say() // Mack say
- 空接口:
var a interface{}
- 空接口可以接收任意类型的数据,但空接口的地址不变!
var a interface{} fmt.Printf("%p", &a) // 0xc0000501c0 a = 10 fmt.Printf("%p", &a) // 0xc0000501c0 a = "Hello Go" fmt.Printf("%p", &a) // 0xc0000501c0 a = false fmt.Printf("%p", &a) // 0xc0000501c0
- 空接口切片
var a []interface{} a = append(a, 12, "Hello", [3]int{1,2,3}) fmt.Println(a) // [12 Hello [1 2 3]] b := make([]interface{}, 3)
- 类型断言:类型判断
var b []interface{} b = append(a, 12, "Hello", [3]int{1,2,3}) for _,v:=range b { data,ok := v.(int) // 判断元素v 是不是 int 类型 }
-
ok
表示判断结果,如果是,则为true
,否则为false
; -
data
表示判断类型的值,当前判断是不是int
类型,如果是,data
值为元素值,否则为int
类型的默认值0
;
for _,v:=range b { if data,ok := v.(int); ok { // ok 为 true 时,则进入此分支 } else if data,ok := v.([3]int); ok { } }
-