侧边栏壁纸
博主头像
梦幻世界博主等级

行动起来,活在当下

  • 累计撰写 23 篇文章
  • 累计创建 2 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

DAY13:方法;面向对象实例;工厂模式

梦幻世界
2024-05-24 / 0 评论 / 0 点赞 / 71 阅读 / 13926 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-05-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

方法

基本介绍

在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓名..),Person 结构体还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用方法才能完成。Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct。

方法的声明和调用

type A struct { 
    Num int
}
func (a A) test() { 
    fmt.Println(a.Num)
}

对上面的语法的说明:

  1. func (a A) test() {} 表示 A 结构体有一方法,方法名为 test

  2. (a A) 体现 test 方法是和 A 类型绑定的

举例说明:

package main

import "fmt"

type Person struct {
	Name string
}

func (p Person) test() {
	fmt.Println(p.Name)
}

func main() {
	var p Person
	p.Name = "Tom"
	p.test()
}

对上面的总结:

  1. test 方法和 Person 类型绑定

  2. test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用

  3. func (p Person) test() {}... p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非常相似。

  4. p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以

快速入门

  1. 给 Person 结构体添加 speak 方法,输出 xxx 是一个好人

  2. 给 Person 结构体添加 jisuan 方法,可以计算从 1+..+1000 的结果, 说明方法体内可以函数一样, 进行各种运算

  3. 给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n,计算从 1+..+n 的结果

  4. 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果

  5. 方法的调用

package main

import "fmt"

type Person struct {
	Name string
}

// 给 Person 结构体添加 speak 方法,输出 xxx 是一个好人
func (p Person) speak() {
	fmt.Println(p.Name, "是一个好人")
}

// 给 Person 结构体添加 jisuan 方法,可以计算从 1+..+1000 的结果, 说明方法体内可以函数一样, 进行各种运算
func (p Person) jisuan() {
	var sum int
	for i := 1; i <= 1000; i++ {
		sum += i
	}
	fmt.Println(sum)
}

// 给 Person 结构体 jisuan2  方法,该方法可以接收一个数 n,计算从 1+..+n  的结果
func (p Person) jisuan2(n int) {
	var sum int = 0
	for i := 1; i <= n; i++ {
		sum += i
	}
	fmt.Println(sum)
}

// 给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1 int, n2 int) int {
	return n1 + n2
}

func main() {
	var p Person
	p.Name = "Tom"
	// 方法的调用
	p.speak()
	p.jisuan()
	p.jisuan2(500)
	sum := p.getSum(1, 1)
	fmt.Println(sum)
}

方法的调用和传参机制原理(重要)

说明:

方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。

案例 1:画出前面 getSum 方法的执行过程+说明

getSum执行过程

说明:

  1. 在通过一个变量去调用方法时,其调用机制和函数一样

  2. 不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)

方法的声明(定义)

func (recevier type) methodName(参数列表) (返回值列表){
  方法体
  return 返回值
}
  1. 参数列表:表示方法输入

  2. recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型

  3. receiver type : type 可以是结构体,也可以其它的自定义类型

  4. receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)

  5. 返回值列表:表示返回的值,可以多个

  6. 方法主体:表示为了实现某一功能代码块

  7. return 语句不是必须的。

方法的注意事项和细节

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

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

结构体指针传递内存分析

3. Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct, 比如 int , float32 等都可以有方法

package main

import "fmt"

type integer int

func (i integer) print() {
	fmt.Println(i)
}

// 编写一个可以改变i的值
func (i *integer) change() {
	*i += 1
}

func main() {
	var i integer = 20
	i.print()
	i.change()
	fmt.Println(i)
}

4. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

5. 如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()进行输出

type Person struct {
	Name string
}

func (p *Person) String() string {
	str := fmt.Sprintf("Name=%v", p.Name)
	return str
}

func main() {
	p := Person{"Tom"}
	fmt.Println(&p)
}

方法的练习

  1. 编写一个方法算该矩形的面积(可以接收长 len,和宽 width), 将其作为方法返回值。在 main方法中调用该方法,接收返回的面积值并打印。

package main

import "fmt"

type juxing struct {
	len   float64
	width float64
}

func (j *juxing) test() float64 {
	return j.len * j.width
}

func main() {
	juxing := juxing{
		width: 1.0,
		len:   2.0,
	}

	test := juxing.test()
	fmt.Println(test)
}

2. 编写方法:判断一个数是奇数还是偶数

type num struct {
	i int
}

func (n num) test() {
	if n.i%2 == 0 {
		fmt.Println("偶数")
	} else {
		fmt.Println("奇数")
	}
}

func main() {
	num := num{2}
	num.test()
}

3. 定义小小计算器结构体(Calcuator),实现加减乘除四个功能实现形式 实现形式 2:用一个方法搞定&p

type Calcuator struct {
	Num1 float64
	Num2 float64
}

func (c *Calcuator) test1(str string) float64 {
	if str == "+" {
		return c.Num1 + c.Num2
	} else if str == "-" {
		return c.Num1 - c.Num2
	} else if str == "*" {
		return c.Num1 * c.Num2
	} else if str == "/" {
		return c.Num1 / c.Num2
	} else {
		return 0.0
	}
}

func main() {
	calcuator := Calcuator{Num1: 1.0, Num2: 1.0}
	test1 := calcuator.test1("+")
	fmt.Println(test1)
}

方法和函数区别

  1. 调用方式不一样

  2. 函数的调用方式: 函数名(实参列表)

  3. 方法的调用方式: 变量.方法名(实参列表)

  4. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然

  5. 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

总结:

  1. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.

  2. 如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则是地址拷贝。

面向对象编程应用实例

步骤

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

  2. 编写结构体的字段

  3. 编写结构体的方法

学生案例

  1. 编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、float64 类型。

  2. 结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。

  3. 在 main 方法中,创建 Student 结构体实例(变量),并访问 say 方法,并将调用结果打印输出。

type Student struct {
	name   string
	gender string
	age    int
	id     int
	score  float64
}

func (s *Student) say() string {
	str := fmt.Sprintf("s 的信息 name=[%v] gender=[%v], age=[%v] id=[%v] score=[%v]",
		s.name, s.gender, s.age, s.id, s.score)
	return str
}

func main() {
	stu := Student{
		name:   "tom",
		gender: "男",
		age:    12,
		id:     1,
		score:  100.0,
	}
	say := stu.say()
	fmt.Println(say)
}

盒子案例

  1. 编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取

  2. 声明一个方法获取立方体的体积。

  3. 创建一个 Box 结构体变量,打印给定尺寸的立方体的体积

type Box struct {
	len    float64
	width  float64
	height float64
}

func (b *Box) geVolume() float64 {
	return b.len * b.width * b.height
}

func main() {
	box := Box{
		len:    1.0,
		width:  2.0,
		height: 3.0,
	}
	fmt.Println(box.geVolume())
}

景区门票案例

  1. 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18,收费 20 元,其它情况门票免费.

  2. 请编写 Visitor 结构体,根据年龄段决定能够购买的门票价格并输出

type Vistor struct {
	Name string
	age  int
}

func (v *Vistor) play() {
	if v.age >= 18 {
		fmt.Println(v.Name, "需要付费", 20)
	} else {
		fmt.Println(v.Name, "需要付费", 0)
	}
}

func main() {
	var v Vistor
	fmt.Println("请输入名字")
	fmt.Scanln(&v.Name)
	fmt.Println("请输入年龄")
	fmt.Scanln(&v.age)

	v.play()
}

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

说明: Golang 在创建结构体实例(变量)时,可以直接指定字段的值

type Stu struct {
	Name string
	Age  int
}

func main() {
	// 方式1 创建结构体的时候直接指定字段
	var stu1 = Stu{"stu1", 1}
	stu2 := Stu{"stu2", 2}
	// 方式2 创建结构体的时候把字段名和字段值放在一起,这样不需要以来字段顺序
	var stu3 = Stu{
		Name: "stu3",
		Age:  3,
	}
	var stu4 = Stu{
		Name: "stu4",
		Age:  4,
	}
	fmt.Println(stu1, stu2, stu3, stu4)
	// 创建指针类型
	var stu5 = &Stu{"stu5", 5}
	stu6 := &Stu{"stu6", 6}
	var stu7 = &Stu{
		Age:  7,
		Name: "stu7",
	}
	stu8 := &Stu{
		Age:  8,
		Name: "stu8",
	}
	fmt.Println(*stu5, *stu6, *stu7, *stu8)
}

工厂模式

说明

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

看一个需求

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

package model
type Student struct { 
    Name string...
}

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

工厂模式来解决问题

如果 model 包的 结构体变量首字母小写,引入后,不能直接使用, 可以工厂模式解决

package model

type student struct {
	Name string
	Age  int
}

func GetStu(name string, age int) *student {
	return &student{
		Name: name,
		Age:  age,
	}
}


package main

import (
	"day13/methodTest6/model"
	"fmt"
)

func main() {
	stu := model.GetStu("tom", 12)
	fmt.Println(*stu)
}

思考题

如果 model 包的 student 的结构体的字段 Age 改成 age,我们还能正常访问吗?又应该如何解决这个问题呢?

package model

type student struct {
	Name string
	age  int
}

func GetStu(name string, age int) *student {
	return &student{
		Name: name,
		age:  age,
	}
}

func (s *student) GetAge() int {
	return s.age
}


package main

import (
	"day13/methodTest6/model"
	"fmt"
)

func main() {
	stu := model.GetStu("tom", 12)
	fmt.Println(*stu)
	fmt.Println("年龄", stu.GetAge())

}

0

评论区