首页 > 其他分享 >Go05-结构体+方法+面向对象三大特性+接口

Go05-结构体+方法+面向对象三大特性+接口

时间:2024-03-18 17:35:58浏览次数:14  
标签:Person fmt Go05 var 面向对象 Student Println type 三大

Go05-结构体+方法+面向对象三大特性+接口

1.Go面向对象

  1. Go支持面向对象编程,但是和传统的面向对象编程有区别,并不是纯粹的面向对象编程语言,所以说Go支持面向对象编程特性是比较准确的。
  2. Go中没有class类,Go中的结构体struct类似于其他编程中的类,即Go通过结构体struct实现面向对象编程。
  3. Go面向对象编程非常的简洁,去掉了传统面向对象语言的继承、方法重载、构造函数、析构函数、隐藏this指针等等。
  4. Go仍然有面向对象编程中继承、封装和多态的特性,只是实现的方式和传统的面向对象语言不一样。比如:Go没有extends关键字,继承通过匿名字段类实现的。
  5. Go面向对象很优雅,通过接口interface关联,耦合性低,也非常的灵活,即Go中面向接口编程是非常重要的特性。

2.结构体

func main()  {
	// 1 结构体的声明。结构体声明语法。
	/*
	type 结构体名 struct {
		// 字段。
	}
	 */
	type Cat struct {
		Name string
		Age int
	}

	// 2 结构体中的属性也称为字段,是结构体的一个组成部分,一般是基本数据类型和引用类型。
	// 3 当创建结构体变量后,如果没有给结构体中的字段赋值,则结构体中的字段是默认值。
	// 如bool是false、 int和float系列是0,string是空串"",数组类型的默认值和它的类型有关,
	// 如a [3]int,默认值为[0, 0, 0]。指针、切片slice、map的默认值,也称为零值是nil,
    // 表示没有分配内存空间。
	type Person struct {
		Name string
		Age int
		Scores [3]int
		p *int
		slice []int
		m map[string]string
	}

	// 定义结构体变量。
	var p Person
	fmt.Println(p.Scores) // [0 0 0]
	// ok1
	if p.p == nil {
		fmt.Println("ok1")
	}
	// ok2
	if p.slice == nil {
		fmt.Println("ok2")
	}
	// ok3
	if p.m == nil {
		fmt.Println("ok3")
	}

	// 4 结构体是值类型,如果直接传递的是结构体,函数中的修改不会影响到原值,
	// 所以需要传递结构体的指针。
	var d Dog
	d.Name = "tom"
	d.Age = 10
	fmt.Println(d) // {tom 10}
	test01(d)
	fmt.Println(d) // {tom 10}
}
type Dog struct {
	Name string
	Age int
}

func test01(d Dog)  {
	d.Name = "bob"
}

3.结构体的创建和访问

// 1 创建结构体变量的四种方式。
type Person struct {
    Name string
    Age int
}

// 1.1 直接声明结构体变量。声明时就会给结构体分配空间,初始化结构体字段为默认值。
var p1 Person
fmt.Println(p1) // { 0}

// 1.2 使用{}创建结构体变量。
var p2 Person = Person{}
fmt.Println(p2) // { 0}

// 创建并给结构体字段赋值。
var p3 Person = Person{"tom", 10}
fmt.Println(p3) // {tom 10}

// 1.3 使用new创建结构体。new用于给值类型分配空间,make用于给引用类型分配空间。
// new返回创建空间的地址,需要使用指针指向这个空间。
var p4 *Person = new(Person)
(*p4).Name = "tom"
// p4是一个指针,因此标准的访问字段的方式为(*p4).Age,但是Go设计者为了程序员更
// 加方便的访问结构体指针的字段,在底层对p4.Age进行处理,会加上取值运算,(*p4).Age,
// 所以可以直接使用p4.Age访问结构体Person中的Age字段。
p4.Age = 10
fmt.Println(p4) // &{tom 10}

// 1.4 使用{}结合取地址符&创建结构体指针变量。
var p5 *Person = &Person{}
(*p5).Name = "bob"
p5.Age = 20
fmt.Println(p5) // &{bob 20}

// 使用{}结合取地址符&创建结构体指针变量并赋值。
var p6 *Person = &Person{"tom", 1}
fmt.Println(p6) // &{tom 1}

4.结构体内存结构体

// 1 通过{}创建结构体并赋值。
type Person struct {
    Name string
    Age int
}

