Golang 面向对象编程

结构体

看一个问题

  1. 张老太养了两只猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。

    使用现有技术解决

  2. 单独的定义变量解决

  3. 使用数组解决 ```go package main

import “fmt”

func main() { / 张老太养了两只猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序, 当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。 /

  1. ////1. 使用变量的处理(变量过多,就会显得冗余,不方便。)
  2. //var cat1Name string = "小白"
  3. //var cat1Age int = 3
  4. //var cat1Color string = "白色"
  5. //var cat2Name string = "小花"
  6. //var cat2Age int = 100
  7. //var cat2Color string = "花色"
  8. //
  9. ////2. 使用数组解决
  10. //catNames := [...]string{"小白", "小花"}
  11. //catAge := [...]int{3, 100}
  12. //catColors := [...]string{"白色", "花色"}
  13. //... map[string]string
  14. /*
  15. 前面三种方式都不好解决这个问题:
  16. 不管是用变量或者数组,他们并不利于数据管理。
  17. */

}

  1. 3. 现有技术解决的缺点分析
  2. - 使用变量或者数组来解决养猫的来解决养猫的问题,都不利于数据的管理和利用。因为名字,年龄,颜色都是属于一只猫,但是这里是分开保存。
  3. - 如果我们希望对一只猫的属性(名字、年龄、颜色)进行操作(绑定方法),也不好处理。
  4. - 引出我们要讲解的技术 => 结构体
  5. <a name="15fac84d"></a>
  6. ## Golang 语言面向对象编程说明
  7. 1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
  8. 1. Golang 没有类(class),Go 语言的结构体(struct)和其他编程语言的类(class)油桶等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
  9. 1. Golang 面向对象编程非常简洁,去掉了传统 oop 语言的竭诚、方法重载、构造函数和析构函数、隐藏的 this 指针等等。
  10. 1. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他 OOP 语言不一样,比如继承:Golang 没有 extends 关键字,继承是通过匿名字段来实现。
  11. 1. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。Golang 中面向接口编程是非常重要的特性。
  12. <a name="26567d99"></a>
  13. ### 结构体与结构体变量(实例/对象)的关系
  14. 1. 将一类事物的特性提取出来(比如:猫类),形成新的数据类型,就是一个结构体。
  15. 1. 通过这个结构体,我们可以创建多个变量(实例/对象)。
  16. <a name="04ec81af"></a>
  17. ### 快速入门案例
  18. ```go
  19. package main
  20. import "fmt"
  21. //使用 struct 来完成案例
  22. //定义一个 Cat 结构体,将 Cat 的各个字段/属性信息,放入到 Cat 结构体进行管理。
  23. type Cat struct {
  24. Name string
  25. Age int
  26. Color string
  27. Hobby string
  28. }
  29. func main() {
  30. //创建一个 Cat 变量
  31. var cat1 Cat // var a int
  32. cat1.Name = "小白"
  33. cat1.Age = 3
  34. cat1.Color = "白色"
  35. cat1.Hobby = "吃鱼"
  36. fmt.Println(cat1)
  37. fmt.Println("猫猫的信息如下:")
  38. fmt.Println("Name =", cat1.Name)
  39. fmt.Println("Age =", cat1.Age)
  40. fmt.Println("Color =", cat1.Color)
  41. fmt.Println("Hobby =", cat1.Hobby)
  42. }

结构体和结构体变量(实例)的区别和联系

  1. 结构体是自定义的数据类型,代表一类事物。
  2. 结构体变量(实例)视具体的,实际的,代表一个具体变量。

    如何声明结构体

    声明结构体

    1. type 标识符 struct {
    2. field1 type
    3. field2 type
    4. }

    字段/属性

    基本介绍
  3. 从概念或叫法上看:结构体字段 = 属性 = field

  4. 字段是一个结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。

    注意事项和细节说明
  5. 字段声明语法同变量,示例:字段名 字段类型

  6. 字段的类型可以为:基本类型、数组或引用类型
  7. 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规定同前面讲的一样:
    • 布尔类型是 false,整型是 0,字符串是 “” 。
    • 数组类型的默认值和它的元素类型相关。
    • 指针,slice,和 map 的零值都是 nil ,即还没分配空间。
  8. 结构体字段默认值 ```go package main

import “fmt”

type Person struct { Name string Age int scores [5]float64 ptr *int //指针 slice []int //切片 map1 map[string]string //切片 }

func main() {

  1. //如果结构体的字段类型是:指针,slice,和 map 的零值都是 nil ,即还没分配空间。
  2. //如果需要使用这样的字段,需要先 make,才能使用。
  3. //定义结构体变量
  4. var p1 Person
  5. fmt.Println(p1)
  6. if p1.ptr == nil {
  7. fmt.Println("ok1")
  8. }
  9. if p1.slice == nil {
  10. fmt.Println("ok2")
  11. }
  12. if p1.map1 == nil {
  13. fmt.Println("ok3")
  14. }
  15. //使用 slice, 一定要 make
  16. //p1.slice[0] = 100 报错
  17. p1.slice = make([]int, 10)
  18. p1.slice[0] = 100
  19. fmt.Println(p1)
  20. //使用 map ,一定要先 make
  21. //p1.map1["key1"] = "Tom" // 报错
  22. p1.map1 = make(map[string]string)
  23. p1.map1["key1"] = "tom~"
  24. fmt.Println(p1)

}

  1. 5. 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个。
  2. ```go
  3. package main
  4. import "fmt"
  5. type Monster struct {
  6. Name string
  7. Age int
  8. }
  9. func main() {
  10. // 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个。
  11. var monster1 Monster
  12. monster1.Name = "牛魔王"
  13. monster1.Age = 500
  14. monster2 := monster1 //结构体是值类型
  15. monster2.Name = "青牛精"
  16. fmt.Println("monster1 =", monster1) // monster1 = {牛魔王 500}
  17. fmt.Println("monster2 =", monster2) // monster2 = {青牛精 500}
  18. }

