面向对象
结构体
结构体和结构体变量的区别和联系
- 结构体是自定义的数据类型,代表一类事物
- 结构体变量(实例)是具体的,实际的,代表一个具体变量
结构体变量内存布局
type Cat struct{
Name string
Age int
Color string
Hobby string
}
var cat1 Cat
// 结构体是一个值类型
结构体声明
/* type 结构体名称 struct{
field1 type
field2 type
} */
// 例子
type Student struct {
Name string
Age int
Score float32
}
字段
- 结构体字段 == 属性 == field
- 字段是结构体的一个组成部分,一般是基本数据类型,数组,也可以是引用类型
- 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值,使用slice和map时必须先make
- 不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个
创建结构变量
// 方式一:直接声明
var person Person
// 方式二:类型推导
person := Person{
}
// 方式三:用new
var person *Person = new(Person)
// 因为person是一个指针,因此给字段赋值方式
person.Name = "smith"// 实质写法是(*person).Name = "smith"
// 方式四:用&符
var person *Person = &Person{
}
// 因为person是一个指针,因此给字段赋值方式
person.Name = "smith"// 实质写法(*person).Name = "smith"
内存分配机制
结构体使用注意事项
-
结构体中所有的字段在内存中是连续的
-
结构体使用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字,个数,类型)
-
结构体进行type重新定义(相当于取别名),Go认为时新的数据类型,但是相互间可以强转
type Student struct{ Name string Age int } type Stu Student
-
struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
抽象
将一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型
// Account.go
package main
import "fmt"
type Account struct {
AccountNo string
Pwd string
Banlance float64
}
func (account *Account) Deposite(money float64, pwd string) {
if pwd != account.Pwd {
fmt.Println("密码错误!")
return
}
if money <= 0 {
fmt.Println("输入的金额错误")
return
}
account.Banlance += money
}
func (account *Account) WithDraw(money float64, pwd string) {
if pwd != account.Pwd {
fmt.Println("密码错误!")
return
}
if money <= 0 || money > account.Banlance {
fmt.Println("输入的金额错误")
return
}
account.Banlance -= money
}
func (account *Account) Query(pwd string) {
if pwd != account.Pwd {
fmt.Println("密码错误!")
return
}
fmt.Printf("你的账号为%v,余额%v\n", account.AccountNo, account.Banlance)
}
func main() {
// 测试
account := Account{
AccountNo: "yfq111111",
Pwd: "666666",
Banlance: 100,
}
account.Query("666666")
account.Deposite(100, "666666")
account.WithDraw(50, "666666")
account.Query("666666")
}
封装
把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作
好处
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包实现封装
实现步骤
- 将结构体,字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)
- 将结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
- 提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值
- 提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值
// personal.go
package model
import "fmt"
type person struct {
Name string
age int
sal float64
}
// 工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
// 为了访问age和sal,编写一堆SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
if age > 0 && age < 150 {
p.age = age
} else {
fmt.Println("年龄范围不正确..")
}
}
func (p *person) GetAge(age int) int {
return p.age
}
继承
继承可以解决代码复用,提高了代码的扩展性和维护性,让编程更加靠近人类思维。Go是利用匿名结构体来实现继承的。
语法
type Goods struct{
Name string
Price int
}
type Book struct{
Goods // 这里就是嵌套匿名结构体Goods
Writer string
}
细节
- 结构体可以使用嵌套匿名结构体所有的字段和方法
- 匿名结构体字段访问可以简化
- 当结构体和匿名结构体有相同的字段或者方法是,编译器采用就近访问原则访问,,如希望访问匿名结构体的字段和方法,可以通过匿名机构提名来区分
- 结构体嵌入两个或多个匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字
- 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方式时,必须带上结构体的名字
- 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
多重继承
如果一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承
- 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分
- 为了保证代码的简洁性,建议不要使用多重继承