var p1 = Person{"tom", 10}
// 结构体属于值类型,在赋值是p2会创建单独的空间。
var p2 Person = p1
p2.Name = "alice"
fmt.Println(p1) // {tom 10}
fmt.Println(p2) // {alice 10}

// 2 通过指针操作结构体。p4的值为p3的地址。
var p3 Person = Person{"bob", 10}
var p4 *Person = &p3
fmt.Printf("%p\n", &p3) // 0xc0000040a8
fmt.Printf("%p %v\n", p4, &p4) // 0xc0000040a8 0xc000006030

5.结构体的其他操作

  1. 结构体中的字段在内存中是连续的。
type Person struct {
    a1 int
    a2 int
    a3 int
}
var p1 Person
// 可以看到买个字段占用了8个字节。0xc000012138表示十六进制,加8之后就是0xc000012140
// 0xc000012138 0xc000012140 0xc000012148
fmt.Printf("%p %p %p\n", &p1.a1, &p1.a2, &p1.a3)
  1. 结构体是用户自定义的类型,结构体和其他类型进行转换时,需要有完成相同的字段(字段的名称、个数、类型)才能进行转换。
type A struct {
    Num int
}
type B struct {
    Num int
}
type C struct {
    Number int
}
var a A
var b B
b.Num = 10
a = A(b)
fmt.Println(a) // {10}

var c C
c.Number = 100
// A和C的字段名不同,报错cannot convert c (variable of type C) to type A
// a = A(c)
fmt.Println(c) // {100}
  1. 使用type取别名后,Go会认为是新类型,不能进行直接复制,需要类型转换。
type Student struct {
    Name string
    Age int
}
// 使用type取别名,Go会认为stu是新类型。
type stu Student
var s1 Student
s1.Name = "a"
var s2 stu
fmt.Println(s2)

// Student和Stu直接的赋值,报错 cannot use s1 (variable of type Student) as type stu in assignment
// s2 = s1

// 将Student类型转换为为stu是可以的。
s2 = stu(s1)
fmt.Println(s2) // {a 0}

// 使用type重新定义基本类型也是同样的,不能直接赋值,但是可以强转。
type integer int
var i1 int = 10
var i2 integer = 100
// 报错 cannot use i1 (variable of type int) as type integer in assignment
// i2 = i1

i2 = integer(i1)
fmt.Println(i2) // 10
  1. 结构体的每个字段上可以写一个tag,tag可以使用反射获取,常见的使用场景是序列化和反序列化。
type Cat struct {
    Name string `json:"name"` // json:"name"就是struct的tag。
    Age int `json:"age"`
}

c1 := Cat{"a", 10}
fmt.Println(c1)
jsonStr, err := json.Marshal(c1)
if err != nil {
    fmt.Println("json转换错误!")
}
// {"name":"a","age":10}
fmt.Println(string(jsonStr))

6.方法

func main() {
	// 1 Go中的方法作用在指定的数据类型上,即和指定的数据类型绑定,因此自定义
	// 类型都可以有方法,而不仅仅是struct。

	// 2 方法的声明。
	/*
	func (a A) test() {} // 表示结构体A有一个方法,名为test。
	 */

	var p1 Person = Person{"tom", 10}
	// 3 run方法只能通过Person类型的变量调用,不能直接调用,也不能使用其他类型的变量调用。
	p1.run() // tom正在跑

	var p2 Person = Person{"alice", 20}
	// 4 p2调用了updateName()方法,会将p2副本的拷贝,传递给updateName(),
	// 即updateName()中对p2的副本进行了修改,也不会影响到这里的p2。
	// 方法调用的传参和函数传参是一样的,因为p2是结构体,结构体是值类型所以
	// 会进行p2副本的拷贝;如果p2是引用类型,则进行p2地址的拷贝。
	p2.updateName()
	fmt.Println(p2) // {alice 20}
}

type Person struct {
	Name string
	Age int
}

// 4 p Person表示调用这个方法的变量,是调用方法这的副本。这点和函数传参很想,
// 即函数中对p的修改,不会影像原值。
func (p Person) updateName() {
	p.Name = "bob"
}

func (p Person) run() {
	fmt.Printf("%v正在跑\n", p.Name)
}

7.方法的其他操作

