面向对象之方法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),则是地址拷贝。

posted @ 2019-08-25 15:52  我是一只忙碌的小青蛙  阅读(168)  评论(0编辑  收藏  举报