6. Go言語の関数

Function は Go の中心的な設計であり、キーワード func を通じて宣言されます。その形式は次のとおりです。

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    
    
    // 这里是处理逻辑代码
    // 返回多个值
    return value1, value2
}

上記のコードからわかるのは、

  • キーワードfunc は、関数 funcName を宣言するために使用されます。
  • 関数には 1 つ以上のパラメータを含めることができ、各パラメータの後には型が続き、,で区切られます。
  • 関数は複数の値を返すことができます
  • 上記の戻り値では、output1とoutput2の2つの変数を宣言していますが、宣言したくない場合は、2つの型をそのまま使用することもできます。
  • 戻り値が 1 つだけで、戻り値変数が宣言されていない場合は、戻り値を囲む括弧を省略できます。
  • 戻り値がない場合、最終的な戻り情報は単に省略されます。
  • 戻り値がある場合は、関数の外に return ステートメントを追加する必要があります。

実際の応用関数の例を見てみましょう(Max 値の計算に使用)

package main
import "fmt"
//  返回 a 、 b 中最大值 .
func max(a, b int) int {
    
    
    if a > b {
    
    
   		return a
    }
    return b
}
func main() {
    
    
    x := 3
    y := 4
    z := 5
    max_xy := max(x, y) // 调用函数 max(x, y)
    max_xz := max(x, z) // 调用函数 max(x, z)
    fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
    fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
    fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) //  也可在这直接调用它
}

上記では、max 関数には 2 つのパラメータがあり、それらの型は int であることがわかります。最初の変数の型は省略できます (つまり、a int、b int ではなく、a、b int)。デフォルトはそのままです
。最も近い型。同じ型の 3 つ以上の変数または戻り値にも同じことが当てはまります。同時に、戻り値が省略された型であることに気付きます。

複数の戻り値

Go 言語の C よりも高度な機能の 1 つは、関数が複数の値を返せることです。

コードに直接アクセスして例を見てみましょう

package main
import "fmt"
// 返回 A+B  和 A*B
func SumAndProduct(A, B int) (int, int) {
    
    
	return A+B, A*B
}
func main() {
    
    
    x := 3
    y := 4
    xPLUSy, xTIMESy := SumAndProduct(x, y)
    fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
    fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}

上の例では、2 つのパラメータが直接返されることがわかります。もちろん、返されるパラメータの変数に名前を付けることもできます。この例では 2 つの型のみが使用されています。次の定義に変更することもできます。変数名は関数内で直接初期化されるためです。ただし、関数がエクスポートされる場合 (最初の文字が大文字になる)、公式の推奨事項は次のとおりです。戻り値に名前を付けるのが最善です。戻り値に名前を付けないとコードはより簡潔になりますが、生成されるドキュメントが読みにくくなります。

func SumAndProduct(A, B int) (add int, Multiplied int) {
    
    
    add = A+B
    Multiplied = A*B
    return
}

可変パラメータ

Go 関数は変数パラメーターをサポートしています。可変個引数パラメーターを受け入れる関数には、無限の数のパラメーターがあります。これを行うには、まず可変引数を受け入れる関数を定義する必要があります。

func myfunc(arg ...int) {
    
    }

arg ...int は、この関数が無限の数の引数を受け入れることを Go に伝えます。これらのパラメータの型はすべて int であることに注意してください。関数本体では、変数 arg は int のスライスです

for _, n := range arg {
    
    
	fmt.Printf("And the number is: %d\n", n)
}

値とポインタによる受け渡し

呼び出される関数にパラメーター値を渡すとき、実際には値のコピーを渡します。呼び出される関数でパラメーター値が変更されても、呼び出し側関数の対応する実際のパラメーターはまったく変更されません。変更されるのは数値のみであるためです。コピーに影響を与えます。

上で述べたことを確認するために、例を見てみましょう

package main
import "fmt"
// 简单的一个函数,实现了参数 +1 的操作
func add1(a int) int {
    
    
    a = a+1 //  我们改变了 a 的值
    return a // 返回一个新值
}
func main() {
    
    
    x := 3
    fmt.Println("x = ", x) //  应该输出 "x = 3"
    x1 := add1(x) // 调用 add1(x)
    fmt.Println("x+1 = ", x1) //  应该输出 "x+1 = 4"
    fmt.Println("x = ", x) //  应该输出 "x = 3"
}