创建结构体变量和访问结构体字段

  1. 方式 1 - 直接声明

    1. var person Person
  2. 方式 2 - {}

    1. var person Person = Person{}
  3. 方式 3

    1. var person *Person = new(Person)
  4. 方式 4

    1. var person *Person = &Person{}
  5. 说明

    • 第 3 种和第 4 种方式返回的是结构体指针。
    • 结构体指针访问字段的标准方式应该是:(_结构体指针).字段名,比如(_person).Name = “Tom”
    • 但 go 做了一个简化,也支持结构体指针.字段名,比如 person.Name = “Tom”。更加符合程序员使用习惯,go 编译器底层对 person.Name 做了转化 (*person).Name
  6. 案例演示 ```go package main

import “fmt”

type Person struct { Name string Age int }

func main() {

  1. //方式2
  2. p2 := Person{"Mary", 20}
  3. //p2.Name = "Tom"
  4. //p2.Age = 18
  5. fmt.Println(p2)
  6. //方式3
  7. var p3 *Person = new(Person)
  8. //因为 p3 是一个指针,因此标准的给字段赋值方式
  9. //(*p3).Name = "Smith" 也可以这样写 p3.Name = "Smith"
  10. //原因:go 的设计者为了程序员使用方便,底层会对 p3.Name 进行处理,会给 p3 加上取值运算。
  11. (*p3).Name = "Smith"
  12. (*p3).Age = 30
  13. fmt.Println(*p3)
  14. p3.Name = "John"
  15. p3.Age = 40
  16. fmt.Println(*p3)
  17. //方式4
  18. //var person *Person = &Person{}
  19. //因为 person 是一个指针,因此标准的访问字段的方法
  20. //(*person.Name) = "Scott"
  21. //go 的设计者为了程序员使用方便,也可以 person.Name = "Scott"
  22. var person *Person = &Person{"Mary", 60}
  23. fmt.Println(*person)
  24. (*person).Name = "Scott"
  25. (*person).Age = 88
  26. fmt.Println(*person)
  27. person.Name = "Scott~~"
  28. person.Age = 10
  29. fmt.Println(*person)

}

  1. <a name="cd2ed584"></a>
  2. ### struct 类型的内存分配机制
  3. 1. 案例一
  4. ```go
  5. package main
  6. import "fmt"
  7. type Person struct {
  8. Name string
  9. Age int
  10. }
  11. // struct 类型的内存分配机制
  12. func main() {
  13. var p1 Person
  14. p1.Name = "Mary"
  15. p1.Age = 10
  16. var p2 Person = p1
  17. fmt.Println(p2.Age)
  18. p2.Name = "Tom"
  19. fmt.Printf("p2.Name = %v p1.Name = %v \n", p2.Name, p1.Name)
  20. }
  1. 案例二 ```go package main

import “fmt”

type Person struct { Name string Age int }

// struct 类型的内存分配机制 func main() { var p1 Person p1.Name = “Mary” p1.Age = 10

  1. var p2 *Person = &p1 //这里是关键
  2. fmt.Println((*p2).Age)
  3. fmt.Println(p2.Age)
  4. p2.Name = "Tom~"
  5. fmt.Printf("p2.Name = %v p1.Name = %v \n", p2.Name, p1.Name)
  6. fmt.Printf("p2.Name = %v p1.Name = %v \n", (*p2).Name, p1.Name)
  7. fmt.Printf("p1 的地址 %p \n", &p1)
  8. fmt.Printf("p2 的地址 %p p2 的值 %p \n", &p2, p2)

}

  1. <a name="a7a04055"></a>
  2. ### 结构体的注意事项和使用细节
  3. 1. 结构体的所有字段在内存中是连续的。
  4. ```go
  5. package main
  6. import "fmt"
  7. //结构体
  8. type Point struct {
  9. x int
  10. y int
  11. }
  12. //结构体
  13. type Rect struct {
  14. leftUp, rightDown Point
  15. }
  16. //结构体
  17. type Rect2 struct {
  18. leftUp, rightDown *Point
  19. }
  20. func main() {
  21. r1 := Rect{Point{1,2}, Point{3,4}}
  22. // r1 有四个 int ,在内存中是连续分布的
  23. // 打印地址
  24. fmt.Printf("r1.leftUp.x 的地址 %p r1.leftUp.y 的地址 %p r1.rightDown.x 的地址 %p r1.rightDown.y 的地址 %p \n",
  25. &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
  26. // r2 有两个 *Point 类型,这两个 *Point 类型的本身地址也是连续的,但是他们指向的地址不一定是连续的。
  27. r2 := Rect2{&Point{10,20}, &Point{30,40}}
  28. fmt.Printf("r2.leftUp 本身的地址 %p r2.rightDown 本身的地址 %p \n",
  29. &r2.leftUp, &r2.rightDown)
  30. // 他们指向的地址不一定是连续..., 这个要看系统运行时是如何分配的。
  31. fmt.Printf("r2.leftUp 指向的地址 %p r2.rightDown 指向的地址 %p \n",
  32. r2.leftUp, r2.rightDown)
  33. fmt.Printf("r2.leftUp.x 的地址 %p r2.leftUp.y 的地址 %p r2.rightDown.x 的地址 %p r2.rightDown 指向的地址 %p \n",
  34. &r2.leftUp.x, &r2.leftUp.y, &r2.rightDown.x, &r2.rightDown.y)
  35. }
  1. 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)。 ```go package main

import “fmt”

type A struct { Num int }

type B struct { Num int }

func main() {

  1. // 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)。
  2. var a A
  3. var b B
  4. //a = b 报错
  5. a = A(b) // 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)。
  6. fmt.Println(a, b)

}

  1. 3. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转。
  2. ```go
  3. package main
  4. import "fmt"
  5. type Student struct {
  6. Name string
  7. Age int
  8. }
  9. type Stu Student
  10. func main() {
  11. //结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转。
  12. var stu1 Student
  13. var stu2 Stu
  14. //stu2 = stu1 报错
  15. stu2 = Stu(stu1)
  16. fmt.Println(stu1, stu2)
  17. }
  1. struct 的每个字段上,可以写上一个 tag ,该 tag 可以通过反射机制获取,使用场景就是序列化和反序列化。 ```go package main

import ( “encoding/json” “fmt” )

type Monster struct { Name string json:"name" //结构体的 tag Age int json:"age" Skill string json:"skill" }

func main() { //1. 创建 monster 变量

  1. monster := Monster{"牛魔王", 500, "芭蕉扇"}
  2. //2. 将 monster 变量序列化为 json 格式字符串
  3. jsonStr, err := json.Marshal(monster)
  4. if err != nil{
  5. fmt.Println("json 处理错误", err)
  6. } else {
  7. fmt.Println("jsonStr =", string(jsonStr))
  8. }

}

  1. <a name="ea340b9d"></a>
  2. ## 方法
  3. <a name="f72dd4a6-1"></a>
  4. ### 基本介绍
  5. 1. 在某些情况下,我们要需要声明(定义方法)。比如 Person 结构体:除了有一些字段外(年龄,姓名..),Person 结构体还有一些行为比如说话、跑步...等等。这时就要用到方法才能完成。
  6. 1. Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct。
  7. <a name="2dbb7dac"></a>
  8. ### 方法的声明和调用
  9. 1. 方法的声明和调用
  10. ```go
  11. type A struct {
  12. Num int
  13. }
  14. func (a A) test() {
  15. fmt.Println(a.Num)
  16. }
  1. 对上面语法的说明
    • func (a A) test() {} 表示 A 结构体有一方法,方法名为 test 。
    • (a A) 体现 test 方法是和 A 类型绑定的。
  2. 举例说明 ```go package main

import “fmt”

type Person struct { Name string }

//给 A 类型绑定一个方法 func (p Person) test() { fmt.Println(“test () name =”, p.Name) }

func main() { var p Person p.Name = “Tom” p.test() //调用方法 }

  1. 4. 对上面代码的总结和说明
  2. - test 方法和 Person 类型绑定
  3. - test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其他类型的变量来调用。
  4. - func (p Person) 里面的 p 表示哪个 Person 变量调用 ,这个 p 就是它的副本,这点和函数传参非常相似。
  5. - p 这个名字,可以由程序员指定,不是固定。
  6. <a name="785a6d32"></a>
  7. ### 方法快速入门
  8. 1. Person 结构体添加 speak 方法,输出 xxx 是个好人。
  9. ```go
  10. package main
  11. import "fmt"
  12. /*
  13. 快速入门:
  14. 给 Person 结构体添加 speak 方法,输出 xxx 是个好人。
  15. */
  16. type Person struct {
  17. Name string
  18. }
  19. // 给 Person 结构体添加 speak 方法,输出 xxx 是个好人。
  20. func (p Person) speak() {
  21. fmt.Printf("%v 是一个好人! \n", p.Name)
  22. }
  23. func main() {
  24. var p Person
  25. p.Name = "Tom"
  26. p.speak()
  27. }
  1. 给 Person 结构体添加 jisun 方法,可以计算从 1 + .. + 1000 的结果。 ```go package main

import “fmt”

/ 快速入门: 给 Person 结构体添加 jisuan 方法,可以计算从 1 + .. + 1000 的结果。 /

type Person struct { Name string }

func (p Person) jisuan() { res := 0 for i := 1; i < 1001; i++ { res += i }

  1. fmt.Println(p.Name, "计算的结果 res =", res)

}

func main() { var p Person

  1. p.Name = "Tom"
  2. p.jisuan() //调用方法

}

  1. 3. Person 结构体 jisuan2 方法,该方法可以接受一个参数 n ,计算从 1 + .. + n 的结果。
  2. ```go
  3. package main
  4. import "fmt"
  5. /*
  6. 快速入门:
  7. 给 Person 结构体 jisuan2 方法,该方法可以接受一个参数 n ,计算从 1 + .. + n 的结果。
  8. */
  9. type Person struct {
  10. Name string
  11. }
  12. func (p Person) jisuan2(n int) {
  13. res := 0
  14. for i := 1; i < n + 1; i++ {
  15. res += i
  16. }
  17. fmt.Println(p.Name, "计算的结果 res =", res)
  18. }
  19. func main() {
  20. var p Person
  21. n := 1000
  22. p.Name = "Tom"
  23. p.jisuan2(n) //调用方法
  24. }
  1. 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果。 ```go package main

import “fmt”

/ 快速入门: 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果。 /

type Person struct { Name string }

func (p Person) getsum(n1 int, n2 int) int{ return n1 + n2 }

func main() { var p Person n1 := 55 n2 := 45 p.Name = “Tom”

  1. res := p.getsum(n1, n2)
  2. fmt.Println(p.Name, "计算的结果 res =", res)

}

  1. <a name="a3aa656e"></a>
  2. ### 方法的调用和传参机制原理
  3. <a name="f72dd4a6-2"></a>
  4. #### 基本介绍
  5. 1. 方法的调用和传参机制核函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。
  6. 1. 案例一
  7. ```go
  8. package main
  9. import "fmt"
  10. /*
  11. 快速入门:
  12. 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果。
  13. */
  14. type Person struct {
  15. Name string
  16. }
  17. func (p Person) getsum(n1 int, n2 int) int{
  18. return n1 + n2
  19. }
  20. func main() {
  21. var p Person
  22. n1 := 55
  23. n2 := 45
  24. p.Name = "Tom"
  25. res := p.getsum(n1, n2)
  26. fmt.Println(p.Name, "计算的结果 res =", res)
  27. }
  1. 对上面代码进行说明:
    • 在通过一个变量去调用方法时,其调用机制和函数一样。
    • 不一样的地方,变量调用方法时,该变量本身也会作为参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)。
  2. 案例二 ```go package main

import “fmt”

/ 声明一个结构体 Circle ,字段为 radius ; 声明一个方法 area 和 Circle 绑定,可以返回面积。 提示:画出 area 执行过程 + 说明 /

type Circle struct { radius float64 }

func (c Circle) area() float64 { return 3.14 c.radius c.radius }

func main() { var c Circle

  1. c.radius = 4.0
  2. area := c.area()
  3. fmt.Println("面积是 area =", area)

}

  1. <a name="086b37db"></a>
  2. ### 方法的声明(定义)
  3. 1. 方法的声明(定义)
  4. ```go
  5. func (recevier type) methodName (参数列表) (返回值列表) {
  6. 方法体
  7. return 返回值
  8. }
  1. 说明

    • 参数列表:表示方法输入
    • recevier type: 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
    • receiver type: type 可以是结构体,也可以其他的自定义类型。
    • receiver: 就是一个 type 类型的一个变量(实例)。
    • 参数列表:表示方法输入
    • 返回值列表:表示返回的值,可以多个
    • 方法主体:表示为了实现某一功能代码块
    • return 语句不是必须的。

      方法注意事项和细节讨论

  2. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式。

  3. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理。 ```go package main

import “fmt”

/ 声明一个结构体 Circle ,字段为 radius ; 声明一个方法 area 和 Circle 绑定,可以返回面积。 提示:画出 area 执行过程 + 说明 /

type Circle struct { radius float64 }

func (c Circle) area() float64 { return 3.14 c.radius c.radius }

//为了提高效率,通常我们方法和结构体的指针类型绑定。

func (c Circle) area2() float64 { // 因为 c 是指针,因此我们标准的访问其字段的方式是 (c).radius // return 3.14 (c).radius (c).radius // (c).radius 等价 c.radius fmt.Printf(“c 是 Circle 指向的地址 = %p \n”, c) c.radius = 10.0 return 3.14 c.radius c.radius }

func main() { var c1 Circle

  1. c1.radius = 4.0
  2. area1 := c1.area()
  3. fmt.Println("面积是 area1 =", area1)
  4. var c2 Circle
  5. fmt.Printf("main c2 结构体变量地址 = %p \n", &c2)
  6. c2.radius = 5.0
  7. // area2 := (&c2).area2()
  8. // 编译器底层做了优化,(&c2).area2() 等价 c2.area2()
  9. //因为编译器会自动的给我们加上 &
  10. area2 := c2.area2()
  11. fmt.Println("面积是 area2 =", area2)
  12. fmt.Println("main() c2.radius =", c2.radius)

}

  1. 3. Golang 中的方法作用在指定的数据类型上(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct 比如 int float 等都可以有方法。
  2. ```go
  3. package main
  4. import "fmt"
  5. /*
  6. Golang 中的方法作用在指定的数据类型上(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct , 比如 int,float 等都可以有方法。
  7. */
  8. type integer int
  9. func (i integer) print() {
  10. fmt.Println("i =", i)
  11. }
  12. //编写一个方法,可以改变 i 的值
  13. func (i *integer) change() {
  14. *i = *i + 1
  15. }
  16. func main() {
  17. var i integer = 10
  18. i.print()
  19. i.change()
  20. fmt.Println("i =", i)
  21. }
  1. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,智能在本包访问,方法首字母大写,可以在本包和其他包访问。
  2. 如果一个类型实现了 String() 这个方法,那么 fmt.Println 默认会调用这个变量的 String() 进行输出。 ```go package main

import “fmt”

type Student struct { Name string Age int }

//给 Student 实现方法 String() func (stu Student) String() string { str := fmt.Sprintf(“Name = [%v] Age = [%v]”, stu.Name, stu.Age) return str }

func main() {

  1. //定义一个 Student 变量
  2. stu := Student{
  3. Name : "Tom",
  4. Age : 20,
  5. }
  6. fmt.Println(stu)
  7. //如果你实现了 *Student 类型的 String 方法,就会自动调用。
  8. fmt.Println(&stu)

}

  1. <a name="dfa522aa"></a>
  2. ### 方法的课堂练习
  3. 1. 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个 10 * 8 的矩形,在 main 方法中调用该方法。
  4. ```go
  5. package main
  6. import "fmt"
  7. /*
  8. 课堂练习:
  9. 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个 10 * 8 的矩形,在 main 方法中调用该方法。
  10. */
  11. type MethodUtils struct {
  12. //字段...
  13. }
  14. //给 MethodUtils 编写方法
  15. func (mu MethodUtils) print() {
  16. for i:=1; i <= 10; i++ {
  17. for j := 1; j <= 8; j++ {
  18. fmt.Print("*")
  19. }
  20. fmt.Println("")
  21. }
  22. }
  23. func main() {
  24. var mu MethodUtils
  25. mu.print()
  26. }
  1. 编写一个方法,提供 m 和 n 两个参数,方法中打印一个 m*n 的矩形。 ```go package main

import “fmt”

/ 课堂练习: 编写一个方法,提供 m 和 n 两个参数,方法中打印一个 mn 的矩形。 */

type MethodUtils struct { //字段… }

func (mu MethodUtils) print(m int, n int) { for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { fmt.Print(“*”) } fmt.Println() } }

func main() { var mu MethodUtils

  1. mu.print(10,8)

}

  1. 3. 编写一个方法算该矩形的面积(可以接受长len,和宽width),将其作为方法返回值。在 main 方法中调用该方法,接受返回的面积之并打印。
  2. ```go
  3. package main
  4. import "fmt"
  5. /*
  6. 课堂练习:
  7. 编写一个方法算该矩形的面积(可以接受长len,和宽width),将其作为方法返回值。在 main 方法中调用该方法,接受返回的面积之并打印。
  8. */
  9. type MethodUtils struct {
  10. //字段...
  11. }
  12. func (mu MethodUtils) area(len float64, width float64) float64 {
  13. return len * width
  14. }
  15. func main() {
  16. var mu MethodUtils
  17. areaRes := mu.area(2.5,8.7)
  18. fmt.Println("面积 area =", areaRes)
  19. }
  1. 编写方法:判断一个数是奇数还是偶数。 ```go package main

import “fmt”

/ 课堂练习: 编写方法:判断一个数是奇数还是偶数。 /

type MethodUtils struct { //字段… }

func (mu * MethodUtils) JudgeNum(num int) { if num % 2 == 0 { fmt.Println(“是偶数…”) } else { fmt.Println(“是奇数…”) } }

func main() { var mu MethodUtils

  1. //(&mu).JudgeNum(10)
  2. mu.JudgeNum(10)

}

  1. 5. 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符 *,则打印相应的结果。
  2. ```go
  3. package main
  4. import "fmt"
  5. /*
  6. 课堂练习:
  7. 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符 *,则打印相应的结果。
  8. */
  9. type MethodUtils struct {
  10. //字段...
  11. }
  12. func (mu * MethodUtils) Print(m int, n int, key string) {
  13. for i := 1; i <= m; i++ {
  14. for j := 1; j <= n; j++ {
  15. fmt.Print(key)
  16. }
  17. fmt.Println()
  18. }
  19. }
  20. func main() {
  21. var mu MethodUtils
  22. mu.Print(7, 20, "#")
  23. }
  1. 定义小小计算器的结构体(Calcuator),实现加减乘除四个功能
    • 实现形式1:分四个方法完成。
    • 实现形式2:用一个方法搞定 ```go package main

import “fmt”

/ 课堂练习: 定义小小计算器的结构体(Calculator),实现加减乘除四个功能: 实现形式1:分四个方法完成。 实现形式2:用一个方法搞定 /

//定义结构体 type Calculator struct { Num1 float64 Num2 float64 }

//方式1 //只写加减,其他类似,不写了 func (calculator *Calculator) GetSum() float64 { return calculator.Num1 + calculator.Num2 }

func (calculator *Calculator) GetSub() float64 { return calculator.Num1 - calculator.Num2 }

//方式2

func (calculator Calculator) getRes(operator byte) float64 { res := 0.0 switch operator { case ‘+’ : res = calculator.Num1 + calculator.Num2 case ‘-‘ : res = calculator.Num1 - calculator.Num2 case ‘‘ : res = calculator.Num1 * calculator.Num2 case ‘/‘ : res = calculator.Num1 / calculator.Num2 default: fmt.Println(“运算符输入有误…”) } return res }

func main() {

  1. var calculator Calculator
  2. calculator.Num1 = 1.2
  3. calculator.Num2 = 2.2
  4. sum := fmt.Sprintf("%.2f", calculator.GetSum())
  5. sub := fmt.Sprintf("%.2f", calculator.GetSub())
  6. fmt.Printf("sum = %v sub = %v \n", sum, sub)
  7. res := calculator.getRes('+')
  8. fmt.Printf("res = %v \n", fmt.Sprintf("%.2f", res))

}

  1. <a name="35cf1d22"></a>
  2. ## 方法和函数区别
  3. 1. 调用方法不一样。
  4. - 函数的调用方式
  5. - 方法的调用方式
  6. 2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
  7. 2. 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以。
  8. ```go
  9. package main
  10. import "fmt"
  11. type Person struct {
  12. Name string
  13. }
  14. //函数
  15. //对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。
  16. func test01(p Person) {
  17. fmt.Println(p.Name)
  18. }
  19. func test02(p *Person) {
  20. fmt.Println(p.Name)
  21. }
  22. //方法
  23. //对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以。
  24. func (p Person) test03() {
  25. p.Name = "Jack"
  26. fmt.Println("test03()", p.Name)
  27. }
  28. func (p *Person) test04() {
  29. p.Name = "Mary"
  30. fmt.Println("test03()", p.Name)
  31. }
  32. func main() {
  33. p := Person{"Tom"}
  34. test01(p)
  35. //test01(&p) 报错
  36. test02(&p)
  37. //test02(p) 报错
  38. //都可以,但是 (&p).test03() 还是值拷贝。
  39. p.test03()
  40. fmt.Println("main() p.name = ", p.Name)
  41. (&p).test03() //只是形式上传递了一个地址,本质仍然是值拷贝。
  42. fmt.Println("main() p.name = ", p.Name)
  43. (&p).test04()
  44. fmt.Println("main() p.name = ", p.Name)
  45. p.test04() // 等价于 (&p).test04()
  46. fmt.Println("main() p.name = ", p.Name)
  47. }
  1. 代码总结

    • 不管调用形式如何,真正决定是值拷贝还是地址拷贝,主要是看这个方法是和哪种类型绑定的。
    • 如果和值类型绑定,则是值拷贝,如果是和指针类型绑定,则是地址拷贝。

      面向对象编程应用实例

      步骤

  2. 声明(定义)结构体,确定结构体名

  3. 编写结构体的字段
  4. 编写结构体的方法

    案例

  5. 案例一

    • 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、float64 类型。
    • 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。
    • 在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。 ```go package main

import ( “fmt” )

/ 案例: (1) 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、float64 类型。 (2) 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。 (3) 在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。 /

type Student struct { Name string json:"name" Gender string json:"gender" Age int json:"age" Id int json:"id" Score float64 json:"score" }

func (stu *Student) say() string {

  1. infoStr := fmt.Sprintf("student的信息 name = [%v] gener = [%v] age = [%v] id = [%v] score = [%v]",
  2. stu.Name, stu.Gender, stu.Age, stu.Id, stu.Score)
  3. return infoStr

}

func main() { var stu Student

  1. stu.Name = "Tom"
  2. stu.Gender = "男"
  3. stu.Age = 20
  4. stu.Id = 1001
  5. stu.Score = 90.0
  6. infoStr := stu.say()
  7. fmt.Println(infoStr)

}

  1. 2. 案例二
  2. - 编写一个 Dog 结构体,包含 nameageweight字段
  3. - 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。
  4. - main 方法中,创建 Dog 结构体实例(变量),并访问 say 方法,将调用结果打印输出。
  5. ```go
  6. package main
  7. import (
  8. "fmt"
  9. )
  10. /*
  11. 案例:
  12. (1) 编写一个 Dog 结构体,包含 name、age、weight 字段
  13. (2) 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。
  14. (3) 在 main 方法中,创建 Dog 结构体实例(变量),并访问 say 方法,将调用结果打印输出。
  15. */
  16. type Dog struct {
  17. Name string
  18. Age int
  19. Weight float64
  20. }
  21. func (dog *Dog) say() string {
  22. infoStr := fmt.Sprintf("Dog 的信息 name = [%v] age = [%v] weight = [%v]",
  23. dog.Name, dog.Age, dog.Weight)
  24. return infoStr
  25. }
  26. func main() {
  27. dog := Dog{
  28. Name : "奶茶",
  29. Age : 5,
  30. Weight: 15.6,
  31. }
  32. infoStr := dog.say()
  33. fmt.Println(infoStr)
  34. }
  1. 案例三
    • 编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取。
    • 声明一个方法获取立方体的体积。
    • 创建一个 Box 结构体变量,打印给定尺寸的立方体的体积。 ```go package main

import “fmt”

/ 案例: (1) 编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取。 (2) 声明一个方法获取立方体的体积。 (3) 创建一个 Box 结构体变量,打印给定尺寸的立方体的体积。 /

type Box struct { Length float64 Width float64 Height float64 }

func (box Box) Volume() float64 { return box.Length box.Width * box.Height }

func main() { var box Box

  1. fmt.Println("请输入立方体的长度:")
  2. fmt.Scanln(&box.Length)
  3. fmt.Println("请输入立方体的宽度:")
  4. fmt.Scanln(&box.Width)
  5. fmt.Println("请输入立方体的高度:")
  6. fmt.Scanln(&box.Height)
  7. volume := box.Volume()
  8. fmt.Println("立方体的体积 volume =", volume)

}

  1. 4. 案例四
  2. - 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18 ,收费 20 元,其他情况门票免费。
  3. - 请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出。
  4. ```go
  5. package main
  6. import "fmt"
  7. /*
  8. 案例:
  9. (1) 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18 ,收费 20 元,其他情况门票免费。
  10. (2) 请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出。
  11. */
  12. type Visitor struct {
  13. Name string
  14. Age int
  15. }
  16. func (visitor *Visitor) ShowPrice() {
  17. if visitor.Age >= 90 || visitor.Age <= 5 {
  18. fmt.Println("考虑到安全,就不玩耍...")
  19. return
  20. }
  21. if visitor.Age > 18 {
  22. fmt.Printf("%v的年龄为:%v,门票价格为:20元 \n", visitor.Name, visitor.Age)
  23. } else {
  24. fmt.Printf("%v的年龄为:%v,门票价格免费 \n", visitor.Name, visitor.Age)
  25. }
  26. }
  27. func main() {
  28. var visitor Visitor
  29. for {
  30. fmt.Println("请输入姓名:")
  31. fmt.Scanln(&visitor.Name)
  32. if visitor.Name == "n" {
  33. fmt.Println("退出程序...")
  34. break
  35. }
  36. fmt.Println("请输入年龄:")
  37. fmt.Scanln(&visitor.Age)
  38. visitor.ShowPrice()
  39. }
  40. }

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

