golang のジェネリックについての深い理解 (Generic)
この記事は、golang のジェネリックスについての深い理解をもたらします。ジェネリック医薬品の使い方は?一定の参考値があるので、困っている友人は参考にしていただければ幸いです。
ジェネリックスとは
ジェネリックス (ジェネリック) はプログラミング テクノロジです。厳密に型指定された言語では、後で指定される型を使用してコードを記述し、インスタンス化時に対応する型を指定できます。
ジェネリックでは、特定のデータ型の代わりに型パラメーターを使用できます。これらの型パラメーターは、クラス、メソッド、またはインターフェイスで宣言でき、これらの宣言で使用できます。ジェネリックスを使用するコードでは、実行時に実際の型パラメーターを指定できるため、コードをさまざまな種類のデータに適用できます。
ジェネリックにより、コードの可読性、保守性、再利用性が向上します。これにより、コードの冗長性が軽減され、型安全性とコンパイル時の型チェックが向上します。
ジェネリクスがコードの冗長性を削減できる理由を具体的な例を使用して説明します:
a と b の最小値を返す関数を提供します。それぞれが必要です。 a に対して関数を作成します。特定のデータ型「int、float...」、またはインターフェイスを使用します。「パラメータの型アサーションが必要ですが、これは実行パフォーマンスに影響を及ぼし、渡されるパラメータを制約できません。」
func minInt(a, b int) int { if a > b { return b } return a } func minFloat(a, b float64) float64 { if a > b { return b } return a } func minItf(a, b interface{}) interface{} { switch a.(type) { case int: switch b.(type) { case int: if a.(int) > b.(int) { return b } return a } case float64: switch b.(type) { case float64: if a.(float64) > b.(float64) { return b } return a } } return nil }
#Generics in go上記のメソッドから、パラメータと返される結果のタイプが異なることを除けば、minInt と minFloat は同じコードであることがわかります。特定の型を指定せずに関数が呼び出されたときに渡される型を判断する方法はありますか?ここでは、ジェネリックと呼ばれる概念が導入されています。これは、単純に広範な型または不特定の特定の型として理解できます。ジェネリックスを導入することで、特定のデータ型を指定する必要がなくなりました。min 関数は次のように使用できます:
##// T 为类型参数, 在调用时确定参数的具体值, 可以为 int, 也可以为 float64;它与 a, b 一样也是参数, 需要调用时传入具体的值;不同的是,T 为类型参数,值为具体的类型, a,b 为函数参数,值为具体类型对应的值 func minIntAndFloat64[T int | float64](a, b T) T { if a < b { return a } return b } minIntAndFloat64[int](1, 2) // 实例化/调用时指定具体的类型ログイン後にコピー
# go はバージョン 1.8 でのみジェネリックを導入しました。 go のバージョンが 1.8 より前の場合は、ジェネリックを使用できません。この記事のコードではバージョン 1.9 を使用します。バージョン 1.8 では、ジェネリックをサポートするために多くの変更が加えられました。
型パラメーターは関数と型の宣言で導入されます
- 型のコレクションは、メソッドのない型も含め、インターフェイスを通じて定義できます一部のシナリオでは型の派生型パラメータが推定され、型パラメータの値を指定せずに関数を呼び出すことができます。仮パラメータ、実パラメータ、型パラメータ、型実パラメータ、インスタンス化
まず、一般的な add
関数を見てみましょう。add は関数名、
x, y は仮パラメータ、
(x, y int) はパラメータ リストです。関数呼び出しが発生した場合、
add(2, 3) 2, 3 が実際のパラメータになります。
ジェネリックスと同様に、型パラメーターが必要です。関数呼び出しが発生すると、対応する型パラメーターが渡されます。型パラメーターを持つ関数は、ジェネリック関数と呼ばれます。
は型パラメータのリスト、 T
は型パラメータ、
int | int64 は型セット/型制約です。関数呼び出し
add[int](2,3) が発生すると、int が型実パラメータになります。この呼び出しはインスタンス化とも呼ばれ、型実パラメータが決定されます。
MyStruct[T] はジェネリック構造体であり、ジェネリック構造体に対してメソッドを定義できます。
型コレクション、インターフェイス
基本型では、uint8 は 0 ~ 255 のコレクションを表します。次に、型パラメータについては、基本型と同様に型のコレクションを定義する必要があります。上の例の int | string は型のコレクションです。では、型のコレクションを再利用するにはどうすればよいでしょうか?ここではインターフェイスを定義に使用します。以下は型コレクションの定義です。したがって、汎用関数
add[T Signed](x, y T) TMyInt 型は Add メソッドを実装しているため、
に変換できます。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>type MyInterface interface {
Add(x, y int) int
}
type MyInt int
func (mi myInt) Add(x, y int) int {
return x + y
}
func main() {
var mi MyInterface = myInt(1)
fmt.Println(mi.Add(1, 2))
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
別の角度から考えると、MyInterface
は、
メソッドを実装するすべての型を含む型のコレクションとみなすことができます。その後、MyInterface を型のコレクションとして使用できます。たとえば、次のようにジェネリック関数を定義できます。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>func I[T MyInterface](x, y int, i T) int {
return i.Add(x, y)
}</pre><div class="contentsignin">ログイン後にコピー</div></div><p>在泛型中, 我们的类型集合不仅仅是实现接口中定义方法的类型, 还需要包含基础的类型。因此, 我们可以对接口的定义进行延伸, 使其支持基础类型。为了保证向前兼容, 我们需要对接口类型进行分类:</p><h4 id="strong-基础接口类型-strong"><strong>基础接口类型</strong></h4><p>只包含方法的集合, 既可以当作类型集合, 又可以作为数据类型进行声明。如下面的 <code>MyInterface
。还有一个特殊的接口类型 interface{}, 它可以用来表示任意类型, 即所有的类型都实现了它的空方法。在 1.8 之后可以使用 any 进行声明。
type any = interface{} type MyInterface interface { Add(x, y int) int String() string String() string // 非法: String 不能重复声明 _(x int) // 非法: 必须要有一个非空的名字 }
接口组合
可以通过接口组合的形式声明新的接口, 从而尽可能的复用接口。从下面的例子可以看出, ReadWriter
是 Reader
和 Write
的类型集合的交集。
type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // ReadWriter's methods are Read, Write, and Close. type ReadWriter interface { Reader // includes methods of Reader in ReadWriter's method set Writer // includes methods of Writer in ReadWriter's method set }
通用接口
上面说的接口都必须要实现具体的方法, 但是类型集合中无法包含基础的数据类型。如: int, float, string...。通过下面的定义, 可以用来表示包含基础数据类型的类型集合。在 golang.org/x/exp/constraints
中定义了基础数据类型的集合。我们可以看到 ~
符号, 它表示包含潜在类型为 int | int8 | int16 | int32 | int64 的类型, |
表示取并集。Singed
就表示所有类型为 int 的类型集合。
// Signed is a constraint that permits any signed integer type. // If future releases of Go add new predeclared signed integer types, // this constraint will be modified to include them. type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } type myInt int // 潜在类型为 int func add[T constraints.Integer](x, y T) T { return x + y } func main() { var x, y myInt = 1, 2 fmt.Println(add[myInt](x, y)) }
下面来看一些特殊的定义
// 潜在类型为 int, 并且实现了 String 方法的类型 type E interface { ~int String() string } type mInt int // 属于 E 的类型集合 func (m mInt) String() string { return fmt.Sprintf("%v", m) } // 潜在类型必须是自己真实的类型 type F interface { ~int // ~mInt invalid use of ~ (underlying type of mInt is int) // ~error illegal: error is an interface } // 基础接口可以作为形参和类型参数类型, 通用类型只能作为类型参数类型, E 只能出现在类型参数中 [T E] var x E // illegal: cannot use type E outside a type constraint: interface contains type constraints var x interface{} = E(nil) // illegal: cannot use interface E in conversion (contains specific type constraints or is comparable)
类型推导
由于泛型使用了类型参数, 因此在实例化泛型时我们需要指定类型实参。 看下面的 case, 我们在调用函数的时候并没有指定类型实参, 这里是编译器进行了类型推导, 推导出类型实参, 不需要显性的传入。
func add[T constraints.Integer](x, y T) T { return x + y } func main() { fmt.Println(add(1, 1)) // add[int](1,1) }
有时候, 编译器无法推导出具体类型。则需要指定类型, 或者更换写法, 也许可以推断出具体类型。
// 将切片中的值扩大 func Scale[E constraints.Integer](s []E, c E) []E { r := make([]E, len(s)) for i, v := range s { r[i] = v * c } return r } func ScaleAndPrint(p Point) { r := Scale(p, 2) r.string() // 非法, Scale 返回的是 []int32 } type Point []int32 func (p Point) string() { fmt.Println(p) } // 方法更新,这样传入的是 Point 返回的也是 Point func Scale[T ~[]E, E constraints.Integer](s T, c E) T { r := make([]E, len(s)) for i, v := range s { r[i] = v * c } return r }
泛型的使用
go 是在 1.8 版本中开始引入泛型的。下面主要介绍一下什么时候使用泛型:
内置容器类型
在 go 中, 提供以下容器类型:map, slice, channel。当我们用到容器类型时, 且逻辑与容器具体的类型无关, 这个时候可以考虑泛型。这样我们可以在调用时指定具体的类型实参, 从而避免了类型断言。例如,下面的例子, 返回 map 中的 key。
// comparable 是一个内置类型, 只能用于对类型参数的约束。在 map 中, key 必须是可比较类型。 func GetKeys[K comparable, V any](m map[K]V) []K { res := make([]K, 0, len(m)) for k := range m { res = append(res, k) } return res }
通用的结构体
对于一些通用的结构体, 我们应该使用泛型。例如, 栈、队列、树结构。这些都是比较通用的结构体, 且逻辑都与具体的类型无关, 因此需要使用泛型。下面是一个栈的例子:
type Stack[T any] []T func (s *Stack[T]) Push(item T) { *s = append(*s, item) } func (s *Stack[T]) Pop() T { if len(*s) == 0 { panic("can not pop item in emply stack") } lastIndex := len(*s) - 1 item := (*s)[lastIndex] *s = (*s)[:lastIndex] return item } func main() { var s Stack[int] s.Push(9) fmt.Println(s.Pop()) s.Push(9) s.Push(8) fmt.Println(s.Pop(), s.Pop()) }
通用的函数
有些类型会实现相同的方法, 但是对于这些类型的处理逻辑又与具体类型的实现无关。例如: 两个数比大小, 只要实现 Ordered 接口即可进行大小比较:
func Min[T constraints.Ordered](x, y T) T { if x < y { return x } return y } func main() { fmt.Println(Min(5, 6)) fmt.Println(Min(6.6, 9.9)) }
总结
go 在引入泛型算是一次较大的改动。我们只有弄清楚类型参数、类型约束、类型集合、基础接口、通用接口、泛型函数、泛型类型、泛型接口等概念, 才能不会困惑。核心改动点还是引入了类型参数, 使用接口来定义类型集合。
当然,也不能为了使用泛型而使用泛型。还是要具体的 case 具体来分析。 简单的指导原则就是, 当你发现你的代码除了类型不同外, 其余代码逻辑都相同; 或者你写了许多重复代码, 仅仅是为了支持不同类型; 那么你可以考虑使用泛型。
推荐学习:Golang教程
以上がgolang のジェネリックについての深い理解 (Generic)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Go では、関数のライフ サイクルには定義、ロード、リンク、初期化、呼び出し、戻り値が含まれます。変数のスコープは関数レベルとブロック レベルに分割されますが、ブロック内の変数はブロック内でのみ表示されます。 。

Go では、正規表現を使用してタイムスタンプを照合できます。ISO8601 タイムスタンプの照合に使用されるような正規表現文字列をコンパイルします。 ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。 regexp.MatchString 関数を使用して、文字列が正規表現と一致するかどうかを確認します。

Go では、gorilla/websocket パッケージを使用して WebSocket メッセージを送信できます。具体的な手順: WebSocket 接続を確立します。テキスト メッセージを送信します。 WriteMessage(websocket.TextMessage,[]byte("message")) を呼び出します。バイナリ メッセージを送信します。WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) を呼び出します。

Go と Go 言語は、異なる特性を持つ別個の存在です。 Go (Golang とも呼ばれます) は、同時実行性、高速なコンパイル速度、メモリ管理、およびクロスプラットフォームの利点で知られています。 Go 言語の欠点としては、他の言語に比べてエコシステムが充実していないこと、構文が厳格であること、動的型付けが欠如していることが挙げられます。

メモリ リークは、ファイル、ネットワーク接続、データベース接続などの使用されなくなったリソースを閉じることによって、Go プログラムのメモリを継続的に増加させる可能性があります。弱参照を使用してメモリ リークを防ぎ、強参照されなくなったオブジェクトをガベージ コレクションの対象にします。 go coroutine を使用すると、メモリ リークを避けるために、終了時にコルーチンのスタック メモリが自動的に解放されます。

IDE を使用して Go 関数のドキュメントを表示する: 関数名の上にカーソルを置きます。ホットキーを押します (GoLand: Ctrl+Q; VSCode: GoExtensionPack をインストールした後、F1 キーを押して「Go:ShowDocumentation」を選択します)。

Golang では、エラー ラッパーを使用して、元のエラーにコンテキスト情報を追加することで新しいエラーを作成できます。これを使用すると、さまざまなライブラリまたはコンポーネントによってスローされるエラーの種類を統一し、デバッグとエラー処理を簡素化できます。手順は次のとおりです。errors.Wrap 関数を使用して、元のエラーを新しいエラーにラップします。新しいエラーには、元のエラーのコンテキスト情報が含まれています。 fmt.Printf を使用してラップされたエラーを出力し、より多くのコンテキストとアクション性を提供します。異なる種類のエラーを処理する場合は、errors.Wrap 関数を使用してエラーの種類を統一します。

並行関数の単体テストは、同時環境での正しい動作を確認するのに役立つため、非常に重要です。同時実行機能をテストするときは、相互排他、同期、分離などの基本原則を考慮する必要があります。並行機能は、シミュレーション、競合状態のテスト、および結果の検証によって単体テストできます。