見えますか?add1 関数を呼び出し、add1 で a = a+1 演算を実行しましたが、上記の例の x 変数の値は変化しませんでした。

理由は簡単です。add1 を呼び出すと、add1 が受け取るパラメータは実際には x そのものではなく、x のコピーであるからです。

それでは、x 自体を本当に渡す必要がある場合はどうすればよいのかと疑問に思うかもしれません。

*これにはいわゆるポインタが関係します。変数はメモリ内の特定のアドレスに格納されており、変数を変更すると、実際にはその変数アドレスのメモリが変更されることがわかっています。add1 関数だけが x 変数のアドレスを知っており、x 変数の値を変更できます。したがって、関数内の x 変数の値を変更するには、x のアドレス &x を関数に渡し、関数パラメーターの型を int からint、つまりポインター型に変更する必要があります。この時点でもパラメータはコピーによって渡されますが、コピーはポインタです。

以下の例を参照してください

package main
import "fmt"
// 简单的一个函数,实现了参数 +1 的操作
func add1(a *int) int {
    
     //  请注意,
    *a = *a+1 //  修改了 a 的值
    return *a //  返回新值
}
func main() {
    
    
    x := 3
    fmt.Println("x = ", x) //  应该输出 "x = 3"
    x1 := add1(&x) //  调用 add1(&x)  传 x 的地址
    fmt.Println("x+1 = ", x1) //  应该输出 "x+1 = 4"
    fmt.Println("x = ", x) //  应该输出 "x = 4"
}

このようにして、x を変更するという目的を達成します。では、ポインタを渡すことの利点は何でしょうか?

  • ポインタを渡すと、複数の関数が同じオブジェクト上で動作できるようになります。
  • ポインタの受け渡しは比較的軽量 (8 バイト) で、メモリ アドレスのみを渡すため、ポインタを使用して大きな構造体を渡すことができます。パラメータ値によって渡される場合、各コピーに比較的多くのシステム オーバーヘッド (メモリと時間) が費やされます。したがって、大きな構造体を渡したい場合は、ポインターを使用するのが賢明な選択です。
  • Go 言語における文字列、スライス、マップの 3 種類の実装メカニズムはポインタに似ているため、アドレスを取得してポインタを渡す代わりに、これらを直接渡すことができます。(注: 関数がスライスの長さを変更する必要がある場合でも、アドレスを取得してポインターを渡す必要があります)

延期する

Go 言語には defer ステートメントという優れた設計があり、関数に複数の defer ステートメントを追加できます。関数が最後まで実行されると、これらの defer ステートメントが逆の順序で実行され、最後に関数が戻ります。特に、リソースをオープンする操作を実行中にエラーが発生し、早期に復帰する必要がある場合は、復帰する前に対応するリソースをクローズする必要があります。そうしないと、リソース リークなどの問題が発生しやすくなります。

次のコードに示すように、通常は次のようなリソースを作成して開きます。

func ReadWrite() bool {
    
    
    file.Open("file")
    //  做一些工作
    if failureX {
    
    
        file.Close()
        return false
    }
    if failureY {
    
    
        file.Close()
        return false
    }
    file.Close()
    return true
}

上記には重複したコードが多数あることがわかりますが、Go の遅延はこの問題を効果的に解決します。これを使用すると、コードの量が大幅に削減されるだけでなく、プログラムがよりエレガントになります。defer の後に指定された関数は、関数が終了する前に呼び出されます。

func ReadWrite() bool {
    
    
    file.Open("file")
    defer file.Close()
    if failureX {
    
    
    	return false
    }
    if failureY {
    
    
    	return false
    }
    return true
}

defer の呼び出しが多数ある場合、defer は後入れ先出しモードを使用するため、次のコードは 4 3 2 1 0 を出力します。

for i := 0; i < 5; i++ {
    
    
	defer fmt.Printf("%d ", i)
}

値、型としての関数

Go では、関数も変数の型であり、型を通じて定義できます。その型は、同じパラメータと同じ戻り値を持つ型です。

type typeName func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])

型としての関数の利点は何ですか? つまり、このタイプの関数は値として渡すことができます。次の例を参照してください。

