一、go的面向对象编程特性
-
go 支持面向对象编程(OOP),但是和传统的面向对象编程有区别,所以更确切的说go支持面向对象编程特性
-
go没有类的概念,但有结构体(struct)和接口(interface),通过这两者可以实现面向对象编程的特性,如封装、继承和多态
-
go的面向对象对象编程非常简洁,去掉传统的继承、方法重载、构造函数、析构函数、隐藏的this指针等复杂特性,使得面向对象编程更简单易用
-
go没有extends、implements等关键字,结构体和接口之间的关系是隐式的,只要结构体实现了接口定义的方法,就自动满足了接口的要求,这种设计使得代码更加灵活和简洁
二、结构体和结构体变量关系图
-
结构体代表了一种数据类型,定义了一组字段和方法,可以看作是一个模板或者蓝图
-
结构体变量是结构体类型的实例,包含了结构体定义的字段和方法,可以通过结构体变量来访问和操作这些字段和方法
三、结构体变量在内存中的存储方式
四、声明结构体
type 结构体名 struct {
字段1 数据类型
字段2 数据类型
...
}
例如
type Person struct {
Name string
Age int
sex string
}
var p Person = Person{Name: "Bob", Age: 25, sex: "male"}
-
结构体的字段可以是任意类型,包括基本类型、数组、切片、映射、函数、接口等
-
结构体的字段也可以是匿名字段,即直接在结构体中嵌入另一个结构体,这样就可以实现类似于继承的效果
-
字段的命名遵循驼峰命名法,首字母大写表示可导出(public),首字母小写表示不可导出(private)
-
字段类型可以是任意合法的go类型,包括自定义类型和内置类型
-
创建一个结构体变量后,如果没有给字段赋值,则字段会被自动初始化为该类型的零值,例如数值类型为0,字符串类型为"",布尔类型为false,指针类型为nil等
-
slice、map、channel等引用类型的字段在结构体变量创建时会被初始化为nil,需要在使用前进行初始化,否则会导致运行时错误
-
slice 、map 、channel使用前都需要使用make函数进行初始化
-
结构体变量是值类型,同一结构体的不同变量之间是相互独立的,修改一个变量的字段不会影响另一个变量的字段
-
如果同一个结构体的不同变量,一个变量的改变需要反映到另一个变量上,可以使用指针类型的字段,或者将结构体变量定义为指针类型,这样就可以通过指针来共享数据
package main
import "fmt"
type S struct {
A int
B string
C bool
D []int
E map[string]int
F chan int
}
func main() {
s := S{}
fmt.Printf("%+v\n", s) // 输出:{A:0 B: C:false D:[] E:map[] F:chan int(nil)}
if(s.D == nil) {
fmt.Println("slice D is nil") // 输出:slice D is nil
}
if(s.E == nil) {
fmt.Println("map E is nil") // 输出:map E is nil
}
if(s.F == nil) {
fmt.Println("channel F is nil") // 输出:channel F is nil
}
// 需要使用make函数初始化slice、map、channel等引用类型的字段
s.D = make([]int, 0)
s.E = make(map[string]int)
s.F = make(chan int)
s.D = append(s.D, 1, 2, 3)
s.E["key"] = 42
go func() {
s.F <- 100
}()
fmt.Printf("%+v\n", s) // 输出:{A:0 B: C:false D:[1 2 3] E:map[key:42] F:0xc0000a2000}
fmt.Println(<-s.F) // 输出:100
// 结构体变量是值类型,同一结构体的不同变量之间是相互独立的
s1 := S{A: 10, B: "hello"}
s2 := s1 // 复制s1的值给s2,s1和s2是两个独立的变量
s2.A = 20 // 修改s2的字段A,不会影响s1的字段A
fmt.Printf("s1: %+v\n", s1) // 输出:s1: {A:10 B:hello C:false D:[] E:map[] F:chan int(nil)}
fmt.Printf("s2: %+v\n", s2) // 输出:s2: {A:20 B:hello C:false D:[] E:map[] F:chan int(nil)}
// 使用指针类型的字段,或者将结构体变量定义为指针类型,可以通过指针来共享数据
s3 := &s1 // 定义s3为指向s1的指针变量,s3和s1指向同一个结构体实例
s3.A = 30 // 修改s3的字段A,会影响s1的字段A,因为s3和s1指向同一个结构体实例
fmt.Printf("s1: %+v\n", s1) // 输出:s1: {A:30 B:hello C:false D:[] E:map[] F:chan int(nil)}
fmt.Printf("s3: %+v\n", s3) // 输出:s3: &{A:30 B:hello C:false D:[] E:map[] F:chan int(nil)}
}
五、创建结构体变量和访问结构体
创建方式
4和5方式返回的是结构体指针变量,1、2、3方式返回的是结构体变量
当使用结构体指针方式创建时,(*p).name和p.name是等价的,go允许直接使用p.name的语法糖来访问指针变量的字段
- 1、先声明后赋值
var 变量名 结构体类型
例如
var p Person
p.Name = "David"
p.Age = 28
p.sex = "male"
- 2、直接声明并初始化
var 变量名 结构体类型 = 结构体类型{字段1:值1, 字段2:值2, ...}
例如
var p Person = Person{Name: "Alice", Age: 30, sex: "female"}
p.Name = "David"
- 3、使用类型推断创建
变量名 := 结构体类型{字段1:值1, 字段2:值2, ...}
例如
p := Person{Name: "Bob", Age: 25, sex: "male"}
p.Name = "David"
- 4、创建结构体指针变量(这种方式返回的结构体指针)
var 变量名 *结构体类型 = &结构体类型{字段1:值1, 字段2:值2, ...}
例如
var p *Person = &Person{Name: "Charlie", Age: 35, sex: "male"}
p.Name = "David" // 也可以写成 (*p).Name = "David",但go允许直接使用p.Name的语法糖来访问指针变量的字段
- 5、使用new函数创建结构体指针变量(这种方式返回的结构体指针)
变量名 := new(结构体类型)
例如
// var p *Person = new(Person) // 等价如下。创建一个Person类型的结构体指针变量,字段会被自动初始化为零值
p := new(Person) // 创建一个Person类型的结构体指针变量,字段会被自动初始化为零值
p.Name = "David" // 也可以写成 (*p).Name = "David",但go允许直接使用p.Name的语法糖来访问指针变量的字段
p.Age = 28
p.sex = "male"
六、结构体类型的内存分配机制
七、结构体使用的注意事项
- 结构体中的字段在内存中是连续分布的,这使得结构体变量在内存中的访问效率较高
-
结构体变量是值类型,传递结构体变量会进行值复制,如果结构体较大或者需要共享数据,建议使用结构体指针变量
-
xxxx