面向对象之方法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),则是地址拷贝。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号