结构体
结构体的定义
// 定义结构体,老师的结构体,各个属性统一放入结构体管理
type Teacher struct {
// 变量名大写开头外界可以访问
Name string
Age int
School string
}
func main() {
// 创建老师的实例对象
var t1 Teacher
fmt.Println(t1) // { 0 }
t1.Name = "王二狗"
t1.Age = 45
t1.School = "清华大学"
fmt.Println(t1) // {王二狗 45 清华大学}
// 第二种结构体实例创建
var t2 Teacher = Teacher{}
fmt.Println(t2) // { 0 }
// 第三种创建实例方法,使用指针
var t3 *Teacher = new(Teacher)
// t3是指针,其实指向的就是地址,应该给这个地址指向的对象字段赋值
t3.Name = "王彪" // 底层其实是(*t3).Name = "王彪", 只是为了复合正常程序员的写法
t3.Age = 28
fmt.Println(*t3) // {王彪 28 }
// 第四种方式
var t4 *Teacher = &Teacher{}
t4.Name = "二狗"
t4.Age = 12
t4.School = "家里蹲大学"
fmt.Println(*t4) // {二狗 12 家里蹲大学}
}
结构体的转换
type Student struct {
Age int
}
type Person struct {
Age int
}
func main() {
var s Student = Student{10}
var p Person = Person{10}
s = Student(p) // 强制转换
fmt.Println(s) // {10}
fmt.Println(p) // {10}
var s1 Student = Student{18}
var s2 Stu = Stu{19}
s1 = Student(s2) // 虽然是别名,一样要强制转换
fmt.Println(s1) // {19}
fmt.Println(s2) // {19}
}
// 给Student取了个别名
type Stu Student
方法
方法的引入
// 定义Persons结构体
type Persons struct {
Name string
}
// 给Persons解耦固体绑定方法test
func (p Persons) test() {
p.Name = "二狗子"
fmt.Println(p.Name) // 二狗子
}
func main() {
var p Persons
p.Name = "二狗"
p.test() // 调用了test()方法,方法转递是值传递
fmt.Println(p.Name) // 二狗
}
方法的注意点
通过上面的demo发现调用了test()方法后,Name属性没有改成二狗子,main方法中还是二狗,如果想让覆盖掉,test方法应该传入参数为指针,只要就会改变具体位置的值
// 定义Persons结构体
type Persons struct {
Name string
}
// 给Persons解耦固体绑定方法test,参数设置为指针类型
func (p *Persons) test() {
p.Name = "二狗子"
fmt.Println(p.Name) // 二狗子
}
func main() {
var p Persons
p.Name = "二狗"
p.test() // test()方法中给替换成了 二狗子
fmt.Println(p.Name) // 二狗子
}
go的方法作用在指定的数据类型上和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等,都可可以有方法
type integer int
func (i integer) print() {
i = 30
fmt.Println(i) // 30
}
func (i *integer) change() {
*i = 30
fmt.Println(*i) // 30
}
func main() {
var i integer = 20
i.print()
i.change()
fmt.Println(i) // 30
}
如果一个类型实现了String()这个方法,那么fmt.Println()会调用这个变量的String()方法进行输出
就好比java的toString()方法
type Students struct {
Name string
Age int
}
func (s *Students) String() string {
str := fmt.Sprintf("Name=%v, Age=%v", s.Name, s.Age)
return str
}
func main() {
stu := Students{
Name: "王彪",
Age: 18,
}
fmt.Println(&stu) // Name=王彪, Age=18
}
方法和函数的区别
- 绑定指定类型:
- 方法:需要绑定指定数据类型
- 函数:不需要绑定数据类型
- 调用方式不一样
- 函数调用方式:函数名(实参列表)
- 方法调用方式:变量.方法名(实参列表)
- 对于函数来说,参数类型对应是什么就要传入什么
- 对于方法来说,接收者为值类型,可以传入指针类型。
接收者为指针类型,可以传入值类型
type Studentss struct {
Name string
}
// 定义方法
func (s Studentss) test1() {
fmt.Println(s.Name)
}
// 定义函数
func method1(s Studentss) {
fmt.Println(s.Name)
}
func method2(s *Studentss) {
fmt.Println(s.Name)
}
func main() {
var s Studentss = Studentss{"二狗"}
// 调用函数
method1(s)
method2(&s)
// 调用方法
s.test1()
(&s).test1()
}
封装
go里面的封装做了简化,尽量不要去和别的语言进行对比
另外在别的地方创建一个包,结构:
代码如下:
package model
import "fmt"
type person struct {
Name string // 首字母大写,可以公开访问
age int // 首字母小写,其他包不能访问
}
// 定义工厂模式的函数,相当于构造器
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
// 定义set和get方法,对age字段进行封装,
// 因为方法中可以加一些列的限制操作,确保被封装字段的安全合理性
func (p *person) SetAge(age int) {
if age > 0 && age < 150 {
p.age = age
} else {
fmt.Println("年龄方位不正确")
}
}
func (p *person) GetAge() int {
return p.age
}
跳出到其他包下,体验一下封装
package main
import (
"fmt"
"unit10/model"
)
func main() {
// 创建person结构体实例
p := model.NewPerson("二狗")
p.SetAge(18)
fmt.Println(p.Name) // 二狗
// age属性是私有的,所以不能像Name一样直接调用,要使用对应的get方法获取属性
fmt.Println(p.GetAge()) // 18
fmt.Println(*p) // {二狗 18}
}
继承
继承思想就是提高代码的复用性,把不同结构体的共同属性抽象出来,形成一个新的结构体,让其他结构体只要继承了,就具有这些结构体的属性和方法
代码:
// 定义动物的结构体
type Animal struct {
Age int
Weight float32
}
// 给Animal绑定方法:喊叫
func (an *Animal) Shout() {
fmt.Println("大声喊叫")
}
// 给Animal绑定方法:展示自我信息
func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%v,体重:%v", an.Age, an.Weight)
fmt.Println()
}
// 定义结构体:Cat
type Cat struct {
// 为了复用性,体现继承思维,加入匿名结构体
Animal
}
// 对Cat绑定特有方法:挠人
func (c *Cat) scratch() {
fmt.Println("我是胖虎,我要挠人")
}
func main() {
// 创建Cat结构体
cat := &Cat{}
// cat结构体具有了Animal的属性
cat.Age = 3
cat.Weight = 13.5
// 调用继承的方法
cat.Shout() // 打印:大声喊叫
cat.ShowInfo() // 打印:动物的年龄:3,体重:13.5
// 调用cat自己的方法
cat.scratch() // 打印:我是胖虎,我要挠人
}
继承的注意事项
- 父类的结构体属性首字母不管大小写,都可以被子类结构体继承
- 结构体和匿名结构体有相同字段或方法是,编译器采用就近访问原则,如果希望访问匿名结构体的字段和方法,通过匿名结构体的名来区分
// 定义动物的结构体
type Animal struct {
age int
weight float32
}
// 给Animal绑定方法:喊叫
func (an *Animal) Shout() {
fmt.Println("大声喊叫")
}
// 给Animal绑定方法:展示自我信息
func (an *Animal) ShowInfo() {
fmt.Printf("动物的年龄:%v,体重:%v", an.age, an.weight)
fmt.Println()
}
// 定义结构体:Cat
type Cat struct {
// 为了复用性,体现继承思维,加入匿名结构体
Animal
Age int
}
// 给猫也绑定一个方法:展示自我信息
func (c *Cat) ShowInfo() {
fmt.Printf("~~~~~~~~动物的年龄:%v,体重:%v", c.age, c.weight)
fmt.Println()
}
// 对Cat绑定特有方法:挠人
func (c *Cat) scratch() {
fmt.Println("我是胖虎,我要挠人")
}
func main() {
// 创建Cat结构体
cat := &Cat{}
// cat结构体具有了Animal的属性
cat.age = 3
cat.weight = 13.5
// 调用继承的方法
cat.Shout() // 打印:大声喊叫
// 可以发现调用的是Cat结构体绑定的方法,不是Animal结构体绑定的方法
cat.ShowInfo() // 打印:~~~~~~~~动物的年龄:3,体重:13.5
// 调用cat自己的方法
cat.scratch() // 打印:我是胖虎,我要挠人
// 如果要调用Animal结构体绑定的方法,通过匿名结构体的名指引一下
cat.Animal.age = 5
cat.Animal.ShowInfo() // 打印:动物的年龄:5,体重:13.5
}
- go里面支持多继承,但不建议这样写
type A struct {
a int
b string
}
type B struct {
c int
d string
}
type C struct {
A
B
}
func main() {
// 构建C的结构体
c := C{A{10, "aaa"}, B{20, "bbb"}}
fmt.Println(c) // {{10 aaa} {20 bbb}}
}
- 嵌入的匿名结构体有相同的字段或方法时,通过匿名结构体类型名来区分
type A struct {
a int
b string
}
type B struct {
c int
d string
a int
}
type C struct {
A
B
}
func main() {
// 构建C的结构体
c := C{A{10, "aaa"}, B{20, "bbb", 50}}
//fmt.Println(c.a) // 这样不行,会报错,因为不知道调用哪个a了,A结构体和B结构体都有a属性
// 如果调用a,要指明调用哪个a的属性
fmt.Println(c.A.a) // 10
fmt.Println(c.B.a) // 50
}
- 结构体匿名字段可以是基本数据类型
- 嵌套匿名结构体后创建结构体时,可以直接指定单个匿名结构体字段的值
- 可以嵌入匿名结构体的指针
type A struct {
a int
b string
}
type B struct {
c int
d string
a int
}
type C struct {
*A
*B
int
}
func main() {
// 构建C的结构体
c := C{&A{10, "aaa"}, &B{20, "bbb", 50}, 888}
fmt.Println(c.int) // 888
fmt.Println(*c.A) // {10 aaa}
fmt.Println(*c.B) // {20 bbb 50}
}
- 结构体的字段可以是结构体类型的 (组合模式)
type B struct {
c int
d string
a int
}
type D struct {
a int
b string
c B // 组合模式
}
func main() {
d := D{10, "ooo", B{666, "ppp", 999}}
fmt.Println(d) // {10 ooo {666 ppp 999}}
fmt.Println(d.c.d) // ppp
}
接口
接口的引入
// 接口的定义:定义规则、定义规范, 定义某种能力:
type SayHello interface {
// 声明没有实现的方法
sayHello()
}
// 接口的实现:定义一个结构体
// 中国人
type Chinese struct {
}
// 实现接口的方法,具体的实现
func (chinese Chinese) sayHello() {
fmt.Println("你好")
}
// 接口的实现:定义一个结构体
// 美国人
type American struct {
}
// 实现接口的方法,具体的实现
func (american American) sayHello() {
fmt.Println("hi")
}
// 定义函数,用来接收各国人打招呼的函数,接收具备SayHello接口的能力的变量
func greet(s SayHello) {
s.sayHello()
}
func main() {
// 创建中国人
c := Chinese{}
// 创建美国人
a := American{}
// 美国人打招呼
greet(a) // 输出 hi
// 中国人打招呼
greet(c) // 输出 你好
}
接口注意事项
- 只要是自定义数据类型,就可以实现接口,不是只有结构体类型才可以
// 自定义数据类型
type integer int
func (i integer) sayHello() {
fmt.Println("say hi + ", i)
}
func main() {
var i integer = 10
var s SayHello = i
s.sayHello() // 打印: say hi + 10
}
- 一个自定义类型可以实现多个接口
type AInterface interface {
a()
}
type BInterface interface {
b()
}
type Stu struct {
}
func (s Stu) a() {
fmt.Println("aaa")
}
func (s Stu) b() {
fmt.Println("bbb")
}
func main() {
var s Stu
var a AInterface = s
var b BInterface = s
a.a() // 打印 aaa
b.b() // 打印 bbb
}
- 一个接口(A接口)可以继承多个别的接口(B接口、C接口),如果要实现A接口,就要把B接口和C接口的方法全都实现
type BInterface interface {
b()
}
type CInterface interface {
c()
}
type AInterface interface {
BInterface
CInterface
a()
}
type Stu struct {
}
func (s Stu) a() {
fmt.Println("a")
}
func (s Stu) b() {
fmt.Println("b")
}
func (s Stu) c() {
fmt.Println("c")
}
func main() {
var s Stu
var a AInterface = s
a.a() // 打印a
a.b() // 打印b
a.c() // 打印c
}
- interface默认是一个指针(引用类型),如果没有对interface初始化就是要,会输出nil
- 空接口可以接任何其他类型
多态
go语言中,多态特征是通过接口实现的,可以按照统一的接口来调用不同的实现,这时接口库变量就会呈现出不同的形态
// 接口的定义:定义规则、定义规范, 定义某种能力:
type SayHello interface {
// 声明没有实现的方法
sayHello()
}
// 接口的实现:定义一个结构体
// 中国人
type Chinese struct {
name string
}
// 实现接口的方法,具体的实现
func (chinese Chinese) sayHello() {
fmt.Println("你好")
}
// 接口的实现:定义一个结构体
// 美国人
type American struct {
name string
}
// 实现接口的方法,具体的实现
func (american American) sayHello() {
fmt.Println("hi")
}
func main() {
// 定义一个Sayhello接口数组,里面存放American、Chinese结构体变量
var arr [3]SayHello
arr[0] = American{"rose"}
arr[1] = Chinese{"二狗"}
arr[2] = Chinese{"黑狗"}
fmt.Println(arr) // [{rose} {二狗} {黑狗}]
}
断言
就是判断是否是什么类型
// 接口的定义:定义规则、定义规范, 定义某种能力:
type SayHello interface {
// 声明没有实现的方法
sayHello()
}
// 接口的实现:定义一个结构体
// 中国人
type Chinese struct {
name string
}
// 实现接口的方法,具体的实现
func (chinese Chinese) sayHello() {
fmt.Println("你好")
}
// 中国人有扭秧歌的方法
func (chinese Chinese) niuYangGe() {
fmt.Println("东北文化-扭秧歌")
}
// 接口的实现:定义一个结构体
// 美国人
type American struct {
name string
}
// 实现接口的方法,具体的实现
func (american American) sayHello() {
fmt.Println("hi")
}
// 美国人跳disco
func (american American) disco() {
fmt.Println("野狼disco")
}
// 定义函数,用来接收各国人打招呼的函数,接收具备SayHello接口的能力的变量
func greet(s SayHello) { // s可以通过上下文来识别具体是什么类型的实例,就体现出多态
s.sayHello()
// 断言: 看s是否可以转成Chinese类型,并赋给变量chinese
//chinese, flag := s.(Chinese)
//if flag {
// chinese.niuYangGe()
//} else {
// fmt.Println("美国人不会扭秧歌")
//}
// 第二种写法
//if chinese, flag := s.(Chinese); flag {
// chinese.niuYangGe()
//} else {
// fmt.Println("美国人不会扭秧歌")
//}
// 第三种写法,如果美国人结构体也有不同的方法
switch s.(type) {
case Chinese:
ch := s.(Chinese)
ch.niuYangGe()
case American:
am := s.(American)
am.disco()
}
}
func main() {
// 创建中国人
c := Chinese{}
a := American{}
greet(c)
greet(a)
}
打印: