方法
基本介绍
在某些情况下,我们要需要声明(定义)方法。比如 Person 结构体:除了有一些字段外( 年龄,姓名..),Person 结构体还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用方法才能完成。Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型, 都可以有方法,而不仅仅是 struct。
方法的声明和调用
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
对上面的语法的说明:
func (a A) test() {} 表示 A 结构体有一方法,方法名为 test
(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()
}
对上面的总结:
test 方法和 Person 类型绑定
test 方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
func (p Person) test() {}... p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非常相似。
p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以
快速入门
给 Person 结构体添加 speak 方法,输出 xxx 是一个好人
给 Person 结构体添加 jisuan 方法,可以计算从 1+..+1000 的结果, 说明方法体内可以函数一样, 进行各种运算
给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
给 Person 结构体添加 getSum 方法,可以计算两个数的和,并返回结果
方法的调用
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执行过程
说明:
在通过一个变量去调用方法时,其调用机制和函数一样
不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)
方法的声明(定义)
func (recevier type) methodName(参数列表) (返回值列表){
方法体
return 返回值
}
参数列表:表示方法输入
recevier type : 表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
receiver type : type 可以是结构体,也可以其它的自定义类型
receiver : 就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)
返回值列表:表示返回的值,可以多个
方法主体:表示为了实现某一功能代码块
return 语句不是必须的。
方法的注意事项和细节
结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
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)
}
方法的练习
编写一个方法算该矩形的面积(可以接收长 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)
}
方法和函数区别
调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
总结:
不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.
如果是和值类型,比如 (p Person) , 则是值拷贝, 如果和指针类型,比如是 (p *Person) 则是地址拷贝。
面向对象编程应用实例
步骤
声明(定义)结构体,确定结构体名
编写结构体的字段
编写结构体的方法
学生案例
编写一个 Student 结构体,包含 name、gender、age、id、score 字段,分别为 string、string、int、int、float64 类型。
结构体中声明一个 say 方法,返回 string 类型,方法返回信息中包含所有字段值。
在 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)
}
盒子案例
编程创建一个 Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取
声明一个方法获取立方体的体积。
创建一个 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())
}
景区门票案例
一个景区根据游人的年龄收取不同价格的门票,比如年龄大于 18,收费 20 元,其它情况门票免费.
请编写 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())
}
评论区