• ch15、面向对象编程-行为的定义和实现


    ch15、面向对象编程 - 行为的定义和实现

    Is Go an object-oriented language?

    Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

    Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

    Go语言的FQA页面:FQA - Is Go an object-oriented language?

    1、封装数据和行为(方法)

    Go语言封装数据:

    /*
    type Name struct {}
    */
    
    package encapsulation
    
    type Employee struct {
      Id   string
      Name string
      Age  int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    数据的初始化:

    package encapsulation
    
    import "testing"
    
    type Employee struct {
    	Id   string
    	Name string
    	Age  int
    }
    
    func TestCreateEmployeeObj(t *testing.T) {
    	// 方法一
    	e := Employee{"0", "Bob", 20}
    	// 方法二
    	e1 := Employee{Name: "Mike", Age: 30}
    	// 方法三,使用new关键字(返回的是指针,使用.访问数据)
    	e2 := new(Employee) // 返回指针
    	e2.Id = "2"
    	e2.Name = "Rose"
    	e2.Age = 22
    	t.Log(e)
    	t.Log(e1)
    	t.Log(e1.Id)
    	t.Log(e2)
    	t.Logf("e is %T", e)
    	t.Logf("e2 is %T", e2)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    封装行为(方法):

    package error_test2
    
    import (
    	"errors"
    	"testing"
    )
    
    func GetFibonacci(n int) ([]int, error) {
    	if n < 2 || n > 100 {
    		// 设置error返回10
    		return nil, errors.New("n's should be in [2,100]")
    	}
    	fibList := []int{1, 1}
    	for i := 2; i < n; i++ {
    		fibList = append(fibList, fibList[i-2]+fibList[i-1])
    	}
    
    	return fibList, nil
    }
    
    func TestGetFibonacci(t *testing.T) {
    	if v, err := GetFibonacci(-10); err != nil {
    		t.Error(err)
    	} else {
    		t.Log(v)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    package encapsulation
    
    import (
    	"fmt"
    	"testing"
    	"unsafe"
    )
    
    type Employee struct {
    	Id   string
    	Name string
    	Age  int
    }
    
    // 第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
    func (e Employee) String() string {
    	fmt.Printf("func object Address is %x\n", unsafe.Pointer(&e.Name))
    	return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }
    
    func TestStructOperations(t *testing.T) {
    	e := Employee{"0", "Bob", 20}
    	e2 := new(Employee) // 返回指针
    	e2.Id = "2"
    	e2.Name = "Rose"
    	e2.Age = 22
    
    	fmt.Printf("e Address is %x\n", unsafe.Pointer(&e.Name))
    	fmt.Printf("e2 Address is %x\n", unsafe.Pointer(&e2.Name))
    	t.Log(e.String())
    	t.Log(e2.String())
    }
    /*
    输出的地址不一样,说明是值复制
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    第二种定义方式定义实例对象为指针(go都是值传递,但复制的是指针,指向的是同一块内存)

    通常情况下为了避免内存拷贝,使用第二种方式

    package encapsulation
    
    import (
    	"fmt"
    	"testing"
    	"unsafe"
    )
    
    type Employee struct {
    	Id   string
    	Name string
    	Age  int
    }
    
    // 通常情况下为了避免内存拷贝我们使用第二种定义方式(指针,go都是值传递,但复制的是指针,但指向的是同一个)
    func (e *Employee) String() string {
    	fmt.Printf("func object Address is %x\n", unsafe.Pointer(&e.Name))
    	return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
    }
    
    func TestStructOperations(t *testing.T) {
    	e := Employee{"0", "Bob", 20}
    	e2 := new(Employee) // 返回指针
    	e2.Id = "2"
    	e2.Name = "Rose"
    	e2.Age = 22
    
    	fmt.Printf("e Address is %x\n", unsafe.Pointer(&e.Name))
    	fmt.Printf("e2 Address is %x\n", unsafe.Pointer(&e2.Name))
    	t.Log(e.String())
    	t.Log(e2.String())
    }
    
    /*
    输出地址一样,说明指向的是同一块内存
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    思考:第二种方式是否协程或线程安全?即:假如修改了结构体里边的元素值,会污染到其他线程或者协程吗?

    2、结构体类型转换

    • 属性名、属性类型、属性个数、排列顺序都是类型组成部分
    • 只有属性名、属性类型、属性个数、排列顺序都相同的结构体类型才能转换(且是强制转换)
    package main
    
    import "fmt"
    
    func main() {
    	type Person1 struct {
    		name string
    		age  int
    	}
    	type Person2 struct {
    		name string
    		age  int
    	}
    	type Person3 struct {
    		age  int
    		name string
    	}
    	type Person4 struct {
    		nm  string
    		age int
    	}
    	type Person5 struct {
    		name string
    		age  string
    	}
    	type Person6 struct {
    		name   string
    		age    int
    		gender string
    	}
    
    	var p1 Person1 = Person1{"lnj", 33}
    	var p2 Person2
    	// 类型名称不一样不能直接赋值(Person1、Person2)
    	//p2 = p1
    
    	// 虽然类型名称不一样, 但是两个类型中的`属性名称`、`属性类型`、`属性个数`、`排列顺序`都一样,所以可以强制转换
    	p2 = Person2(p1)
    	fmt.Println(p2)
    	// 两个结构体类型中的`属性名称`、`属性类型`、`属性个数`都一样,但是`排列顺序`不一样,所以不能强制转换
    	//var p3 Person3
    	//p3 = Person3(p1)
    	//fmt.Println(p3)
    
    	// 两个结构体类型中的`属性类型`、`属性个数`、`排列顺序`都一样,但是`属性名称`不一样,所以不能强制转换
    	//var p4 Person4
    	//p4 = Person4(p1)
    	//fmt.Println(p4)
    
    	// 两个结构体类型中的`属性名称`、`属性个数`、`排列顺序`都一样,但是`属性类型`不一样,所以不能强制转换
    	//var p5 Person5
    	//p5 = Person5(p1)
    	//fmt.Println(p5)
    
    	// 两个结构体类型中的`属性名称`、`属性类型`、`排列顺序`都一样,但是`属性个数`不一样,所以不能强制转换
    	//var p6 Person6
    	//p6 = Person6(p1)
    	//fmt.Println(p6)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    3、匿名属性

    • 没有指定属性名称,只有属性的类型, 我们称之为匿名属性
    • 任何有命名的数据类型都可以作为匿名属性(int、float、bool、string、struct等)
    package main
    
    import "fmt"
    
    func main() {
    	type Person struct {
    		int
    		float32
    		bool
    		string
    	}
    	// 不指定名称初始化
    	per1 := Person{3, 3.14, false, "lnj"}
    	fmt.Println(per1)
    
    	// 可以把数据类型作为名字显示初始化
    	per2 := Person{
    		int:     3,
    		float32: 3.14,
    		bool:    true,
    		string:  "lnj",
    	}
    	fmt.Println(per2)
    
    	// 可以把数据类型当做属性名称操作结构体
    	per2.int = 666
    	fmt.Println(per2.int) // 666
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • Go语言中最常见的匿名属性是用结构体类型作为匿名属性
    package main
    
    import "fmt"
    
    func main() {
    	type Person struct {
    		name string
    		age  int
    	}
    	type Student struct {
    		Person // 匿名属性
    		class  string
    	}
    
    	stu := Student{
    		Person{"lnj", 33},
    		"学前一班",
    	}
    	fmt.Println(stu) // {{lnj 33} 学前一班}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 如果结构体作为匿名属性, 想访问匿名属性的属性有两种方式
    package main
    
    import "fmt"
    
    func main() {
    	type Person struct {
    		name string
    		age  int
    	}
    	type Student struct {
    		Person // 匿名属性
    		class  string
    	}
    
    	stu := Student{
    		Person{"lnj", 33},
    		"学前一班",
    	}
    	fmt.Println(stu) // {{lnj 33} 学前一班}
    
    	// 方式一: 先找到匿名属性,再访问匿名属性中的属性
    	stu.Person.name = "zs"
    	fmt.Println(stu) // {{zs 33} 学前一班}
    	// 方式二: 直接访问匿名属性中的属性
    	// 系统会先查找当前结构体有没有名称叫做name的属性
    	// 如果没有会继续查找匿名属性中有没有名称叫做name的属性
    	stu.name = "ww"
    	fmt.Println(stu) // {{ww 33} 学前一班}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 注意点: 如果多个匿名属性的属性名称相同,那么不能通过方式二操作,只能通过方式一
    package main
    
    import "fmt"
    
    func main() {
    	type Person struct {
    		name string
    		age  int
    	}
    	type Class struct {
    		name string
    		time string
    	}
    	type Student struct {
    		Person // 匿名属性
    		Class  // 匿名属性
    	}
    	stu := Student{
    		Person{"lnj", 33},
    		Class{"学前一班", "2020-12-12"},
    	}
    	fmt.Println(stu) // {{lnj 33} {学前一班 2020-12-12}}
    	// 编译报错, 系统搞不清楚要找哪个name
    	//stu.name = "zs"
    
    	stu.Person.name = "zs"
    	stu.Class.name = "小学一年级"
    	fmt.Println(stu) // {{zs 33} {小学一年级 2020-12-12}}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 注意点: 只有匿名结构体才支持向上查找
    package main
    
    import "fmt"
    
    func main() {
    	type Person struct {
    		name string
    	}
    	type Student struct {
    		per Person
    		age int
    	}
    
    	var stu Student = Student{Person{"lnj"}, 18}
    	//fmt.Println(stu.name) // 报错
    	fmt.Println(stu.per.name) // 必须通过属性进一步查找
    	fmt.Println(stu.age)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 注意点: 如果匿名属性是一个结构体类型, 那么这个结构体类型不能是自己
    package main
    
    func main() {
    	type Person struct {
    		Person // 错误
    		name   string
    	}
    	type Student struct {
    		*Student // 正确, 链表
    		age      int
    	}
        var stu Student = Student{age: 32}
    	var stu2 Student = Student{&stu, 18}
    	fmt.Println(stu)
    	fmt.Println(stu2)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    Spring(IOC_DI)依赖注入(配置文件)2022/08/22
    Leetcode 2290. 到达角落需要移除障碍物的最小数目
    中国大数据与实体经济融合发展白皮书2019整理
    Django--ORM 常用字段及属性介绍
    基于saas的一套基础版架构 审核中
    LVS DR模式
    物联网通信技术|课堂笔记|week10-2 11月1日|应用层协议|域名解析
    Windows11重置提示找不到恢复环境怎么解决?
    聊聊ChatGLM-6B源码分析(二)
    Matlab程序结构
  • 原文地址:https://blog.csdn.net/u012558127/article/details/126021626