说明

  1. Golang 再创建结构体实例(变量)时,可以直接指定字段的值。

    创建结构体变量时指定字段值方式

  2. 方式一 ```go var stu1 Student = Student{“tom”, 10} stu2 := Student(“tom~”, 10)

var stu3 Student = Student{ Name: “Mary”, Age: 30, }

stu4 := Student{ Name: “Mary~”, Age: 20, }

  1. ```go
  2. //举例
  3. package main
  4. import "fmt"
  5. type Stu struct {
  6. Name string
  7. Age int
  8. }
  9. func main() {
  10. //方式1
  11. //在创建结果体变量时,就直接指定字段的值
  12. var stu1 = Stu{"小明",19}
  13. stu2 := Stu{"小明~", 20}
  14. //在创建结构体变量时,把字段名和字段值写在一起, 这种写法就不依赖这个字段的定义顺序
  15. var stu3 = Stu{
  16. Name : "Jack",
  17. Age : 20,
  18. }
  19. stu4 := Stu{
  20. Age : 30,
  21. Name : "Mary",
  22. }
  23. fmt.Println(stu1, stu2, stu3, stu4)
  24. }
  1. 方式二
    1. var stu5 *Student = &Student{"Smith", 30}
    2. var stu6 *Student = &Student{
    3. "Name" : "Scott",
    4. "Age" : "80",
    5. }
  1. package main
  2. import "fmt"
  3. type Stu struct {
  4. Name string
  5. Age int
  6. }
  7. func main() {
  8. //方式1
  9. //在创建结果体变量时,就直接指定字段的值
  10. var stu1 = Stu{"小明",19}
  11. stu2 := Stu{"小明~", 20}
  12. //在创建结构体变量时,把字段名和字段值写在一起, 这种写法就不依赖这个字段的定义顺序
  13. var stu3 = Stu{
  14. Name : "Jack",
  15. Age : 20,
  16. }
  17. stu4 := Stu{
  18. Age : 30,
  19. Name : "Mary",
  20. }
  21. fmt.Println(stu1, stu2, stu3, stu4)
  22. //方式2
  23. //返回结构体的指针类型(!!!)
  24. var stu5 = &Stu{"小王", 29}
  25. stu6 := &Stu{"小王~", 39}
  26. //在创建结构体指针变量时,把字段名和字段值写在一起,这种写法,就不依赖这个字段的定义顺序
  27. var stu7 = &Stu{
  28. Name : "小李",
  29. Age : 49,
  30. }
  31. stu8 := &Stu{
  32. Age : 59,
  33. Name : "小李~",
  34. }
  35. fmt.Println(stu5, stu6, stu7, stu8)
  36. fmt.Println(*stu5, *stu6, *stu7, *stu8)
  37. }