package main
import "fmt"
type testInt func(int) bool //  声明了一个函数类型
func isOdd(integer int) bool {
    
    
    if integer%2 == 0 {
    
    
    	return false
	}
	return true
}
func isEven(integer int) bool {
    
    
    if integer%2 == 0 {
    
    
    	return true
    }
    return false
}
//  声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
    
    
    var result []int
    for _, value := range slice {
    
    
        if f(value) {
    
    
        	result = append(result, value)
        }
	}
	return result
}
func main(){
    
    
    slice := []int {
    
    1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd) //  函数当做值来传递了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven) //  函数当做值来传递了
    fmt.Println("Even elements of slice are: ", even)
}

値と型としての関数は、一般的なインターフェイスを作成するときに非常に役立ちます。上記の例から、型 testInt が関数型であり、2 つのフィルター関数のパラメーターと戻り値が同じであることがわかります。 testInt 型ですが、さまざまな種類のロジックを実装できるため、プログラムは非常に柔軟になります。

main関数とinit関数

Go には、init 関数 (すべてのパッケージに適用できる) と main 関数 (パッケージ main にのみ適用できる) の 2 つの予約関数があります。これら 2 つの関数は、定義時にパラメーターや戻り値を持つことができません。パッケージ内には任意の数の init 関数を作成できますが、読みやすさと将来の保守性の両方を考慮して、パッケージ内のファイルごとに init 関数を 1 つだけ作成することを強くお勧めします。

Go プログラムは自動的に init() と main() を呼び出すため、これら 2 つの関数をどこでも呼び出す必要はありません。各パッケージの init 関数はオプションですが、パッケージ main には main 関数が含まれている必要があります。

プログラムの初期化と実行はメイン パッケージから開始されます。メイン パッケージが他のパッケージもインポートする場合、それらはコンパイル中に順番にインポートされます。場合によっては、パッケージが複数のパッケージによって同時にインポートされるため、インポートされるのは 1 回だけになります (たとえば、多くのパッケージで fmt パッケージが使用される可能性がありますが、複数回インポートする必要がないため、インポートされるのは 1 回だけになります)回)。パッケージがインポートされるときに、そのパッケージが他のパッケージもインポートする場合は、他のパッケージが最初にインポートされ、次にこれらのパッケージ内のパッケージレベルの定数と変数が初期化され、次に init 関数 (存在する場合) が実行されます。 .)、など。インポートされたパッケージがすべてロードされた後、メイン パッケージ内のパッケージ レベルの定数と変数が初期化され、次にメイン パッケージ内の init 関数 (存在する場合) が実行され、最後に main 関数が実行されます。

輸入

Goのコードを書く際、パッケージファイルをインポートするためにimportコマンドをよく使いますが、よく見かけるのは以下のような方法です。

import(
	"fmt"
)

次に、コード内で次の方法で呼び出すことができます。

fmt.Println("hello world")

上記の fmt は Go 言語の標準ライブラリですが、実際には goroot にアクセスしてモジュールをロードする必要がありますが、もちろん Go の import では自分で書いたモジュールをロードする次の 2 つの方法もサポートしています。

  • 相対パス
    import "./model" //現在のファイルと同じディレクトリにあるモデル ディレクトリですが、このインポート方法は推奨されません。

  • 絶対パス
    import "shorturl/model" // gopath/src/shorturl/model モジュールをロードする

上記は一般的に使用されるインポート方法をいくつか示しましたが、多くの初心者には理解が難しい特殊なインポートもあります。

  • 「操作」をクリックします。
    パッケージをインポートする次の方法が表示される場合があります。

    import(
    	. "fmt"
    )
    

    この操作の意味は、パッケージがインポートされた後、このパッケージの関数を呼び出すときに、接頭辞付きのパッケージ名を省略できる、つまり、前に呼び出した fmt.Println("hello world") を省略できるということです。 Println("hello world") world") として書かれます。

  • エイリアス操作 名前が
    示すように、パッケージに覚えやすい別の名前を付けることができます。

    import(
    	f "fmt"
    )
    

    エイリアス操作の場合、このプレフィックスは、パッケージ関数を呼び出すときのプレフィックスになります。つまり、f.Println("hello world")

  • _Operation
    この演算は、多くの人にとって混乱を招く演算子です。次のインポートを参照してください。

    import (
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
    )
    

    _ 操作は実際にパッケージをインポートします。パッケージ内の関数を直接使用するのではなく、パッケージ内の init 関数を呼び出します。

おすすめ

転載: blog.csdn.net/u012534326/article/details/120028914