func main() {
	// 1 结构体是值类型,在方法调用时遵循值类型的传递机制,是值拷贝的传递方式。
	// 2 如果想在方法中修改结构体变量的值,可以通过结构体指针的方式处理。
	var p1 Person = Person{"bob", 10}
	fmt.Println(p1) // {bob 10}

	// 3 如果方法是(p *Person),则调用时需要是(&p1).updateName(),但是编译器在底层
	// 进行了优化,所以这里可以使用p1.updateName()调用。即编译器在底层会将p1.updateName()
	//翻译为(&p1).updateName(),所以这里可以直接使用。
	p1.updateName()
	fmt.Println(p1) // {tom 10}

	// 4 Go中的方法作用在指定数据类型上,因此自定义类型都可以有方法。不仅struct可以有方法,
	// int、float的自定类型都可以有方法。
	// 给int的自定义类型integer添加方法。
	var a integer = 10
	a.test01() // a = 10

	// 5 方法的访问权限控制和函数的一样,首字母小写只能在本包中访问,首字母大写可以在本包和其他包中访问。

	// 6 如果一个类型实现了String()方法,那么fmt.Println默认会调用这个变量的String()方法进行输出。
	var s Student = Student{"tom"}
	fmt.Println(s) // {tom}
	// String()在实现时,参数是s Student,所以fmt.Println()的参数需要是&s
	fmt.Println(&s) // 学生名是:tom
}

type Student struct {
	Name string
}

func (s *Student) String() string {
	str := fmt.Sprint("学生名是:", s.Name)
	return str
}

type integer int
func (a integer) test01() {
	fmt.Println("a =", a)
}

type Person struct {
	Name string
	Age int
}

func (p *Person) updateName() {
	p.Name = "tom"
}

8.方法和函数的区别

func main() {
	// 1 调用方式不同。函数的调用方式:函数名(参数列表);
	// 方法的调用方式:变量.方法名(参数列表)。

	// 2 对于普通函数,接受者为值类型,则不能传递指针类型;
	// 接受者为指针类型,则不能传递值类型。
	p1 := Person{"tom", 12}
	p2 := &Person{"alice", 22}
	test01(p1) // {tom 12}
	test02(p2) // &{alice 22}

	// 3 对于方法,接受者为值类型时,可以使用指针类型调用方法;
	// 接受者为指针类型时,可以使用值类型调用方法。
	p3 := Person{"张三", 22}
	p4 := &Person{"李四", 32}

	p3.Demo01() // {张三 22}
	p3.Demo02() // &{张三 22}

	p4.Demo01() // {李四 32}
	p4.Demo02() // &{李四 32}
}

type Person struct {
	Name string
	Age int
}

func (p Person) Demo01() {
	fmt.Println(p)
}

func (p *Person) Demo02() {
	fmt.Println(p)
}

func test01(p Person)  {
	fmt.Println(p)
}

func test02(p *Person) {
	fmt.Println(p)
}

9.创建结构体变量时指定字段值

type Person struct {
    Name string
    Age int
}
// 1 创建结构体变量时指定字段的值。
var p1 Person = Person{"tom", 20}
fmt.Println(p1) // {tom 20}

// 2 创建结构体变量时,将字段名和字段值写在一起,
// 这种写法字段值不依赖结构体中字段的定义顺序。
p2 := Person{
    Age: 20,
    Name: "alice",
}
fmt.Println(p2) // {alice 20}

// 3 使用结构体指针结构返回的对象。
var p3 *Person = &Person{"bob", 30}
fmt.Println(p3) // &{bob 30}

// 4 在使用结构体指针时,使用字段名和字段值的方式设置字段的值,
// 不依赖结构体中字段的定义顺序。
p4 := &Person{
    Age: 40,
    Name: "jerry",
}
fmt.Println(p4) // &{jerry 40}

10.工厂模式

  1. Car、Person、Student结构体。
type car struct {
	color string
}

// 结构体首字母小写的处理方式。
func NewCar(c string) *car {
	return &car{
		color: c,
	}
}

// 结构体中字段首字母小写的处理方式。
func (c *car) GetColor() string {
	return c.color
}
type person struct {
	Address string
	Number int
}

func NewPerson(address string, number int) *person {
	return &person{
		Address: address,
		Number: number,
	}
}
type Student struct {
	Name string
	Age int
}
  1. 工厂模式创建结构体。
// 1 当model包中结构体首字母大写时,直接应用。
var s1 model.Student = model.Student{Name: "tom", Age: 10}
fmt.Println(s1)

// 2 当model包中结构体首字母小写时,使用工厂模式创建对象。
var p1 = model.NewPerson("A01", 10)
fmt.Println(p1)

