在上一节,我们学习了定义一个结构体,比如一只猫,它有自己的名字、年龄、花色,然而这些都是一些静态信息,猫猫还会有很多的行为动作,比如奔跑、恰饭、鄙视你等等,而这些动作的实现就依赖于方法了
golang中的方法是作用在指定的数据类型上的。 即:和指定的数据类型绑定,因此自定义的数据类型都可以有方法,不仅仅是结构体有)
方法声明的格式
func (变量 自定义数据类型) 方法名(){
代码块
}
案例
package main
import (
"fmt"
)
type Person struct{
Num int
}
func (p Person) test(){ //这里的意思是将test这个方法绑定了Person的结构体
fmt.Println(p.Num) //可以通过调用Person结构体来引用方法
}
func main(){
var p Person = Person{10} //声明结构体
p.test() //因为上面Person结构体绑定了test方法,所以可以直接通过"."来调用方法,
//在调用该方法时,会将main下的p变量传参到test方法中绑定的Person结构体前面的形参上
}
返回
10
小结
1. test方法和Person类型进行绑定
2. test方法只能通过Person类型的变量来调用,而不能直接调用,也不能用其他类型的变量来调用
3. main中 p 变量的名称不需要和func 方法定义传参一样
4. func 方法中p 表示那个Person变量调用,这个p就是他的一个副本 值传递,方法中的操作不会影响外部的值,因为这是他的副本值拷贝
5. func 方法中的p 可以自定义
一、快速入门
给Person结构体绑定一个"speak"方法,调用该方法输出"xxx是一个好人"
package main
import (
"fmt"
)
type Person struct{
Name string
}
func (p Person) test(){
fmt.Println(p.Name)
}
//添加speak 方法
func (p Person) speak(){
fmt.Printf("%v 是一个好人",p.Name)
}
func main(){
var p Person = Person{"test"}
p.speak() //调用方法
}
案例1 调用方法打印矩形
编写结构体MethodUtils 编写一个方法,不需要字段,在方法中打印一个10*8的矩形,在main方法中调用该方法
package main
import "fmt"
type MethodUtils struct {
//结构体 可以不写字段也可以使用
}
func (mu MethodUtils) Print(){
for i := 0; i < 10;i++{
for k :=0; k < 8; k++{
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
mu := MethodUtils{}
mu.Print()
}
返回
********
********
********
********
********
********
********
********
********
********
案例2 给定方法参数打印矩形
编写一个方法,提供m和n的参数,输出一个m*n的矩形
package main
import "fmt"
type MethodUtils struct {
//可以不写字段也可以使用
}
func (mu MethodUtils) Print(m int,n int){ //给方法设置接收值
for i := 0; i < m;i++{
for k :=0; k < n; k++{
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
mu := MethodUtils{}
mu.Print(10 , 81) //在调用函数时传入参数
}
案例3 编写方法计算矩形面积
编写一个方法 算该矩形的面积,将其作为方法返回值。在main方法中调用该方法,接收返回的面积值,并打印
package main
import "fmt"
type MethodUtils struct {
//可以不写字段也可以使用
}
func (mu MethodUtils) area(len float64, width float64) (mianji float64) { //设置返回值类型为float64
mianji = len * width //当返回值有具体的变量名称时需要圆括号括起来
return
}
func main() {
mu := MethodUtils{}
areaRes := mu.area(2.5 , 8.3)
fmt.Println("面积为", areaRes)
}
案例4 定义方法判断奇数偶数
编写一个方法,判断一个数是奇数还是偶数
package main
import "fmt"
type MethodUtils struct {
//可以不写字段也可以使用
}
func (mu *MethodUtils) JudgeNum(num int){ //为了使得我们这个方法可以修改外部的值
if num % 2 == 0 { //所以这里将类型设置为指针类型
//如果整除以2 等于0 那么就是整数
fmt.Println("偶数")
}else {
fmt.Println("奇数")
}
}
func main() {
mu := MethodUtils{} //我们上面方法说明接收的类型是一个指针类型
mu.JudgeNum(13) //正常应该写(&mu).JudgeNum(13) 来调用的
//但编辑器做了优化,不用写也是可以用的
(&mu).JudgeNum(13) //两种方法都写上
}
返回
奇数
奇数
案例5 编写方法打印指定行列字符
根据行、列、字符打印对应行数和列数的字符 比如 行3 列2 字符"s"
package main
import "fmt"
type MethodUtils struct {
//可以不写字段也可以使用
}
func (mu *MethodUtils) Print(n int, m int , key string){
for i := 1; i <= n; i++{
for j := 1; j <= m; j++{
fmt.Print(key)
}
fmt.Println()
}
}
func main() {
mu := MethodUtils{}
mu.Print(7,3,"sss")
}
返回
sssssssss
sssssssss
sssssssss
sssssssss
sssssssss
sssssssss
sssssssss
案例6 定义方法实现加减乘除
定义一个小小计算器的结构体(Calcuator) 实现加减乘除的功能
package main
import "fmt"
//计算机要传入两个数,
//方法1 写入结构体, 在结构体中定义两个数据类型 xxx int
//然后传参给结构体,在调用方法时使用
//方法2 直接在方法中设置传参,直接调用变量
type Calcuator struct {
Num1 float64
Num2 float64
}
func (calcuator *Calcuator) getSum(operator byte) float64 {
res := 0.0
switch operator {
case '+':
res = calcuator.Num1 + calcuator.Num2
case '-':
res = calcuator.Num1 - calcuator.Num2
case '*':
res = calcuator.Num1 * calcuator.Num2
case '/':
res = calcuator.Num1 / calcuator.Num2
default:
fmt.Println("运算符输入有误")
}
return res
}
func main() {
calcuator := Calcuator{}
calcuator.Num1 = 1.2
calcuator.Num2 = 2.2
res := calcuator.getSum('*') //在传参时提供一个运算符
fmt.Println("res",res)
}
返回
res 2.64
案例7 九九乘法表 封装为方法
先实现99乘法表
package main
import "fmt"
func main() {
for i := 1; i < 10; i++ {
for k := 1 ; k <= i; k++ {
fmt.Print(k ,"*", i , "=",i * k," ")
}
fmt.Println()
}
}
封装
package main
import "fmt"
type MethodUtils struct {
max int
}
func (methodUtils MethodUtils) jiujiu(max int) {
//将上限设置为max的最大值
for i := 1; i <= max; i++ {
for k := 1; k <= i; k++ {
fmt.Print(k, "*", i, "=", i*k, " ")
}
fmt.Println()
}
}
func main() {
methodUtils := MethodUtils{}
methodUtils.jiujiu(9) //调用
}
返回
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
//可以通过指定上限值来定义打印到多少上限
二、方法和函数的区别
1、函数 可以使用函数名称(实参列表) 来调用
方法 需要先指定一个变量,一个与方法绑定的变量(实参)进行调用
2.对于普通函数,接收者为值类型时,不能将指针类型的数据之间传递,反之亦然
3. 对于方法(如 struct 的方法) 接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样可以
案例1 函数不支持 值与指针的传递
package main
import "fmt"
type Person struct {
Name string
}
func test01(p Person){ //对于普通函数,接收者为值类型时,不能将指针类型的数据之间传递,反之亦然
fmt.Println(p.Name)
}
func main(){
p := Person{"tom"}
test01(p)
test01(&p) //报错了, 因为在函数中,如果没有定义接收的值是一个指针,那么是无法传递一个地址的
}
案例2 方法支持 值与指针的传递
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) test03() { //可以看到,我这里使用值类型的变量
// 对于方法(如 struct 的方法) 接收者为值类型时,可以直接用指针类型的变量调用方
//反过来同样可以,意思是下面调用的时候&p是可以的,这里的p *Person定义都是成功的
//即可以使用指针类型传,也可以使用值类型传
p.Name = "jack"
fmt.Println(p.Name)
}
func main(){
p := Person{"tom"}
p.test03() //我们发现这里可以使用值传递 和 指针传递 效果是一样的
(&p).test03() //这里的(&p) 和函数不同,他也是值拷贝,方法
fmt.Println(p.Name) //输出全局的变量
}
总结
不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和那个类型绑定的
如果是和值类型绑定的,比如 (p Persion)则是值拷贝, 如果是和值类型,比如(p *Person)则是地址拷贝
小练习
1、学生案例
1. 编写一个Student的结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64类型
2. 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值
3. 在main方法中,创建Student结构体实例(变量),并访问say方法,并将调用结果打印输出
代码
package main
import "fmt"
type Student struct {
name string
gender string
age int
id int
score float64
}
//Printf 和Sprintf的区别
//Printf用传入的格式化规则符将传入的变量写入到标准输出里面(即在终端中有显示),
//用传入的格式化规则符将传入的变量格式化,(终端中不会有显示) 返回为 格式化后的字符串。
func (student Student) say() string {
infoStr := fmt.Sprintf("student的信息 name=[%v] gender=[%v] age=[%v] id=[%v] score=[%v]",
student.name,student.gender,student.age,student.id,student.score)
return infoStr
}
func main(){
student :=Student{
name: "tom",
gender : "male",
age : 18,
id : 1000,
score : 99.98,
}
str := student.say()
fmt.Println(str)
}
2、盒子案例
1. 编程创建一个Box结构体,在其中声明三个字段,表示一个立方体的长、宽、搞,长宽高从终端获取
2. 声明一个方法获取立方体的体积
3. 创建一个Box 结构体变量,打印给定尺寸的立方体的体积
代码
package main
import "fmt"
type Box struct {
len float64
width float64
height float64
}
func (box *Box) getVolumn() float64 {
return box.len * box.width * box.height
}
func main(){
box := Box{}
box.len = 1.1
box.width = 2.0
box.height = 3.0
volumn := box.getVolumn()
fmt.Printf("%.2f",volumn)
}