面向对象之方法2
方法的调用和传参机制原理:(重要!)
说明:方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。下面我们举例说明:
案例1:画出前面getSum 方法的执行过程+说明
说明:
1)在通过一个变量去调用方法时,其调用机制和函数一样。
2)不一样的地方,变量调用方法时,该变量本身也会作为一个参数传递到方法里面去。(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)
案例2:请编写一个程序,要求如下:
1)声明一个结构体Cirle,字段为radius
2)声明一个方法 area 和 Cirle 绑定,可以返回面积。
3)提示:画出area执行过程+说明
方法使用的深度剖析:
方法的声明(定义)
func (recevier type) methodName (参数列表) (返回值列表) {
方法体
return 返回值
}
1)参数列表:表示方法输入
2)recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
3)recevier type:type可以是结构体,也可以是其它的自定义类型
4)recevier type:就是type类型的一个变量(实例),比如:Person结构体 的一个变量(实例)
5)返回值列表:表示返回的值,可以多个
6)方法主图:表示为了实现某一功能代码块
7)return 语句不是必须的
方法注意事项和细节讨论:
1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
2)如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
案例演示:
type Circle struct {
radius float64
}
//为了提高效率,通常我们方法和结构体的指针类型绑定
func (c *Circle) area2() float64 {
//因为c是指针,因此我们标准的访问其字段的方式是 (*c).radius
//return 3.14 * (*c).radius * (*c).radius
//(*c).radius 等价 c.radius
c.radius = 10.0
return 3.14 * c.radius * c.radius
}
func main() {
var c Circle
c.radius = 7.0
//res2 := (&c).area2()
res2 := c.area2()
//编译器底层做了优化 (&c).area2() 等价 c.ares2()
//因为编译器会自动给加上&c
fmt.Println("面积是=", res2)
fmt.Println("c.radius=", c.radius) //10
}
3)Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法
案例:
package main
import (
"fmt"
)
type integer int
func (i integer) print() {
fmt.Println("i=", i)
}
//编写一个方法,可以改变i的值
func (i *integer) change() {
*i = *i + 1
}
func main() {
var i integer = 10
i.print()
i.change()
fmt.Println("i=",i)
}
4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
案例:
type Student struct {
Name string
Age int
}
func (stu *Student) String() string {
str := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age)
return str
}
func main() {
stu := Student{
Name : "tom",
Age : 20,
}
//如果你实现了 *Student 类型的 String方法,就会自动调用
fmt.Println(&stu)
}
方法和函数的区别:
1)调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
2)对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递
案例:
type Person struct {
Name string
}
func test01(p Person) {
fmt.Println(p.Name)
}
func test02(p *Person) {
fmt.Println(p.Name)
}
func main() {
p := Person{"tom"}
test01(p) //因为是值拷贝,所以不能传入地址 test01(*p)这么写会报错的
test02(&p) //因为是地址拷贝,所以不能直接值拷贝,test02(p)这么写也会报错的。
}
3)对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以。
案例:
type Person struct {
Name string
}
func (p Person) test03() { //虽然main函数里是以传入地址的方式,但是在p接收的时候还是值拷贝的,所以下面的jack该的只是方法里的值,而main函数里的值是不会更改的。
p.Name = "jack"
fmt.Println("test03() =", p.Name) //jack
}
func (p *Person) test04() { //因为是指针类型,所以接收的会是一个指针地址,所以在方法里更改name的值,也就是更改main函数里的值。
p.Name = "mary"
fmt.Println("test04() =", p.Name) //mary
}
func main() {
p := Person{"tom"}
p.test03()
fmt.Println("main() p.name=", p.Name) //tom
(&p).test03() //从形式上是传入地址,但本质仍然是值拷贝
fmt.Println("main p.name=", p.Name) //tom
(&p).test04()
fmt.Println("main() p.name=", p.Name) //mary
p.test04() //等价 (&p).test04() 从形式上是传入值类型,但本质仍然是地址拷贝
}
总结:
1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,主要是看这个方法是和哪个类型绑定的。
2)如果是和值类型绑定的,比如 (p Person),则是值拷贝,如果是和指针类型绑定的,比如(p *Person),则是地址拷贝。