// 3 当model包中结构体字段首字母小写时,结构体中字段首字母小写的处理方式。
var c1 = model.NewCar("t")
fmt.Println(c1.GetColor())

11.面向对象编程三大特性-封装

  1. 面向对象特性-封装提现在结构体和结构体中字段小写,让其他包不能直接访问结构体和结构体字段。然后通过工厂模式对外暴露创建结构体的方法;提供首字母大写的Set方法,用于对结构体字段进行赋值;提供首字母大写的Get方法,用于获取结构体字段。
  2. Go中没有特别强调封装,Go本身对面对对象的特性进行了简化。即不强制要求结构体和结构体字段首字母小写。

12.面向对象编程三大特性-继承

func main() {
	// 1 在Go中,如果struct结构体中嵌套了另一个匿名结构体,那么这个结构体中
	// 可以直接访问匿名结构体中的字段和方法。这种方式就是Go中的继承。

	// 2 结构体可以使用嵌套的匿名结构体所有的字段和方法。
	var s1 Student = Student{}
	s1.Person.Name = "tom"
	s1.Person.age = 10
	s1.ClassNo = 10
	s1.TeacherName = "bob"
	fmt.Println(s1) // {{tom 10} 10 bob}
	fmt.Println(s1.Person) // {tom 10}

	// 3 匿名结构体中的字段和方法的访问可以被简化。
	// s2.Name的访问流程:编译器先去结构体Student中寻找是否存在Name字段,存在就直接使用;
	// 不存在就继续看Student结构体中的匿名结构体Person中是否存在Name字段,存在就直接使用;
	// 不存在就继续往下查找,没有找到就报错。
	var s2 = Student{}
	s2.Name = "alice"
	fmt.Println(s2) // {{alice 0} 0 }

	// 4 当结构体和匿名结构体中有相同的字段或方法时,编译器采用就近原则访问。如果希望访问匿名
	// 结构体中的字段,则可以使用匿名结构体访问。
	var s3 = Student{}
	s3.Name = "sir"
	// 就近原则访问的是Student的方法。
	s3.Print() // Student sir
	// 如果想访问Person的Print则可以使用s3.Person.Print()的方式访问。
	s3.Person.Print() // Person sir

	// 5 如果结构体中嵌入了两个或者多个结构体,并且两个匿名结构体中有相同的字段和方法,
	// 在访问时就必须指定匿名结构体的名字,否则编译报错。
	type Parent1 struct {
		Name string
	}
	type Parent2 struct {
		Name string
	}
	type Child struct {
		Parent1
		Parent2
	}
	var c1 = Child{}
	// 结构体Child有两个匿名结构体,并且这两个匿名结构体中有同名的字段Name,
	// 则访问时需要指定结构体后在访问。
	// 报错:ambiguous selector c1.Name
	// c1.Name = "tom"

	// 结构体Child有两个匿名结构体,并且这两个匿名结构体中有同名的字段Name,
	// 则需要指定具体访问的匿名字段。
	c1.Parent1.Name = "tom"
	c1.Parent2.Name = "alice"
	fmt.Println(c1) // {{tom} {alice}}

	// 6 如果struct嵌套了一个有名结构体,这种模式称为组合。在访问组合关系
	// 的结构体的字段和方法时,必须带上结构体的名字。
	type Color struct {
		Name string
	}
	type Cat struct {
		Color Color
	}
	var cat = Cat{}
	// struct中嵌套的是有名结构体,这个嵌套称为组合模式,在访问组合结构体的字段
	// 和方法时,必须带上结构体的名字。
	cat.Color.Name = "red"
	fmt.Println(cat) // {{red}}

	// 7 嵌套匿名结构体后,在创建结构体变量实例时,可以直接指定各个匿名结构体字段的值。
	type Animal struct {
		Name string
		Age int
	}
	type Dog struct {
		Animal
		Color string
	}
	var dog = Dog{Animal{"tom", 10}, "red"}
	fmt.Println(dog) // {{tom 10} red}

	// 8 如果结构体的匿名字段的类型是基本数据类型,则结构体中只能有一个相同基本类型的字段。
	type Pig struct {
		int
		Name string
		Age int
	}
	var p1 = Pig{}
	p1.int = 10
	p1.Name = "tom"
	p1.Age = 20
	fmt.Println(p1) // {10 tom 20}

	// 9 一个结构体中嵌套了两个或者多个结构体,这种模式也成为多重继承。案例为5中Parent1、
	// Parent2和Child的案例。
	// 9.1 当嵌入的多个匿名结构体中有相同的字段名或者方法名,访问时就需要通过匿名结构体类型名来区分。
	// 9.2 为了保证代码的简洁性,不建议使用多重继承。
}

