関数
Go は通常の関数、無名関数、クロージャ、メソッドを提供します。
関数は第一級の値で、変数に代入したり、引数や戻り値にしたりできます。
メソッドを持つ定義された関数型は、インターフェースを満たせます。
1. 関数を宣言する
基本の形式:
func 函数名(参数列表) (返回值列表) {
函数体
}
引数は省略でき、複数ならカンマで区切ります。
戻り値も省略でき、複数の値を返せます。戻り値のある関数では、通常の終了経路でそれらを返す必要があります。
簡単な例:
func add(a, b int, c float32) (int, error) {
return a + b, nil
}
Go は値渡しで、引数のコピーを受け取ります。引数への再代入では、呼び出し側の変数は変わりません。
スライスの記述子は、コピー後も基底配列を共有する場合があります。共有要素の変更は、呼び出し側に見えます。
拡張で新しい基底配列が必要になった場合、その新しい配列への変更は、古い配列には影響しません。
戻り値に名前を付けることもできます。関数内の変数になるので、別の宣言は不要です。
例:
func add(a, b int) (sum int, err error) {
sum = a + b
return sum, err
//也可以直接写return
}
2. 可変長引数
渡す値の数が変わるときは、可変長引数を使います。
基本の形式:
func 函数名(参数列表...类型) (返回值列表) {
函数体
}
簡単な例:
func add(a ...int)(sum int, err error) {
for _, value := range a {
sum += value
}
}
c := 1
d := 2
sum, _ := add(a, b, 3, 4)
fmt.Println(sum)
関数内の a は int のスライスです。a[0]、a[1] やループで要素にアクセスできます。
3. 第一級の値としての関数
関数を引数、戻り値、変数として扱えます。
柔軟性と再利用性を高めます。
3.1. 変数へ代入する
例:
func add(a ...int)(sum int, err error) {
for _, value := range a {
sum += value
}
}
func main(){
//这里将add函数作为参数赋值给变量funcVar
funcVar := add
c := 1
d := 2
sum, _ := funcVar(a, b, 3, 4)
fmt.Println(sum)
}
3.2. 関数を返す
例:
func cal(op string) func(){
switch op {
case "+":
return func(){
fmt.Println("这是加法")
}
case "-":
return func(){
fmt.Println("这是减法")
}
default:
return func(){
fmt.Println("这不是加法也不是减法")
}
}
}
//因为返回的是函数,所以在调用的时候需要加上()
cal("+")()
3.3. 関数を引数にする
書き方を示すための簡単な例です。
func add(a, b int) int{
fmt.Printf("sum is %d\n", a + b)
}
func cal(y int, myfunc func(int, int)) {
myfunc(y, 2)
}
cal(1, add)
//输出结果为sum is 3
//调用cal函数传递add函数成为myfunc, 然后1+2
3.4. 無名関数
宣言名のない関数で、値として渡したり返したりできます。
値が必要な場所で直接定義できます。
//匿名函数作为参数进行传递
func add(a, b int) int{
fmt.Printf("sum is %d\n", a + b)
}
func cal(y int, myfunc func(int, int)) {
myfunc(y, 2)
}
cal(1, func(a, b int){
fmt.Printf("total is %d\n", a + b)
})
//输出结果为total is 3
//调用cal函数传递临时定义的匿名函数函数成为myfunc, 然后1+2
//匿名函数作为变量进行赋值
func cal(y int, myfunc func(int, int)) {
myfunc(y, 2)
}
localFunc := func(a, b int){
fmt.Printf("local is %d\n", a + b)
}
cal(1, localFunc)
//输出结果为local is 3
//调用cal函数传递localFunc成为myfunc, 然后1+2,跟上边的效果是一样的
4. クロージャ
呼び出すたびにカウンタを増やす関数を考えます。
一つの方法は、パッケージ変数を増やして返すことです。
パッケージ変数を使う例:
var local int
func add() int{
local += 1
return local
}
for i := 0; i < 5; i++ {
fmt.Println(add())
}
共有されたグローバル状態ができるため、独立したカウンタやリセットを扱いにくくなります。
クロージャを使う方法があります。
func auotoAdd() func() int {
local := 0
return func() int {
return local
}
}
nextFunc := auotoAdd()
for i := 0; i < 5; i++ {
fmt.Println(nextFunc())
}
//输出结果为0 1 2 3 4
//想要归零的话,只需要重新调用auotoAdd()函数即可
クロージャは、外側の関数が終了した後も、周囲のスコープの変数へアクセスできる関数です。
例えば、無名関数が外側の関数のローカル変数を捕捉します。
5. defer の用途
defer は、関数が戻る際に呼び出す処理を登録します。資源の後処理によく使います。目的は finally に似ていますが、動作の規則は異なります。
ファイルやロックなどは、成功・失敗にかかわらず後処理しないと、資源が漏れる場合があります。
// 一个在Java中的例子
try {
// 可能会发生异常的代码
} catch (Exception e) {
// 异常处理代码
} finally {
// 无论是否发生异常,都会执行的代码
}
Java の finally は取得処理から離れることがあります。Go の defer は、取得の近くに後処理を書けます。
基本の使い方:
var mu sync.Mutex
mu.lock()
defer mu.unlock() //defer后面的代码会在return之前执行
取得の直後に defer を置くと、後処理を確認しやすくなります。
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("4")
//输出结果为4 3 2 1
defer はスタックのように、後から登録した順に実行されます。
遅延実行するクロージャから、名前付きの戻り値を変更できます。
// 在这个例子中,defer 推迟执行的是一个匿名函数
// deferReturn 函数被调用,准备返回值 10。
// 在返回之前,defer 推迟执行的匿名函数被执行。
// 匿名函数中,result 被增加 1,因此 result 的值从 10 变为 11。
// deferReturn 函数返回修改后的 result 值,即 11。
// 因此,尽管 deferReturn 函数的返回语句是 return 10,但由于 defer 推迟执行的函数在返回之前修改了 result 的值,所以最终的输出结果是 11。这也展示了在 Go 语言中,defer 可以修改函数的返回值。
// 搞不懂就看源码
func deferReturn() (result int){
defer func(){
result++
}()
return 10
}
ret := deferReturn()
fmt.Println(ret) //输出结果为11
6. error、recover、panic
重要な概念は三つです。
1、error
2、panic
3、recover
通常の失敗には error 値を使い、例外で普通の制御フローを組み立てないようにします。
他の言語では、処理を try-catch で囲むことがあります。 {: .prompt-tip }
Go の関数は、通常 error を返して成功・失敗を知らせます。呼び出し側が扱うため、「if err != nil」がよく現れます。
明示的なエラー処理は、防御的な実装の一部です。
panic は通常の error と別です。現在の goroutine を巻き戻し、defer を実行します。適切な defer 内の recover で停止できますが、回復しなければプログラムが終了します。
6.1、error
errors パッケージで error 値を作れます。例:
import (
"fmt"
"errors"
)
func A() (int, error) {
return 1, errors.New("这是一个错误")
}
func main() {
if _, err := A(); err!= nil {
fmt.Println(err)
}
}
6.2、panic
panic は組み込みです。回復されない panic はプログラムを終了させるため、通常の error の代わりには使いません。
別の例:
import (
"fmt"
"errors"
)
func A() (int, error) {
panic("this is a panic")
fmt.Println("能执行到这吗?")
return 1, errors.New("这是一个错误")
}
func main() {
if _, err := A(); err!= nil {
fmt.Println(err)
}
}
// 在上面的例子中,panic会导致程序退出,所以fmt.Println("能执行到这吗?")不会被执行,只会打印错误栈
panic は慎重に使います。ここでは起動時の失敗を考えます。
サービスは起動前に、設定、ログ、データベースなどの必要な依存を準備します。
必須の条件を満たせないときは、明確に起動を失敗させます。panic も一つの方法ですが、error を返すだけで十分な場合もあります。
稼働中のサーバーで未回復の panic が起きると、プロセスが停止し、重大な障害になります。
実行時の検査からも、例えば次の場合に panic が起きます。
インデックスが範囲外
nil ポインタの間接参照
……
適切な defer の中で recover を呼ぶと、panic を停止できます。ただし、panic が起きた文から再開するわけではありません。
recover は、panic に渡された値をインターフェース値として返します。
6.3、recover
例:
// 在go中map的使用需要进行初始化,否则会报错,这是一个会被动触发panic的场景
func A() {
defer func(){
if err := recover(); err!= nil {
fmt.Println("recoverd is A(): ", err)
}
}()
var names map[string]string
names["xiaoming"] = "go工程师"
return 0, errors.New("这是一个错误")
}
// 上面的代码会输出recoverd is A(): assignment to entry nil map
ほかの注意点:
panic が起きた goroutine の defer 関数から、直接 recover を呼びます。
panic より前に defer を登録しておく必要があります。
回復しても panic の場所から処理は再開しません。
複数の defer は、登録と逆の順序で実行されます。