工厂模式

说明

  1. Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。

    看一个需求

  2. 一个结构体的声明是这样的:

    1. package model
    2. type Student struct{
    3. Name string...
    4. }
  3. 因为这里的 Student 的首字母 S 是大写的,如果我们想在其他包创建 Student 的实例(比如 main 包),引入 model 包后,就可以直接创建 Student 结构体的变量(实例),但是问题来了,如果首字母是小写,比如是 type student struct 就不行了。==> 工厂模式来解决。

    工厂模式来解决问题

  4. 使用工厂模式实现跨包创建结构体实例(变量)的案例:

    • 如果 model 包的结构体变量首字母大写,引入后,直接使用,没有问题。
    • 如果 model 包的结构体变量字母小写,引入后,不能直接使用,可以工厂模式解决。 ```go //案例 //model包 //student.go中的内容

package model

//定义一个结构体 type student struct { Name string Score float64 }

//因为 student 结构体首字母是小写,因此是只能在 model 使用。 //我们通过工厂模式来解决

func NewStudent(n string, s float64) *student { return &student { Name : n, Score : s, } }

  1. ```go
  2. //main 包
  3. //main.go
  4. package main
  5. import (
  6. "GoProject/src/go_code/chapter10/factory_case_02/model"
  7. "fmt"
  8. )
  9. func main() {
  10. //创建一个 Student 实例
  11. //var stu = model.Student{
  12. // Name : "Tom",
  13. // Score : 78.9,
  14. //}
  15. //当 student 结构体是首字母小写,我们可以通过工厂模式解决。
  16. var stu = model.NewStudent("Tom~", 88.8)
  17. fmt.Println(stu)
  18. fmt.Println(*stu)
  19. fmt.Println("name =", stu.Name, "score =", stu.Score)
  20. }
  1. 思考题
    • 如果 model 包的 student 的结构体的字段 Score 改成 score ,我们还能正常访问吗?又应该如何解决这个问题呢? ```go //案例 //model包 //student.go中的内容

package model

//定义一个结构体 type student struct { Name string score float64 }

//因为 student 结构体首字母是小写,因此是只能在 model 使用。 //我们通过工厂模式来解决

func NewStudent(n string, s float64) *student { return &student { Name : n, score : s, } }

//如果 score 字段首字母小写,则在其他包不可以直接方法,我们可以绑定一个方法 func (s *student) GetScore() float64 { return s.score //ok }

  1. ```go
  2. //main 包
  3. //main.go
  4. package main
  5. import (
  6. "GoProject/src/go_code/chapter10/factory_case_02/model"
  7. "fmt"
  8. )
  9. func main() {
  10. //创建一个 Student 实例
  11. //var stu = model.Student{
  12. // Name : "Tom",
  13. // Score : 78.9,
  14. //}
  15. //当 student 结构体是首字母小写,我们可以通过工厂模式解决。
  16. var stu = model.NewStudent("Tom~", 88.8)
  17. fmt.Println(stu)
  18. fmt.Println(*stu)
  19. fmt.Println("name =", stu.Name, "score =", stu.GetScore())
  20. }

课程来源