type Person struct {
	Name string
	age int
}

func (p *Person) Print() {
	fmt.Println("Person", p.Name)
}

type Student struct {
	Person // 结构体中嵌套匿名结构体,则当前结构体可以访问匿名结构体的字段和方法。
	ClassNo int
	TeacherName string
}

func (s *Student) Print() {
	fmt.Println("Student", s.Name)
}

13.接口的声明和定义

func main() {
	// 1 接口的定义语法。
	/*
	type 接口名 interface {
		Start() // 声明没有实现的方法。
	}
	 */
	cat := Cat{}
	dog := Dog{}
	manager := Manager{}
	// Cat Eat - Cat Run
	manager.Do(cat)
	// Dog Eat - Dog Run
	manager.Do(dog)

	// 2 接口类型可以定义一组方法,这一组方法不需要实现,并且接口不能包含任何变量。
	// 3 接口提现了程序设计的多态和高内聚低耦合的特性。
	// 4 Go中的接口不需要显示的实现,只需要一个变量,这个变量含有接口类型中的所有方法,
	// 那么这变量就实现了接口。因此,Go中没有implement关键字。
}

// 声明定义一个接口。
type Animal interface {
	Eat()
	Run()
}


type Cat struct {

}

// Cat实现Animal的Eat()方法
func (p Cat) Eat() {
	fmt.Println("Cat Eat")
}

// Cat实现Animal的Run()方法。
func (p Cat) Run() {
	fmt.Println("Cat Run")
}

type Dog struct {

}

// Dog实现Animal的Eat()方法
func (p Dog) Eat() {
	fmt.Println("Dog Eat")
}

// Dog实现Animal的Run()方法。
func (p Dog) Run() {
	fmt.Println("Dog Run")
}

type Manager struct {

}

// Do()方法接受Animal接口类型的变量,实现了Animal接口的所有方法的结构体,
// 都可以通过Do()方法的参数传入。
func (m Manager) Do(a Animal)  {
	a.Eat()
	a.Run()
}

14.接口的注意事项和使用细节

func main() {
	// 1 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。
	smallStudent := SmallStudent{}
	bigStudent := BigStudent{}

	// 接口变量指向实现了该接口的实现类(实例)
	var s1 Student = smallStudent
	var s2 Student = bigStudent
	fmt.Println(s1) // {}
	fmt.Println(s2) // {}

	// 2 Go中接口中所有的方法都没有方法体,即都是没有实现的方法。
	// 3 Go中一个自定义类型只有将某个接口的所有方法都实现了,我们才能说这个自定义类型实现了该接口。
	// 4 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例赋值给接口类型。
	// 5 只要是自定义类型都可以实现接口,不仅仅是结构体。
	var i integer = 10
	i.GoSchool() // integer Go School

	// 6 一个自定义类型可以实现多个接口。
	// 7 接口中只有方法的定义,不能有变量的定义。

	// 8 一个接口(比如接口A)可以继承多个别的接口(B、C接口),这时如果要实现A接口,
	// 也必须将B、C接口的全部方法实现。
	var a AInterface
	// Person实现了AInterface,则需要将AInterface继承的BInterface和BInterface的
	// 所有方法都实现了,否则无法通过a = Person{}进行赋值。
	a = Person{}
	a.testA() // Person TestA
	a.testB() // Person TestB
	a.testC() // Person TestC

	// 9 interface接口默认是一个指针类型,即接口是引用类型,如果没有对接口初始化,则会输出nil。
	var b BInterface
	fmt.Println(b) // <nil>

	// 10 空接口interface{}没有任何方法,所有类型都实现了空接口,即可以将任何类型赋给空接口。
	// 任何类型都可以赋值给空接口。
	var t1 T = Teacher{}
	fmt.Println(t1) // {}

	// 任何类型赋值给空接口的第二种方式。
	var t2 interface{} = Teacher{}
	fmt.Println(t2) // {}

	var t3 interface{} = 10
	fmt.Println(t3) // 10
}

// 1的说明。
type Student interface {
	GoSchool()
}

type SmallStudent struct {

}

func (s SmallStudent) GoSchool() {
	fmt.Println("Small Student Go School")
}
type BigStudent struct {

}

