【Golang】函数

Posted by 西维蜀黍 on 2020-03-15, Last Modified on 2021-09-21

函数定义

A function type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is nil.

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

比如:

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Example:

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

函数命名规范

由于Golang的特殊性(用大小写来控制函数的可见性),除特殊的性能测试与单元测试函数之外,都应该遵循如下原则:

  1. 使用驼峰命名
  2. 如果包外不需要访问请用小写开头的函数
  3. 如果需要暴露出去给包外访问需要使用大写开头的函数名称

一个典型的函数命名方法如下:

// 注释一律使用双斜线, 对象暴露的方法
func (*fileDao) AddFile(file *model.File) bool {
   result := db.NewRecord(*file)
   if result {
      db.Create(file)
   }
   return result
}
 
// 不需要给包外访问的函数如下
func removeCommaAndQuote(content string) string {
   re, _ := regexp.Compile("[\\`\\,]+")
   return strings.TrimSpace(re.ReplaceAllString(content, ""))
}

Receiver - 类的函数

golang 中存在receiver 的概念,receiver 名称应该尽量保持一致

type A struct{}

func (a *A) methodA() {
}
func (a *A) methodB() {
    a.methodA()
}

Pointer receivers

You can declare methods with pointer receivers.

This means the receiver type has the literal syntax *T for some type T. (Also, T cannot itself be a pointer such as *int.)

For example, the Scale method here is defined on *Vertex.

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

Try removing the * from the declaration of the Scale function on line 16 and observe how the program’s behavior changes.

With a value receiver, the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.) The Scale method must have a pointer receiver to change the Vertex value declared in the main function.

package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}


//output
50

可变长度参数

The final incoming parameter in a function signature may have a type prefixed with .... A function with such a parameter is called variadic and may be invoked with zero or more arguments for that parameter.

Summary Usage

支持可变长参数列表的函数可以支持任意个传入参数,比如 fmt.Println 函数就是一个支持可变长参数列表的函数。

package main

import "fmt"

// 这个函数可以传入任意数量的整型参数
func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}

func main() {
    // 支持可变长参数的函数调用方法和普通函数一样
    // 也支持只有一个参数的情况
    sum(1, 2)
    sum(1, 2, 3)

    // 如果你需要传入的参数在一个切片中,像下面一样
    // "func(slice...)"把切片打散传入
    nums := []int{1, 2, 3, 4}
    sum(nums...)
}

输出结果为

[1 2] 3
[1 2 3] 6
[1 2 3 4] 10

需要注意的是,可变长参数应该是函数定义的最右边的参数,即最后一个参数。

一个简单可变参数函数

这个函数返回经过空格连接以后的参数形成的字符串。

func toFullname(names ...stirng) string {
  return strings.Join(names, " ")
}

你可以不传或传入更多的参数

toFullname("carl", "sagan")

// output: "carl sagan"

toFullname("carl")

// output: "carl"

toFullname()

// output: ""

切片和可变参数函数

可变参数函数会在其内部创建一个”新的切片”。事实上,可变参数是一个简化了切片类型参数传入的语法糖

不传参数

当你不传入参数的时候,可变参数会成为一个空值切片( nil )。

传入已有的切片

你可以通过向一个已有的切片添加可变参数运算符 ”…“ 后缀的方式将其传入可变参数函数。

names := []string{"carl", "sagan"}

toFullname(names...)

// output: "carl sagan"

这就好比通常的传参方式:

toFullname("carl", "sagan")

一些切片传入后的特异表现

假设你传入了一个已有的切片到某可变参数函数:

dennis := []string{"dennis", "ritchie"}

toFullname(dennis...)

假设这个函数在内部改变了可变参数的第一个元素,譬如这样:

func toFullname(names ...string) string {
  names[0] = "guy"
  return strings.Join(names, " ")
}

而这个修改会影响到源切片,”dennis“ 现在的值是:

[]string{"guy", "ritchie"}

而非最初:

[]string{"dennis", "ritchie"}

这是因为,传入的切片和函数内部使用的切片共享同一个底层数组,因此在函数内部改变这个数组的值同样会影响到传入的切片:

如果你直接传入参数(不使用切片),自然就不会产生这个现象了。

同类型函数参数

When two or more consecutive named function parameters share a type, you can omit the type from all but the last.

package main

import "fmt"

func add(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

In this example, we shortened

x int, y int

to

x, y int

函数返回多个值

Go 函数可以返回多个值,例如:

实例

package main

import "fmt"

func swap(x, y string) (string, string) {
  return y, x
}

func main() {
  a, b := swap("Google", "Runoob")
  fmt.Println(a, b)
}

Named return values

Go’s return values may be named. If so, they are treated as variables defined at the top of the function.

These names should be used to document the meaning of the return values.

A return statement without arguments returns the named return values. This is known as a “naked” return.

Naked return statements should be used only in short functions, as with the example shown here. They can harm readability in longer functions.

package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

Example

func Open(dialect string, args ...interface{}) (db *DB, err error) {
	if len(args) == 0 {
		err = errors.New("invalid database source")
		return nil, err
	}
	var source string
	var dbSQL SQLCommon
	var ownDbSQL bool

	switch value := args[0].(type) {
	case string:
		var driver = dialect
		if len(args) == 1 {
			source = value
		} else if len(args) >= 2 {
			driver = value
			source = args[1].(string)
		}
		dbSQL, err = sql.Open(driver, source)
		ownDbSQL = true
	case SQLCommon:
		dbSQL = value
		ownDbSQL = false
	default:
		return nil, fmt.Errorf("invalid database source: %v is not a valid type", value)
	}

	db = &DB{
		db:        dbSQL,
		logger:    defaultLogger,
		callbacks: DefaultCallback,
		dialect:   newDialect(dialect, dbSQL),
	}
	db.parent = db
	if err != nil {
		return
	}
	// Send a ping to make sure the database connection is alive.
	if d, ok := dbSQL.(*sql.DB); ok {
		if err = d.Ping(); err != nil && ownDbSQL {
			d.Close()
		}
	}
	return
}

Reference