10、go的面向对象

结构体

结构体的定义

// 定义结构体,老师的结构体,各个属性统一放入结构体管理
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
}

方法和函数的区别

  1. 绑定指定类型:
    • 方法:需要绑定指定数据类型
    • 函数:不需要绑定数据类型
  2. 调用方式不一样
    • 函数调用方式:函数名(实参列表)
    • 方法调用方式:变量.方法名(实参列表)
  3. 对于函数来说,参数类型对应是什么就要传入什么
  4. 对于方法来说,接收者为值类型,可以传入指针类型。
    接收者为指针类型,可以传入值类型
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里面的封装做了简化,尽量不要去和别的语言进行对比

另外在别的地方创建一个包,结构:
image
代码如下:

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}
}

继承

继承思想就是提高代码的复用性,把不同结构体的共同属性抽象出来,形成一个新的结构体,让其他结构体只要继承了,就具有这些结构体的属性和方法
image
代码:

// 定义动物的结构体
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() // 打印:我是胖虎,我要挠人
}

继承的注意事项

  1. 父类的结构体属性首字母不管大小写,都可以被子类结构体继承
    image
  2. 结构体和匿名结构体有相同字段或方法是,编译器采用就近访问原则,如果希望访问匿名结构体的字段和方法,通过匿名结构体的名来区分
// 定义动物的结构体
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
}
  1. 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}}
}
  1. 嵌入的匿名结构体有相同的字段或方法时,通过匿名结构体类型名来区分
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
}
  1. 结构体匿名字段可以是基本数据类型
    image
  2. 嵌套匿名结构体后创建结构体时,可以直接指定单个匿名结构体字段的值
    image
  3. 可以嵌入匿名结构体的指针
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}
}
  1. 结构体的字段可以是结构体类型的 (组合模式)
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) // 输出 你好
}

接口注意事项

  1. 只要是自定义数据类型,就可以实现接口,不是只有结构体类型才可以
// 自定义数据类型
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
}
  1. 一个自定义类型可以实现多个接口
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
}
  1. 一个接口(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
}
  1. interface默认是一个指针(引用类型),如果没有对interface初始化就是要,会输出nil
  2. 空接口可以接任何其他类型

多态

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)
}

打印:
image

posted @ 2022-11-25 15:59  aBiu--  阅读(11)  评论(0编辑  收藏  举报