func (b BigStudent) GoSchool() {
	fmt.Println("Big Student Go School")
}

// 5的说明。
type integer int

// 自定义类型integer也可以实现Student接口的方法。
func (i integer) GoSchool() {
	fmt.Println("integer Go School")
}

// 8的说明。
type BInterface interface {
	testB()
}

type CInterface interface {
	testC()
}

// A接口继承B接口和C接口。
type AInterface interface {
	BInterface
	CInterface
	testA()
}

type Person struct {

}

func (p Person) testA() {
	fmt.Println("Person TestA")
}

func (p Person) testB() {
	fmt.Println("Person TestB")
}

func (p Person) testC() {
	fmt.Println("Person TestC")
}

// 10的说明。
// 定义一个空接口。
type T interface {}
type Teacher struct {
	Name string
}

15.两个接口中有同名方法和通过指针实现接口

func main() {
	// 1 Student实现的接口AInterface和BInterface可以有同名的方法,
	// 如果有同名的方法,则方法的返回值和参数列表也必须相同。因为Go
	// 中不支持方法的重载。
	s := Student{}
	var a AInterface = s
	var b BInterface = s
	fmt.Println(a, b) // {} {}

	// 2 实现方法时通过指针完成。
	var c Cat = Cat{}
	// 如果Cat实现Animal的Eat()方式时通过指针接受Cat参数,则不能直接赋值,
	// 否则报错
	/*
		cannot use c (variable of type Cat) as type Animal in variable declaration:
	Cat does not implement Animal (Eat method has pointer receiver)
	 */
	// var animal Animal = c

	// 如果Cat实现Animal的Eat()方式时通过指针接受Cat参数,则需要通过指针赋值。
	var animal Animal = &c
	fmt.Println(animal) // &{}
}

// 1的说明。
type AInterface interface {
	Test01()
	Test02()
}

type BInterface interface {
	Test01()
	Test03()
}

type Student struct {

}

func (s Student) Test01() {
	fmt.Println("Student Test01")
}

func (s Student) Test02() {
	fmt.Println("Student Test02")
}

func (s Student) Test03() {
	fmt.Println("Student Test03")
}

// 2的说明。
type Animal interface {
	Eat()
}

type Cat struct {

}

func (c *Cat) Eat() {
	fmt.Println("Cat Eat")
}

16.排序接口

func main() {
	// 1 声明Student结构体,然后对Student按照字段Score进行排序。
	s1 := Student{"tom", 30}
	s2 := Student{"alice", 20}
	s3 := Student{"bob", 60}
	s4 := Student{"jerry", 50}
	s5 := Student{"sim", 10}

	// var s []Student = []Student{s1, s2, s3, s4, s5}
	// 报错。
	/*
	cannot use s (variable of type []Student) as type sort.Interface in argument to sort.Sort:
	[]Student does not implement sort.Interface (missing Len method)
	 */
	// sort.Sort()的参数是一个接口,这个接口有三个空方法,Len()、Less()和Swap,所以[]Student
	// 切片需要实现这三个方法后才能进行排序。
	// sort.Sort(s)

	// 2 对Student切片进行排序。
	s := StudentSlice{s1, s2, s3, s4, s5}
	// [{tom 30} {alice 20} {bob 60} {jerry 50} {sim 10}]
	fmt.Println(s)
	sort.Sort(s)
	fmt.Println(s) // [{bob 60} {jerry 50} {tom 30} {alice 20} {sim 10}]
}

// 1.1 声明Student结构体。
type Student struct {
	Name string
	Score int
}

// 1.2 自定义Student类型切片。
type StudentSlice []Student

// 1.3 Student切片实现Len(),返回切片的长度。
func (s StudentSlice) Len() int {
	return len(s)
}

// s[i].Score > s[j].Score,按照分数从大到小排序;
// s[i].Score < s[j].Score,按照分数从小到大排序。
// 1.4 Student切片实现Less()。返回切片索引i、j处值的大小,
// 即 s[i] > s[j],返回true,否则返回false。
func (s StudentSlice) Less(i, j int) bool {
	return s[i].Score > s[j].Score
}

// 1.5 Student切片实现Swap(),交互索引i、j的值。
func (s StudentSlice) Swap(i, j int) {
	//temp := s[i]
	//s[i] = s[j]
	//s[j] = temp

	// 位置交互的简单写法。
	s[i], s[j] = s[j], s[i]
}

