go语言入门之-函数和方法

1,070 阅读4分钟

函数

函数声明

函数生成包含函数的名字,形参列表,返回值列表(可选)以及函数体构成.

func name(parameter-list) (result-list) {
  body
}

需要注意一下几点:

  1. 函数的形参列表和返回值列表组成函数的签名,函数的签名会在函数被调用的时候做校验是否调用合法.

  2. 参数的传递是按值传递的.

  3. 支持多返回值.

  4. 函数变量是有类型的,不符合函数签名类型的调用会报错

     func changeArr(a [3]int) {
       a[0] = 100
     }
     func getArr(a [3]int) (int, int) {
         return a[0], a[1] // 多返回值
     }
     func main() {
       test := [3]int{1,2,3}
       changeArr(test)
       fmt.Println(test[0]) // 1 数组是基本类型 值传递不会改变原数组
       a, b := getArr(test); // 1, 2
     }
     // 当形参的基本类型的时候,不会修改外部的值.当形参是引用类型的时候,有可能会修改外部的值.
    

变长函数声明

通过在参数列表最后的类型名称之前使用省略号来声明一个变长函数声明.

func log(vals ...int) {
  for _, value := range vals {
    fmt.Println(value)
  }
}
func main() {
  b := []int{1,2,3}
  log(b...) // 1, 2, 3
  log(1,2,3) // 1, 2, 3
}

函数变量(匿名函数)

通过在func关键字后不指定函数的名字可以声明函数变量,这种方式函数能获取整个词法环境(外部的变量).

func add() func() int {
  var x int;
  return func() int {
    x++
    return x
  }
}
func main() {
  f := add()
  fmt.Println(f()) // 1
  fmt.Println(f()) // 2
}

错误处理机制

go语言通过普通的值来报告错误.常规的错误是开发者可以预见并且决定错误的行为的.这样得到的错误信息由于没有相应的堆栈信息而更加清晰.

错误传递

调用者在调用函数发生错误的时候,在错误信息上添加更多的调用信息传递给上层.

func test2()([]int, error) {
  return nil, errors.New("test2")
}
func test1() ([]int, error) {
  ret, err := test2()
  if err != nil {
    return nil, fmt.Errorf("test1 call test2 %v", err)
  }
  return ret, nil;
}
func main() {
  _, err := test1()
  fmt.Println(err); // test1 call test2 test2
}

defer

defer语句是普通的函数调用,defer语句能确保函数的return语句或函数执行完毕之后执行对应的defer函数.

func log() func() {
  fmt.Println("start")
  return func() { fmt.Println("end") }
}
func main() {
  defer log()()
  fmt.Println("test defer")
}
// 输出 start test defer end
注意点
  1. defer执行匿名函数会获取当前的词法环境,有可能修改函数执行的结果.

  2. defer语句能保证函数执行完执行,某些情况会导致资源无法释放.

     func readFills (filenams []string) {
       for _, filename := range filenams {
         f, err := os.Open(filename)
         defer f.Close()
       }
     }
     // 上面的例子会导致文件描述符被消耗无法释放,可以在进行单独的封装来控制defer对资源的释放.  
    

方法

方法声明

方法是声明特定类型(对象)上可以执行的函数. 通常可以使用如下的方式声明:

func (p structName) funcName(parameter-list) (result-list) {
  body
}  
// p 特定的类型(接受者)  声明可以在p类型上调用funcName的方法 

注意:

  1. 由于方法的调用是p.funcName和获取p结构体上的属性一致,要注意同一类型上的命名冲突.

指针接收者方法

由于方法会复制实参,当需要方法的调用对外界产生影响的时候,就需要通过指针类型来完成方法的声明,如下面的例子:

type Point struct {
  x, y int
}

func (p *Point) ScaleBy(factor int) {
  p.x *= factor
  p.y *= factor
}

func main() {
  p := &Point{10, 10} // 获取指针
  p.ScaleBy(2) // p{20, 20}
  q := Point{1,2}
  q.ScaleBy(3)  // q{3,6} 当类型符合的时候,会进行隐式转换 相当于 (&q).ScaleBy(3)
}  

方法变量和方法表达式

方法变量

可以将一个特定类型的方法赋值给一个变量,这个变量称为方法变量.该方法变量已绑定到特定的接收者上(caller),通过传递形参就可以完成方法的调用.通常用于绑定特定的接受者.

type Point struct {
  x, y int
}

func (p *Point) ScaleBy(factor int) {
  p.x *= factor
  p.y *= factor
}

func main() {
  p := &Point{10, 10}
  scaleBy := p.ScaleBy
  scaleBy(2) // p{20, 20}
}  

方法表达式

type Point struct {
  x, y int
}

func (p *Point) ScaleBy(factor int) {
  p.x *= factor
  p.y *= factor
}

func main() {
  p := &Point{10, 10}
  scaleBy := (*Point).ScaleBy // 方法表达式
  scaleBy(p,2)
}