17.继承和接口的比较

  1. 在继承关系中,子类继承父类结构体的字段和方法,解决代码的复用性和可维护性。
  2. 当结构体需要扩展某个功能,同时又不想破坏继承关系,则可以去实现某个接口,可以理解为实现接口是对继承的一种补偿。
  3. 接口比继承更加的灵活,继承满足is-a的关系,接口只满足like-a的关系。
  4. 接口在一定程度上可以实现代码的解耦。

18.面向编程三大特性-多态

func main() {
	// 1 多态是面向对象编程的第三大特征,多态表示变量有多种形态。
	// 2 Go中多态是通过接口实现的,可以通过统一的接口调用不同的实现,
	// 这是接口变量就呈现出不同的形态。

	// 3 接口提现多态的两种形式-多态参数。
	var a1 Animal = Cat{}
	var a2 Animal = Dog{}
	test01(a1) // Cat Eat
	test01(a2) // Dog Eat

	// 4 接口提现多态的两种形式-多态数组。
	// 定义Animal接口数组,可以存放Cat和Dog的结构体变量,这就提现出多态数组。
	var a3 [2]Animal
	a3[0] = Cat{}
	a3[1] = Dog{}
	fmt.Println(a3) // [{} {}]
}

// 3的说明。
type Animal interface {
	Eat()
}

type Cat struct {
}

type Dog struct {
}

func (c Cat) Eat()  {
	fmt.Println("Cat Eat")
}

func (d Dog) Eat() {
	fmt.Println("Dog Eat")
}

func test01(a Animal) {
	a.Eat()
}

19.类型断言

  1. 类型断言。
func main() {
	// 1 由于接口是一般类型,不知道具体的类型,如果需要将接口转换为具体的类型,
	// 就需要使用类型断言。
	var a1 Animal
	var d1 Dog = Dog{}
	// 实例可以直接赋值给接口。
	a1 = d1
	fmt.Println(a1) // {}

	var a2 Animal = Dog{}
	var d2 Dog
	// 将接口赋值给实例变量会报错,此时就需要使用类型断言。
	/*
	cannot use a2 (variable of type Animal) as type Dog in assignment:
	need type assertion
	 */
	// d2 = a2

	// d2 = a2.(Dog)就是类型断言,表示a2是否是Dog类型,如果是就转换为Dog类型,
	// 否则就报错。
	d2 = a2.(Dog)
	fmt.Println(d2) // {}

	// 2 类型断言时如果类型不是断言的类型就会报panic。
	var x interface{}
	var y float64 = 1.1
	x = y
	z := x.(float64)
	// z类型为float64,z值为1.1
	fmt.Printf("z类型为%T,z值为%v\n", z, z)

	// 在进行类型断言时,如果类型不匹配,就会报panic,阻断后续代码的执行,
	// 所以需要确保变量实际指向的就是断言的类型。
	// e := x.(int)
	// fmt.Println(e)

	// 3 在类型断言时带上检查机制,防止出现panic,阻断程序的执行。
	// 利用类型断言返回的第二个参数,这时即使类型断言失败,也不好返回panic。
	// f为int的默认值0,ok为false。
	f, ok := x.(int)
	fmt.Println(f, ok) // 0 false

	// 使用_忽略类型断言的返回的第二个参数,也不会出现错误。
	g, _ := x.(int)
	fmt.Println(g) // 0

	// 类型断言结合if处理panic。
	// 类型不匹配
	if m, n := x.(int); n {
		fmt.Println("success", m)
	} else {
		fmt.Println("类型不匹配")
	}
}

type Animal interface {
	Eat()
}

type Dog struct {

}

func (d Dog) Eat() {
	fmt.Println("Dog Eat")
}
  1. 类型断言练习。
func main() {
	// 1 通过类型断言来调用Cat特有方法Run。
	var a Animal = Cat{}
	// 通过类型断言判断变量a是Cat的实例,然后再调用Cat的特有方法Run(),否则不调用。
	if c, ok := a.(Cat); ok {
		c.Run() // Cat Run
	}

	// 2 通过类型断言判断函数输入参数的类型。
	a1 := 10
	a2 := 10.10
	a3 := false
	a4 := "hello"
	a5 := Cat{}
	a6 := &Cat{}
	var a7 interface{}
	/*
	第0个参数类型为int,值为10
	第1个参数类型为float64,值为10.1
	第2个参数类型为bool,值为false
	第3个参数类型为string,值为hello
	第4个参数类型为main.Cat,值为{}
	第5个参数类型为*main.Cat,值为&{}
	第6个参数类型不确定
	 */
	test01(a1, a2, a3, a4, a5, a6, a7)
}

// 1的说明。
type Animal interface {
	Eat()
}

type Cat struct {

}

func (c Cat) Eat() {
	fmt.Println("Cat Eat")
}

func (c Cat) Run() {
	fmt.Println("Cat Run")
}

// 2的说明。
// 函数的入参是空接口interface{},可以接受任何类型的参数。
func test01(a... interface{})  {
	for index, value := range a {
		switch value.(type) {
		case bool:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		case int, int8, int16, int32:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		case float32, float64:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		case string:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		case Animal:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		case *Animal:
			fmt.Printf("第%v个参数类型为%T,值为%v\n", index, value, value)
		default:
			fmt.Printf("第%v个参数类型不确定\n", index)
		}
	}
}

标签:Person,fmt,Go05,var,面向对象,Student,Println,type,三大
From: https://www.cnblogs.com/godistance/p/17258516.html

相关文章

  • python 面向对象(三)magic methods
    magicmethods就是双下划线方法AsaPythondeveloperwhowantstoharnessthepowerofobject-orientedprogramming,you’lllovetolearnhowtocustomizeyourclassesusingspecialmethods,alsoknownasmagicmethodsordundermethods.Aspecialmethodisa......
  • MySQL补充:数据库的三大范式
    什么是范式?范式是数据库设计时遵循的一种规范,不同的规范要求遵循不同的范式。每个范式,都是用来规定某种结构或数据要求——后一范式都是在前一范式已经满足的情况用来“加强要求”最常用的三大范式第一范式(1NF):属性不可分割,即每个属性都是不可分割的原子项。(实体的属性即表中......
  • 采用设备认证、通信安全、系统安全三大机制,全方位为业务出海保驾护航
    中国联通推出了创新的跨境物联网产品解决方案,通过一卡双IMSI技术实现无感知切换,为企业海外业务提供了一站式的管理和快速部署能力。一、一卡双IMSI,实现无感知切换中国联通跨境物联网产品解决方案的核心亮点在于其采用的一卡双IMSI技术。这种技术使得物联网设备在跨国使用时,无需......
  • Android第一行代码——快速入门 Kotlin 编程(2.5 面向对象编程)
    目录2.5    面向对象编程2.5.1    类与对象2.5.2    继承与构造函数2.5.3    接口2.5.4    数据类与单列类2.5    面向对象编程        和很多现代高级语言一样,Kotlin也是面向对象的,因此理解什么是面向对......
  • JAVA面向对象高级:继承:权限修饰符(继承注意事项) 单继承 Object类 方法重写
    权限修饰符(共四个):publilcprivateprotected缺省   单继承:   Object类:所有类的祖宗类。类均继承了Object类  方法重写     方法重写在开发中的应用场景:子类重写Object类中toString方法  ......
  • JAVA面向对象高级:static注意事项
    packagecom.itheima.static1;publicclassStudent{staticStringschoolName;doublescore;//实例变量//1.类方法中可以直接访问类的成员,不可以直接访问实例成员publicstaticvoidprinthelloworld(){//注意:同一个类中,访问类成员,可以省略类......
  • JAVA面向对象高级:static修饰成员方法 真正搞懂main方法 类方法实例方法应用场景
         真正搞懂main方法    类方法实例方法应用场景类方法最常见的应用场景是做工具类      ......
  • Python面向对象编程:合集篇(类、对象、封装、继承和多态)
    Python语言设计之初,就是为了面向对象。所以Python的面向对象更加易于理解。如果你以前学过Java、C++你大概就懂得什么是面向对象,但如果你是第一门编程语言就选择Python,那么也不要害怕。这篇文章,我们将会尽量详细的讲解,把Python面向对象编程的知识讲清楚。接下来我们先来简单的......
  • int强转面向对象分析
    int强转面向对象分析importmathclassMyInt():def__call__(self,num):#针对boolifisinstance(num,bool):ifnum==False:return0else:return1#针对inte......
  • 2024 年值得关注的三大 DevOps 趋势
    在过去几年中,DevOps世界以前所未有的速度发展,但它仍然是许多组织效率、创新和数字化转型的主要驱动力。Google的2023年加速DevOps状态报告显示,公司的软件交付性能质量可以预测组织绩效、团队绩效和员工福祉。2024年,从业者预计有几个趋势会对行业产生重大影响。随着时......