注: このブログでは、私が最初に GO を学習したときの概要を共有します。このブログを通じて、皆さんが短期間で GO の基本的なプログラミング機能をすぐにマスターできることを願っています。間違いがある場合は、メッセージを残して修正してください。ありがとう!
1. Go 言語の予備的な理解
(1) Go言語誕生の主な課題と目標
-
マルチコア ハードウェア アーキテクチャ:コンピュータ ハードウェアの発展に伴い、マルチコア プロセッサが主流になり、並列コンピューティングが一般的になりました。ただし、従来のプログラミング言語には適切なネイティブ サポートがないため、マルチコア並列処理の処理が困難になる可能性があります。Go 言語は、軽量の goroutine とチャネル メカニズムを導入することにより、同時プログラミングを容易にします。開発者は、スレッド管理の複雑さを気にすることなく、同時に実行される数千のコルーチンを簡単に作成できます。
-
超大規模分散コンピューティング クラスター:クラウド コンピューティングと分散システムの台頭により、超大規模分散コンピューティング クラスターを構築および維持することがますます一般的になりました。これらのクラスターは、大量のリクエスト、データ共有、調整を効率的に処理できる必要があります。Go 言語の同時実行機能とチャネル メカニズムにより、分散システムの作成が容易になり、開発者はコルーチンとチャネルを使用して同時タスク、メッセージ パッシング、調整作業を処理できます。
-
Web モデルによる開発規模と更新速度の増大: Web アプリケーションの台頭により、前例のない開発規模と継続的な更新要件がもたらされました。従来のプログラミング言語では、大規模な Web アプリケーションを開発する場合、保守性、パフォーマンス、開発効率などの課題に直面することがあります。Go 言語は、その簡潔な構文、効率的なコンパイル速度、同時実行性のサポートにより、開発者が Web アプリケーションをより迅速に繰り返してデプロイできるようにし、同時に発生するネットワーク リクエストをより適切に処理することもできます。
まとめると、Go 言語が誕生したとき、マルチコア ハードウェア アーキテクチャ、超大規模分散コンピューティング クラスター、Web モードでの開発規模と速度などの技術的課題を解決することに重点が置かれていました。その設計目標の 1 つは、開発者がこれらの課題にうまく対処できるように、現代のソフトウェア開発のニーズに適応するプログラミング言語を提供することです。
(2) Go言語アプリケーションの代表的なもの
現在のアプリケーション開発では Go 言語が広く使われており、多くの有名企業やプロジェクトが Go 言語を使用してさまざまな種類のアプリケーションを構築しています。以下は、Go 言語をコア開発言語として使用する代表的な製品とプロジェクトの一部です。
これらは Go 言語アプリケーションのほんの一例にすぎませんが、実際には、Go 言語を使用して高性能で信頼性が高く、保守が容易なアプリケーションを構築するプロジェクトや製品が他にもたくさんあります。これは、Go 言語が現代のアプリケーション開発、特に分散システム、クラウド コンピューティング、高性能アプリケーションの分野で重要な役割を果たしていることを示しています。
(3) Go の書き方を学ぶ際の Java、C++、C プログラマー間の誤解
Java、C++、C、その他のプログラミング言語のプログラマーが Go 言語の書き方を学び始めると、Go はこれらの伝統的な言語とはいくつかの点で異なるため、いくつかの誤解に遭遇する可能性があります。よくある誤解をいくつか挙げます。
-
従来の同時実行モデルの過剰使用: Java、C++、C などの従来のプログラミング言語では通常、スレッドとロックを使用して同時実行を処理しますが、Go ではゴルーチンとチャネルを使用する方が良い方法です。Go を初めて使用するプログラマは、Go の軽量コルーチンやチャネルを最大限に活用せずに従来の同時実行モデルを使い続ける可能性があり、その結果、Go の同時実行の利点が失われます。
-
ポインタの過剰な使用: C や C++ などの言語はポインタの使用を重視しますが、Go 言語は設計時に過剰なポインタ操作を避けます。Go に慣れていないプログラマはポインタを使いすぎて、コードが複雑になる可能性があります。Go では、実際に値を変更する必要がない限り、ポインターの使用を避けるようにしてください。
-
エラー処理を無視する: Go では、エラーを単に無視するのではなく、明示的にエラーを処理することを推奨します。これは、エラーが無視されるか単にスローされることが多い他の言語の規則とは異なります。Go に慣れていないプログラマーはエラー処理を見落とし、潜在的な問題が検出されないままになる可能性があります。
-
グローバル変数の過剰使用:グローバル変数は、C や C++ などの言語では一般的な方法です。ただし、Go では、グローバル変数の使用は悪い習慣とみなされます。Go では、不必要な結合や副作用の導入を避けるために、データを渡すためにローカル変数とパラメーターを渡すことを推奨しています。
-
スライスとマップに慣れていない: Go のスライスとマップは強力なデータ構造ですが、他の言語のプログラマには馴染みがないかもしれません。スライスとマップは Go でコレクションやデータ処理に広く使用されるため、スライスとマップの正しい使用方法を学ぶことが重要です。
-
間違った Go スタイル:すべての言語には、独自の独自のコーディング スタイルと規則があります。Go を初めて使用するプログラマーは、他の言語のコーディング スタイルを Go コードに適用する可能性があり、コードが読みにくく、理解しにくくなる可能性があります。Go のコーディング規約を理解し、従うことが重要です。
これらの誤解を避けるために、Go を学ぶプログラマは、同時実行モデル、エラー処理、データ構造などを含む Go 言語の中核概念を理解するために時間を投資し、同時に Go コミュニティに積極的に参加し、本を読む必要があります。 Go の設計哲学とベスト プラクティスをより適切に適応させるための Go の公式ドキュメントとサンプル コード。
2. 環境準備(Macで説明)
(1) 環境設定
macOS 上で Go 言語開発環境をセットアップするのは非常に簡単で、次の手順に従うことができます。
-
Homebrew を使用してインストールする: Homebrew パッケージ マネージャーを使用する場合、これが最も便利な方法です。ターミナルを開き、次のコマンドを実行して Go 言語をインストールします。
brew install go
-
手動インストール: Go 言語を手動でインストールする場合は、次の手順に従います。
a. 公式 Web サイトにアクセスして、インストール パッケージ「goX.XXdarwin-amd64.pkg」をダウンロードします。
b. ダウンロードしたインストール パッケージをダブルクリックし、指示に従ってインストール プログラムを実行します。デフォルト設定に従ってください。通常、インストール パスは です
/usr/local/go
。 -
環境変数を設定する:インストールが完了したら、Go 言語のバイナリ パスをターミナル構成ファイルの PATH 環境変数に追加する必要があります。これにより、ターミナルで Go コマンドを直接実行できるようになります。
a. ターミナルを開き、nano、vim、または任意のエディタなどのテキスト エディタを使用してターミナル構成ファイルを編集します。例えば:
nano ~/.bash_profile
b. 次の行をファイルに追加し (インストール パスに合わせて調整します)、保存してエディタを終了します。
export PATH=$PATH:/usr/local/go/bin
c. 構成を有効にするには、次のコマンドを実行するか、ターミナルを再起動します。
source ~/.bash_profile
-
インストールの確認:ターミナルを開いて次のコマンドを入力し、Go が正しくインストールされていることを確認します。
go version
Go のバージョン番号が表示されれば、インストールは成功しています。
(2) IDEの選択手順
私は個人的に GoLand を使用しています。公式 Web サイトから直接ダウンロードした後、クラック版をオンラインで購入できます。ここでは詳しく説明しません。
3. Go言語プログラムの学習
独自のプロジェクト ディレクトリ/Users/zyf/zyfcodes/go/go-learning を作成し、新しい src ディレクトリを作成します。
(1) 最初に Go 言語で書かれたもの
src ディレクトリの下に Chapter1/hello ディレクトリを作成し、新しい hello.go ファイルを作成して、次のようにコードを記述します。
package main
import (
"fmt"
"os"
)
/**
* @author zhangyanfeng
* @description 第一个godaima
* @date 2023/8/20 23:45
* @param
* @return
**/
func main() {
if len(os.Args) > 1 {
fmt.Println("Hello World", os.Args[1])
}
}
このコードは、コマンド ライン パラメーターを受け取り、パラメーター付きの "Hello World" メッセージを出力する単純な Go 言語プログラムです。コードの行ごとの分析は次のとおりです。
-
package main
: このファイルが、Go プログラムのエントリ パッケージ名である「main」という名前のパッケージに属することを宣言します。 -
import ("fmt" "os")
: 出力をフォーマットするための「fmt」と、オペレーティング システムと対話するための「os」という 2 つの標準ライブラリ パッケージが導入されています。 -
func main() { ... }
: これはプログラムのエントリ関数であり、プログラムの実行時に最初に呼び出されます。 -
if len(os.Args) > 1 { ... }
: この条件文は、コマンド ライン パラメータの数が 1 より大きいかどうか、つまりプログラムに渡されるパラメータがあるかどうかをチェックします。os.Args
すべてのコマンド ライン パラメータを含む文字列スライスです。最初のパラメータはプログラムの名前です。 -
fmt.Println("Hello World", os.Args[1])
: パラメータがプログラムに渡されると、このコード行が実行されます。この関数を使用して、文字列「Hello World」で構成され、プログラムに渡される最初の引数を表すfmt.Println
メッセージを出力します。os.Args[1]
os.Args[1]
要約すると、このコードは次の知識ポイントをカバーします。
-
import
パッケージのインポートと標準ライブラリの使用:キーワードを使用して「fmt」および「os」パッケージをインポートし、コード内でこれらのパッケージによって提供される関数と型を使用します。 -
コマンドラインパラメータの取得:
os.Args
コマンドラインパラメータを取得するために使用します。 -
条件ステートメント:
if
条件ステートメントを使用して、コマンド ライン パラメーターがプログラムに渡されるかどうかを決定します。 -
文字列操作: 文字列連結操作を使用して、「Hello World」とコマンド ライン パラメーターを連結します。
-
フォーマットされた出力:
fmt.Println
関数を使用してメッセージを標準出力に出力します。
注: プログラムにパラメーターが渡されない場合、このコードはメッセージを出力しません。複数のパラメータが渡された場合、コードは最初のパラメータのみを使用し、他のパラメータは無視します。
このディレクトリで「go run hello.go ZYF」を実行すると、実行結果は「Hello World ZYF」になります。
(2) 基本的なプログラム構造の書き方を学ぶ
srcディレクトリにchapter2を作成
1.変数
前提条件: Chapter2 ディレクトリに変数を作成する 学習の概要は次のとおりです。
- 変数宣言:キーワードを使用して
var
変数を宣言します。例:var x int
。 - 型推論:変数の宣言と代入に演算子を使用できます
:=
。Go は右側の値に基づいて変数の型を自動的に推論します (例: )y := 5
。 - 変数の代入:代入演算子を使用して
=
変数に値を代入します。例:x = 10
。 - 複数変数の宣言:複数の変数を同時に宣言できます。たとえば、次のようになります
var a, b, c int
。 - 変数の初期化:変数は、宣言時に初期化できます。たとえば、次のようになります
var name string = "John"
。 - ゼロ値:初期化されていない変数にはゼロ値が割り当てられます。数値型は 0、ブール型はブール型
false
、文字列型は空の文字列などです。 - 短い変数宣言:関数内で、次のような短い変数宣言を使用できます
count := 10
。
新しい fib_test.go を作成します。背景: 練習用のシンプルで実用的なフィボナッチ数列
package variables
import "testing"
func TestFibList(t *testing.T) {
a := 1
b := 1
t.Log(a)
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
func TestExchange(t *testing.T) {
a := 1
b := 2
// tmp := a
// a = b
// b = tmp
a, b = b, a
t.Log(a, b)
}
コードに含まれる知識ポイントを以下で 1 つずつ説明します。
-
package variables
: 「variables」という名前のパッケージを宣言します。これはテストに使用されるパッケージ名です。 -
import "testing"
: テスト関数を記述して実行するために、Go 言語テスト フレームワーク「testing」パッケージをインポートしました。 -
func TestFibList(t *testing.T) { ... }
: フィボナッチ数列生成ロジックをテストするために使用されるテスト関数「TestFibList」を定義します。これはテスト関数の標準的な名前であり、「Test」で始まり、その後にテスト対象の関数の名前が続きます。a
テスト関数内で、2 つの整変数と が宣言されb
、フィボナッチ数列の最初の 2 つの数値である 1 に初期化されます。t.Log(a)
出力変数値を使用しa
てログをテストします。- ループを使用してフィボナッチ数列の最初の 5 つの数値を生成し、各反復で の値を
b
テスト ログに出力し、a
との値を更新しb
て次の数値を生成します。
-
func TestExchange(t *testing.T) { ... }
: 変数交換のロジックをテストするために使用される別のテスト関数「TestExchange」が定義されています。- テスト関数内で、2 つの整数変数
a
と が宣言されb
、それぞれ 1 と 2 に初期化されます。 - コメントの使用は、(中間変数を介した) 変数交換の記述方法を示していますが、実際にはコメントアウトされています。次に、
a, b = b, a
このコード行を使用して、a
との交換を実装しますb
。これは Go 言語独自の交換メソッドであり、追加の中間変数を必要としません。 t.Log(a, b)
スワップされた変数値をテスト ログに出力するために使用します。
- テスト関数内で、2 つの整数変数
2.定数
前提条件: Chapter2 ディレクトリに定数を作成する 学習の概要は次のとおりです。
- 定数の宣言:キーワードを使用して
const
定数を宣言します。例:const pi = 3.14159
。 - 定数の割り当て:定数の値は、宣言時に割り当てる必要があり、一度割り当てられると変更できません。
- 列挙型定数:列挙型は、次のような定数のセットを使用してシミュレートできます。
const ( Monday = 1 Tuesday = 2 // ... )
- 型の指定:定数の型も指定できます。例:
const speed int = 300000
。 - 定数式:定数は、次のような式を使用して評価できます
const secondsInHour = 60 * 60
。 - 型なし定数:定数は型なしで、コンテキストに基づいて型が自動的に推測されます。たとえば、
const x = 5
整数型が推論されます。
新しい constant_test.go を作成し、次のようにコードを記述します。
package constant
import "testing"
const (
Monday = 1 + iota
Tuesday
Wednesday
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestConstant1(t *testing.T) {
t.Log(Monday, Tuesday)
}
func TestConstant2(t *testing.T) {
a := 1 //0001
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
コードに含まれる知識ポイントを以下で 1 つずつ説明します。
-
package constant
: テストに使用されるパッケージ名「constant」という名前のパッケージを宣言します。 -
import "testing"
: テスト関数を記述して実行するために、Go 言語テスト フレームワーク「testing」パッケージをインポートしました。 -
const (...)
: 2 つの定数ブロックが定義されています。-
最初の定数ブロックでは、定数ジェネレーターを使用して、
iota
1 から始まり増加する一連の定数を定義します。この例では、Monday
には値 1 が割り当てられ、Tuesday
には値 2 が割り当てられ、さらにWednesday
値 3 が割り当てられます。iota
定数ブロックで使用されるたびにインクリメントされるため、後続の定数も順番にインクリメントされます。 -
2 番目の定数ブロックでは、
iota
一連のビット単位で左シフトされた定数を定義するために使用されます。この例では、Readable
には値 1 が割り当てられ、Writable
値 2 (2 進数で 10) が割り当てられ、Executable
値 4 (2 進数で 100) が割り当てられます。ビット単位の演算では、左シフト演算により、指定された桁数だけ 2 進数を左に移動できます。
-
-
func TestConstant1(t *testing.T) { ... }
: 最初の定数ブロックで定義された定数をテストするためのテスト関数「TestConstant1」を定義します。t.Log(Monday, Tuesday)
定数の値をテストログにMonday
出力するために使用します。Tuesday
-
func TestConstant2(t *testing.T) { ... }
: ビット操作と定数の使用をテストするための別のテスト関数「TestConstant2」を定義します。- テスト関数内で、整数変数が宣言され
a
、1 (バイナリでは 0001) に初期化されます。 - ビットごとの演算とビットごとの AND 演算を使用して、
a
変数に および プロパティがあるかどうかをReadable
確認Writable
しますExecutable
。たとえば、a&Readable == Readable
この式は のa
バイナリ表現にReadable
フラグ ビットが含まれているかどうかをチェックします。 t.Log()
3 つの式の結果をテスト ログに出力するために使用します。
- テスト関数内で、整数変数が宣言され
3.データ型
前提条件: Chapter2 ディレクトリにタイプを作成する 学習の概要は次のとおりです。
主なデータ型の説明
Go 言語には、さまざまな種類の値やデータを表すために使用される豊富な組み込みデータ型セットがあります。以下は、Go 言語のいくつかの主要なデータ型の分析の概要です。
-
整数型:
int
Go 言語には、 、int8
、int16
、int32
など、さまざまなサイズの整数型が用意されていますint64
。符号なし整数型にはuint
、、、、およびが含まれます。整数型のサイズは、32 ビットや 64 ビットなど、コンピューターのアーキテクチャによって異なります。uint8
uint16
uint32
uint64
-
浮動小数点型: Go 言語は、それぞれ単精度および倍精度浮動小数点数に対応する
float32
2 つの浮動小数点型を提供します。float64
-
複合型: Go 言語は、2 つの浮動小数点数で構成される複素数に対応する
complex64
2 つの複合型を提供します。complex128
-
ブール型:ブール型は、true (
true
) と false (false
) の値を表すために使用され、条件判断や論理演算に使用されます。 -
文字列型:文字列型は文字のシーケンスを表します。
"
文字列は不変であり、二重引用符またはバックティックを使用して`
定義できます。 -
文字タイプ (Rune タイプ):文字タイプは
rune
、int32 の別名である Unicode 文字を表すために使用されます。通常、一重引用符は'
、 などの文字を表すために使用されます'A'
。 -
配列タイプ:配列は、固定サイズの同じタイプの要素のコレクションです。配列を宣言するときは、要素の型とサイズを指定する必要があります。
-
スライス タイプ:スライスは、配列と動的長の可変シーケンスをカプセル化した層です。スライスは要素を格納せず、基になる配列の一部を参照するだけです。
-
マップ タイプ:マップは、データの保存と取得に使用されるキーと値のペアの順序付けされていないコレクションです。キーと値は任意の型にすることができますが、キーは同等である必要があります。
-
構造体の型:構造体は、さまざまな型のフィールドを含めることができるユーザー定義の複合データ型であり、各フィールドには名前と型があります。
-
インターフェイスの種類:インターフェイスは、一連のメソッドを定義する抽象型です。インターフェイスを実装する型のメソッドのセットがインターフェイスを実装します。
-
関数タイプ:関数タイプは、パラメータや戻り値のタイプなど、関数のシグネチャを表します。関数はパラメータとして渡して返すことができます。
-
チャネルの種類:チャネルは、コルーチン間の通信と同期のためのメカニズムです。チャネルには送信操作と受信操作があります。
-
ポインタ型:ポインタ型は変数のメモリ アドレスを表します。変数の値には、ポインタを介して直接アクセスして変更できます。
Go 言語のデータ型は明確な構文とセマンティクスを持ち、豊富な組み込み関数をサポートしています。さまざまなデータ型を適切に選択して使用すると、プログラムの効率と読みやすさが向上します。
特定のコード展開解析
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
}
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func add(a, b int) int {
return a + b
}
func subtract(a, b int) int {
return a - b
}
type Operation func(int, int) int
func main() {
fmt.Println("整数类型(Integer Types)")
var x int = 10
var y int64 = 100
fmt.Println(x)
fmt.Println(y)
fmt.Println("浮点数类型(Floating-Point Types)")
var a float32 = 3.14
var b float64 = 3.14159265359
fmt.Println(a)
fmt.Println(b)
fmt.Println("布尔类型(Boolean Type)")
var isTrue bool = true
var isFalse bool = false
fmt.Println(isTrue)
fmt.Println(isFalse)
fmt.Println("字符串类型(String Type)")
str1 := "Hello, "
str2 := "Go!"
concatenated := str1 + str2
fmt.Println(concatenated)
fmt.Println("切片类型(Slice Types)")
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(numbers)
// 修改切片元素
numbers[0] = 10
fmt.Println(numbers)
// 切片操作
subSlice := numbers[1:4]
fmt.Println(subSlice)
fmt.Println("映射类型(Map Types)")
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println(ages)
fmt.Println("Alice's age:", ages["Alice"])
// 添加新的键值对
ages["Charlie"] = 22
fmt.Println(ages)
fmt.Println("结构体类型(Struct Types)")
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
fmt.Println(person)
fmt.Println("Name:", person.FirstName, person.LastName)
fmt.Println("接口类型(Interface Types)")
var shape Shape
circle := Circle{Radius: 5}
shape = circle
fmt.Println("Circle Area:", shape.Area())
fmt.Println("函数类型(Function Types)")
var op Operation
op = add
result := op(10, 5)
fmt.Println("Addition:", result)
op = subtract
result = op(10, 5)
fmt.Println("Subtraction:", result)
fmt.Println("通道类型(Channel Types)")
messages := make(chan string)
go func() {
messages <- "Hello, Go!"
}()
msg := <-messages
fmt.Println(msg)
fmt.Println("指针类型(Pointer Types)")
x = 10
var ptr *int
ptr = &x
fmt.Println("Value of x:", x)
fmt.Println("Value stored in pointer:", *ptr)
*ptr = 20
fmt.Println("Updated value of x:", x)
}
コードに含まれる知識ポイントを以下で 1 つずつ説明します。
-
type Person struct { ... }
: 、フィールドなど、Person
個人の情報を表す構造タイプを定義します。FirstName
LastName
Age
-
type Shape interface { ... }
: 型を返すShape
メソッドの実装を必要とするインターフェイス型を定義します。Area()
float64
-
type Circle struct { ... }
Circle
:円の半径を表す構造体のタイプを定義します。func (c Circle) Area() float64 { ... }
:円の面積を計算するために使用される、タイプのインターフェイスのメソッドCircle
を実装します。Shape
Area()
-
func add(a, b int) int { ... }
: 整数の加算演算を実行する関数を定義しますadd
。 -
func subtract(a, b int) int { ... }
: 整数の減算を実行する関数を定義しますsubtract
。 -
type Operation func(int, int) int
Operation
: 2 つの整数パラメータを受け取り、整数の結果を返す関数タイプを定義します。 -
main() { ... }
:番組のエントリー機能。
- 整数、浮動小数点、ブール値、文字列、スライス、マップ、構造体、インターフェイス、関数、チャネル、ポインター型など、いくつかの異なる型の変数が定義されています。
- さまざまなタイプの変数の初期化、代入、アクセス、および基本操作を示します。
- スライス操作を使用して部分スライスを抽出します。
- 新しいキーと値のペアの追加やキーと値のペアへのアクセスなど、マッピングの使用方法を示します。
- 構造体の定義と初期化を示し、構造体フィールドにアクセスします。
- インターフェイスの使用法、型変数
Circle
への型の割り当てShape
、インターフェイス メソッドの呼び出しを示します。 - 関数型の定義と使用法を示し、さまざまな関数を
Operation
型変数に割り当て、それらを呼び出します。 - チャネルを使用して同時通信を実装し、匿名関数を介してゴルーチンでメッセージを送受信します。
- ポインター変数の作成やポインターを介した変数の値の変更などの操作を含む、ポインターの使用方法を示します。
Go 言語での型変換命令
Go 言語は型変換をサポートしていますが、注意すべきルールと制限がいくつかあります。型変換は、あるデータ型の値を別のデータ型に変換して、別のコンテキストで使用するために使用されます。Go 言語での型変換に関する重要な情報を次に示します。
-
基本型間の変換:基本データ型間の変換は可能ですが、型の互換性とデータ損失の可能性に注意する必要があります。たとえば、
int
toからの変換は安全ですが、 to からのfloat64
変換では小数部分が切り捨てられる可能性があります。float64
int
-
明示的なキャスト: Go では、キャストは、ある値から別の型への変換を明示的に指定するために使用されます。構文は次のとおりです
destinationType(expression)
。例:float64(10)
。 -
互換性のない型間の変換:コンパイラは、互換性のない型を自動的に変換しません。たとえば、型を型
string
に直接変換することはできません。int
-
型エイリアスの変換:型エイリアス(Type Alias)がある場合、変換する際はエイリアスの互換性に注意する必要があります。
型変換を示すいくつかの例を次に示します。
package main
import "fmt"
func main() {
// 显式类型转换
var x int = 10
var y float64 = float64(x)
fmt.Println(y)
// 类型别名的转换
type Celsius float64
type Fahrenheit float64
c := Celsius(25)
f := Fahrenheit(c*9/5 + 32)
fmt.Println(f)
}
4.オペレーター
前提条件: Chapter2 ディレクトリにオペレーターを作成する 学習の概要は次のとおりです。
実はこの部分は他の言語にも似ていて、見直して固める部分は特にないのではないかと個人的には感じています。Go 言語は、さまざまな算術演算、論理演算、および比較演算を実行するためのさまざまな演算子をサポートしています。
通常のオペレーター
以下は、Go における一般的な演算子とその使用法および知識ポイントです。
算術演算子:
+
:追加-
:引き算*
:乗算/
:分割%
: モジュロ (剰余)
代入演算子:
=
: 割り当て+=
: 加算割り当て-=
: 減算の代入*=
:乗算代入/=
: 部門割り当て%=
: モジュロ代入
論理演算子:
&&
:論理積(AND)||
:論理和(OR)!
:論理否定(NOT)
比較演算子:
==
:等しい!=
:等しくない<
: 未満>
:以上<=
: 以下>=
:以上
ビット演算子:
&
: ビット単位の論理積 (AND)|
: ビット単位の論理和 (OR)^
: ビットごとの排他的論理和 (XOR)<<
: 左にシフト>>
:右に動く
他の演算子:
&
:アドレス演算子*
: ポインタ演算子++
:インクリメント演算子--
: デクリメント演算子
演算子を使用する場合は、次の点を考慮する必要があります。
- 演算子のオペランドは、演算子の予期される型と一致する必要があります。
- 一部の演算子は優先順位が高く、優先順位を明確にするために括弧を使用する必要があります。
- 演算子のオペランドには、変数、定数、式などを使用できます。
新しい Operator_test.go を作成します。演算子の使用方法を示す例をいくつか示します。
package operator
import (
"fmt"
"testing"
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestOperatorBasic(t *testing.T) {
// 算术运算符
a := 10
b := 5
fmt.Println("Sum:", a+b)
fmt.Println("Difference:", a-b)
fmt.Println("Product:", a*b)
fmt.Println("Quotient:", a/b)
fmt.Println("Remainder:", a%b)
// 逻辑运算符
x := true
y := false
fmt.Println("AND:", x && y)
fmt.Println("OR:", x || y)
fmt.Println("NOT:", !x)
// 比较运算符
fmt.Println("Equal:", a == b)
fmt.Println("Not Equal:", a != b)
fmt.Println("Greater Than:", a > b)
fmt.Println("Less Than:", a < b)
fmt.Println("Greater Than or Equal:", a >= b)
fmt.Println("Less Than or Equal:", a <= b)
}
func TestCompareArray(t *testing.T) {
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 3, 2, 4}
// c := [...]int{1, 2, 3, 4, 5}
d := [...]int{1, 2, 3, 4}
t.Log(a == b)
//t.Log(a == c)
t.Log(a == d)
}
func TestBitClear(t *testing.T) {
a := 7 //0111
a = a &^ Readable
a = a &^ Executable
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
コードに含まれる知識ポイントを以下で 1 つずつ説明します。
-
const (...)
: 3 つの定数Readable
、合計を定義しWritable
、Executable
変位演算を使用してさまざまな値を生成します。 -
func TestOperatorBasic(t *testing.T) { ... }
: 基本的な演算子の使用をテストするためのテスト関数「TestOperatorBasic」を定義します。- 算術演算子: 加算、減算、乗算、除算、および剰余演算を示します。
- 論理演算子: 論理 AND、論理 OR、および論理 NOT 演算を示します。
- 比較演算子: 等しい、等しくない、より大きい、より小さい、以上、および以下の演算を示します。
-
func TestCompareArray(t *testing.T) { ... }
: 配列比較をテストするためのテスト関数「TestCompareArray」を定義します。a
2 つの整数配列と が宣言されておりb
、また、配列と 配列d
の内容が同一である別の配列も宣言されています。a
d
- 比較演算子を使用して、
==
配列a
と がb
等しいかどうか、および配列a
と がd
等しいかどうかを確認します。
-
func TestBitClear(t *testing.T) { ... }
: ビットクリア動作をテストするためのテスト関数「TestBitClear」を定義します。- 整数変数を宣言し
a
、それを7
、つまりバイナリ表現に初期化します0111
。 - ビットクリア操作を使用して
&^
、および のa
ビットをクリアします。Readable
Executable
- ビットごとの AND 演算を使用して、およびプロパティがあるかどうかを
&
確認します。a
Readable
Writable
Executable
- 整数変数を宣言し
ビット単位のクリア演算子 &^
Go 言語では、&^
Bit Clear 演算子です。これは、特定の位置のビットをクリアする、つまり、指定された位置のビットを 0 に設定するために使用されます。&^
演算子は、バイナリ ビット演算を処理する場合に非常に便利です。
&^
オペレーターは次の操作を実行します。
- 各ビットについて、右側のオペランドの対応するビットが 0 の場合、結果のビットは左側のオペランドと同じになります。
- 各ビットについて、右側のオペランドの対応するビットが 1 の場合、結果ビットは強制的に 0 になります。
これは、&^
右側のオペランドの対応するビットが影響を受けないように、左側のオペランドの特定のビットを「クリア」するために演算子が使用されることを意味します。検証するコードを記述します。
func TestOther(t *testing.T) {
var a uint8 = 0b11001100 // 二进制表示,十进制为 204
var b uint8 = 0b00110011 // 二进制表示,十进制为 51
result := a &^ b
fmt.Printf("a: %08b\n", a) // 输出:11001100
fmt.Printf("b: %08b\n", b) // 输出:00110011
fmt.Printf("Result: %08b\n", result) // 输出:11000000
fmt.Println("Result (Decimal):", result) // 输出:192
}
5. 条件文
前提条件: Chapter2 ディレクトリに条件を作成する 学習の概要は次のとおりです。
if
声明
if
ステートメントは、条件に基づいて特定のコードを実行するかどうかを決定するために使用されます。その基本的な構文は次のとおりです。
if condition {
// 代码块
} else if anotherCondition {
// 代码块
} else {
// 代码块
}
switch
声明
switch
ステートメントは、式のさまざまな値に基づいてコードのさまざまな分岐を実行するために使用されます。他の言語とは異なり、Go はステートメントswitch
を使用せずに、条件を満たす最初の分岐を自動的に照合できます。break
その構文は次のとおりです。
switch expression {
case value1:
// 代码块
case value2:
// 代码块
default:
// 代码块
}
検証分析用にcondition_test.go を作成します。具体的なコードは次のとおりです。
package condition
import (
"fmt"
"testing"
)
func TestConditionIf(t *testing.T) {
age := 18
if age < 18 {
fmt.Println("You are a minor.")
} else if age >= 18 && age < 60 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a senior citizen.")
}
}
func TestConditionSwitch(t *testing.T) {
dayOfWeek := 3
switch dayOfWeek {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
case 4:
fmt.Println("Thursday")
case 5:
fmt.Println("Friday")
default:
fmt.Println("Weekend")
}
}
func TestSwitchMultiCase(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Logf("%d is Even", i)
case 1, 3:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is not 0-3", i)
}
}
}
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Logf("%d is Even", i)
case i%2 == 1:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is unknow", i)
}
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
年齢のさまざまな状況に応じて、未成年者、成人、高齢者かどうかが分岐によって判断されますfunc TestConditionIf(t *testing.T) { ... }
:if
ステートメントの使用をテストします。if
。else if
else
-
func TestConditionSwitch(t *testing.T) { ... }
:switch
ステートメントの使用をテストします。dayOfWeek
の値に従って、switch
ステートメントを使用して、対応する曜日を出力します。 -
func TestSwitchMultiCase(t *testing.T) { ... }
:switch
ステートメントに複数のcase
値がある場合をテストします。このステートメントを使用してswitch
、各数値のパリティを決定し、対応する情報を出力します。 -
func TestSwitchCaseCondition(t *testing.T) { ... }
:switch
ステートメント内の条件式をテストします。このステートメントを使用してswitch
、数値の剰余を取得して数値のパリティを決定し、対応する情報を出力します。
これらのテスト関数は、条件ベースの分岐判断や複数の値の処理、ステートメントでの条件式の使用case
など、 Go 言語の条件ステートメントのさまざまな使用法を示します。switch
6. ループ文
前提条件: Chapter2 ディレクトリにループを作成する 学習の概要は次のとおりです。
for
サイクル
for
ループは、コードのブロックを繰り返し実行するために使用され、初期化ステートメント、ループ条件、およびループ後のステートメントをサポートします。その基本的な形式は次のとおりです。
for initialization; condition; post {
// 代码块
}
初期化ステートメントでは、ループ変数を初期化し、ループ本体内の条件を使用してループを制御し、最後にpost
ステートメント内でインクリメントまたはデクリメント操作を実行します。
for
ループの簡略化された形式
Go 言語のループは、for
他の言語のwhile
ループと同様に、ループ条件部分のみに簡略化することもできます。
for condition {
// 代码块
}
range
サイクル
range
ループは、配列、スライス、マップ、文字列などの反復可能なデータ構造を反復するために使用されます。各反復のインデックスと値を返します。例:
for index, value := range iterable {
// 使用 index 和 value
}
検証分析用にloop_test.goを作成します。具体的なコードは次のとおりです。
package loop
import (
"fmt"
"testing"
)
func TestLoopFor(t *testing.T) {
for i := 1; i <= 5; i++ {
fmt.Println("Iteration:", i)
}
}
func TestLoopForBasic(t *testing.T) {
i := 1
for i <= 5 {
fmt.Println("Iteration:", i)
i++
}
}
func TestLoopForRange(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
func TestLoopForUnLimit(t *testing.T) {
i := 1
for {
fmt.Println("Iteration:", i)
i++
if i > 5 {
break
}
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
func TestLoopFor(t *testing.T) { ... }
: 基本的なfor
ループをテストします。ループを使用してfor
1 から 5 まで繰り返し、ループの繰り返し回数を出力します。 -
func TestLoopForBasic(t *testing.T) { ... }
: 初期化ステートメントを使用せずにループをテストしますfor
。ループを使用してfor
1 から 5 まで反復してループの反復回数を出力しますが、ループの先頭で初期化ステートメントが宣言されていません。 -
func TestLoopForRange(t *testing.T) { ... }
:for range
反復スライスを使用してテストします。整数スライスを定義しnumbers
、for range
ループを使用してスライス内の各要素を反復処理し、要素のインデックスと値を出力します。 -
func TestLoopForUnLimit(t *testing.T) { ... }
: 無限ループとbreak
ステートメントをテストします。無限ループとbreak
ステートメントを使用して、ループ本体内でループを終了するかどうかを決定し、i
値が 5 より大きい場合はループを終了します。
これらのテスト関数はfor
、標準のカウント ループ、初期化ステートメントのないループ、スライスの走査、無限ループとループ終了条件など、Go 言語のさまざまなタイプのループの使用法を示します。
7. ジャンプステートメント
前提条件: Chapter2 ディレクトリにジャンプを作成する 学習の概要は次のとおりです。
Go 言語は、ループや条件内のフローを制御するためのいくつかのジャンプ ステートメントもサポートしています。
break
: ループから抜け出します。continue
: このループの反復をスキップして、次の反復に進みます。goto
: コード内の指定されたタグに直接ジャンプします(推奨されません)。
検証分析用にjump_test.goを作成します。具体的なコードは次のとおりです。
package jump
import (
"fmt"
"testing"
)
func TestJumpBreak(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
break
}
fmt.Println("Iteration:", i)
}
}
func TestJumpContinue(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println("Iteration:", i)
}
}
func TestJumpGoto(t *testing.T) {
i := 1
start:
fmt.Println("Iteration:", i)
i++
if i <= 5 {
goto start
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
func TestJumpBreak(t *testing.T) { ... }
:break
ステートメントの使用をテストします。for
ループを使用して 1 から 5 まで繰り返しますが、i
反復変数が 3 に等しい場合は、break
ステートメントを使用してループを終了します。 -
func TestJumpContinue(t *testing.T) { ... }
:continue
ステートメントの使用をテストします。ループを使用してfor
1 から 5 まで反復しますが、反復変数がi
3 に等しい場合は、continue
ステートメントを使用してこの反復をスキップし、次の反復に進みます。 -
func TestJumpGoto(t *testing.T) { ... }
:goto
ステートメントの使用をテストします。goto
無限ループは、ラベルを使用し、ループ本体内のループの開始位置にジャンプするステートメントstart
を使用して実装されます。goto start
ループの終了条件は、 がi
5 より大きい場合です。
break
これらのテスト関数は、ループの終了、現在の反復のスキップ、continue
および無限ループのステートメントを含む、Go 言語のループ制御ジャンプ ステートメントを示しますgoto
。
(3) よく使われるコレクションと文字列
src ディレクトリに Chapter3 を作成する Go 言語では、セットとは値のセットを格納するデータ構造です。一般的に使用されるコレクション タイプには、配列、スライス、マップ、チャネルなどがあります。
1.アレイ
前提条件: Chapter3 ディレクトリに配列を作成する 学習の概要は次のとおりです。
Go 言語の配列は、固定長の同じ型の要素のコレクションです。以下は、Go 配列に関する知識の概要と応用です。
アレイの特徴
- 配列の長さは宣言時に指定され、作成後に変更することはできません。
- 配列は値型であり、配列が新しい変数に割り当てられるか、パラメーターとして渡されると、新しいコピーが作成されます。
- 配列はメモリに継続的に保存され、ランダム アクセスをサポートします。
配列の宣言と初期化
var arrayName [size]dataType
arrayName
: アレイの名前。size
: 配列の長さは定数式である必要があります。dataType
: 配列に格納される要素のタイプ。
配列を初期化する方法
// 使用指定的值初始化数组
var arr = [5]int{1, 2, 3, 4, 5}
// 根据索引初始化数组
var arr [5]int
arr[0] = 10
arr[1] = 20
// 部分初始化
var arr = [5]int{1, 2}
// 自动推断数组长度
arr := [...]int{1, 2, 3, 4, 5}
配列へのアクセスとトラバーサル
// 访问单个元素
value := arr[index]
// 遍历数组
for index, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
関数パラメータとしての配列
関数パラメータが渡されると配列のコピーが作成されるため、関数内で配列を変更しても元の配列には影響しません。関数内で元の配列を変更する必要がある場合は、配列へのポインターを渡すことができます。
func modifyArray(arr [5]int) {
arr[0] = 100
}
func modifyArrayByPointer(arr *[5]int) {
arr[0] = 100
}
多次元配列
Go 言語は、2 次元配列や 3 次元配列などの多次元配列をサポートしています。多次元配列の初期化とアクセスは、複数のインデックスを指定する必要があることを除いて、1 次元配列の初期化とアクセスに似ています。
var matrix [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
配列は、同じ型の固定数の要素を格納する場合に非常に便利ですが、固定長の制限があるため、実際の開発では通常、動的長さの特性を持つスライスの方がよく使用されます。必要に応じてスライスを追加、削除、再割り当てできるため、より柔軟になります。
検証分析用に array_test.go を作成します。具体的なコードは次のとおりです。
package array
import "testing"
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1, 2, 3, 4}
arr3 := [...]int{1, 3, 4, 5}
arr1[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 3, 4, 5}
for i := 0; i < len(arr3); i++ {
t.Log(arr3[i])
}
for _, e := range arr3 {
t.Log(e)
}
}
func TestArraySection(t *testing.T) {
arr3 := [...]int{1, 2, 3, 4, 5}
arr3_sec := arr3[:]
t.Log(arr3_sec)
}
以下に各テスト機能の内容を一つずつ説明します。
-
func TestArrayInit(t *testing.T) { ... }
: 配列の初期化をテストします。- 配列とを初期化するには
arr
、さまざまな方法を使用します。arr1
arr3
arr1
の 2 番目の要素を変更します5
。t.Log()
さまざまな配列の要素値と内容を出力するために使用します。
- 配列とを初期化するには
-
func TestArrayTravel(t *testing.T) { ... }
: 配列の走査をテストします。for
配列をループしarr3
、各要素の値を個別に出力するために使用します。for range
配列をループしarr3
、各要素の値を出力するために使用します。
-
func TestArraySection(t *testing.T) { ... }
: 配列スライスの使用をテストします。arr3_sec
配列全体に基づいて配列スライスを作成しますarr3
。t.Log()
出力配列スライスの内容を使用しますarr3_sec
。
2. スライス
前提条件: Chapter3 ディレクトリにスライスを作成する 学習の概要は次のとおりです。
Go 言語のスライスは配列をカプセル化する層であり、より柔軟な動的な長さのシーケンスを提供します。以下は、スライスに関する知識と応用の概要です。
スライスの特徴
- スライスは参照型であり、データを保存せず、基になる配列の一部を参照するだけです。
- スライスは動的な長さであり、必要に応じて拡大または縮小できます。
- スライスはインデックス可能で、スライスインデックスによってカットできます。
スライスの宣言と初期化
var sliceName []elementType
スライスを初期化する方法
// 声明切片并初始化
var slice = []int{1, 2, 3, 4, 5}
// 使用 make 函数创建切片
var slice = make([]int, 5) // 创建长度为 5 的 int 类型切片
// 使用切片切割已有数组或切片
newSlice := oldSlice[startIndex:endIndex] // 包括 startIndex,但不包括 endIndex
スライス用の組み込み関数と操作
len(slice)
: スライスの長さを返します。cap(slice)
: スライスの容量、つまり基礎となる配列の長さを返します。append(slice, element)
:要素をスライスの末尾に追加し、新しいスライスを返します。copy(destination, source)
: 要素をソース スライスからターゲット スライスにコピーします。
スライストラバーサル
for index, value := range slice {
// 使用 index 和 value
}
関数の引数としてのスライス
スライスがパラメータとして関数に渡される場合、関数内でスライスを変更すると、元のスライスに影響します。
func modifySlice(s []int) {
s[0] = 100
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
modifySlice(numbers)
fmt.Println(numbers) // 输出:[100 2 3 4 5]
}
スライスは、コレクション、リスト、キューなどの動的データセットを処理するために Go 言語で広く使用されています。これは、固定配列の制限を回避しながら要素を管理する便利な方法を提供します。実際のアプリケーションでは、スライスは可変長データの保存と処理によく使用されます。
検証分析用にslice_test.goを作成します。具体的なコードは次のとおりです。
package slice
import (
"fmt"
"testing"
)
func TestSlice(t *testing.T) {
// 声明和初始化切片
numbers := []int{1, 2, 3, 4, 5}
fmt.Println("Original Slice:", numbers)
// 使用 make 函数创建切片
slice := make([]int, 3)
fmt.Println("Initial Make Slice:", slice)
// 添加元素到切片
slice = append(slice, 10)
slice = append(slice, 20, 30)
fmt.Println("After Append:", slice)
// 复制切片
copySlice := make([]int, len(slice))
copy(copySlice, slice)
fmt.Println("Copied Slice:", copySlice)
// 切片切割
subSlice := numbers[1:3]
fmt.Println("Subslice:", subSlice)
// 修改切片的值会影响底层数组和其他切片
subSlice[0] = 100
fmt.Println("Modified Subslice:", subSlice)
fmt.Println("Original Slice:", numbers)
fmt.Println("Copied Slice:", copySlice)
// 遍历切片
for index, value := range slice {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
以下に各テスト機能の内容を一つずつ説明します。
func TestSlice(t *testing.T) { ... }
: スライスの基本操作をテストします。
- スライスを宣言して初期化し
numbers
、初期スライスの内容を出力します。 make
この関数を使用して、初期容量の3
スライスを作成しslice
、初期スライスの内容を出力します。- 関数を使用して要素を
append
スライスに追加します。slice
- 関数を使用して
copy
、スライスをslice
新しいスライスにコピーしますcopySlice
。 - [スライス] を使用して
numbers
、スライス カットを作成し、サブスライスを作成しますsubSlice
。 - 変更
subSlice
の最初の要素は で100
、変更されたスライスと元のスライス、およびコピーされたスライスを出力します。 - ループを使用し
for range
てスライスをループしslice
、各要素のインデックスと値を出力します。
このテスト関数は、スライスの作成、要素の追加、スライスのコピー、スライスの切断、スライス要素の変更など、Go 言語でのスライスのさまざまな操作を示します。
3.地図
前提条件: Chapter3 ディレクトリにマップを作成する 学習の概要は次のとおりです。
Go 言語のマップは、連想配列または辞書とも呼ばれる、キーと値のペアの順序付けされていないコレクションです。以下は、マッピングに関する知識と応用の概要です。
マッピング特性
- マップは、各キーが一意である一連のキーと値のペアを保存するために使用されます。
- マップには順序がなく、キーと値のペアの順序は保証されません。
- キーは同等の任意の型にすることができ、値は任意の型にすることができます。
- マップは、関数に割り当てて渡すことができる参照型です。
マッピングの宣言と初期化
var mapName map[keyType]valueType
マッピングを初期化する方法
// 声明和初始化映射
var ages = map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
// 使用 make 函数创建映射
var ages = make(map[string]int)
マッピング操作
- キーと値のペアを追加します。
ages["Charlie"] = 35
- キーと値のペアを削除します。
delete(ages, "Eve")
- 値の取得:
value := ages["Alice"]
マッピングトラバーサル
for key, value := range ages {
fmt.Printf("Name: %s, Age: %d\n", key, value)
}
関数パラメータとしてのマッピング
マッピングがパラメータとして関数に渡される場合、関数内でマッピングを変更すると、元のマッピングに影響します。
func modifyMap(m map[string]int) {
m["Alice"] = 30
}
func main() {
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
modifyMap(ages)
fmt.Println(ages) // 输出:map[Alice:30 Bob:30]
}
Map は、データの保存と取得のために Go 言語で非常に一般的に使用されるデータ構造です。これは、名前と年齢の対応関係、単語と定義の対応関係など、関連付けられたキーと値のペアのセットを保存する場合に非常に役立ちます。実際のアプリケーションでは、マッピングはキーと値のデータを処理および保存するための重要なツールです。
検証分析用にmap_test.goを作成します。具体的なコードは次のとおりです。
package my_map
import (
"fmt"
"testing"
)
func TestBasic(t *testing.T) {
// 声明和初始化映射
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println("Original Map:", ages)
// 添加新的键值对
ages["Charlie"] = 35
fmt.Println("After Adding:", ages)
// 修改已有键的值
ages["Bob"] = 31
fmt.Println("After Modification:", ages)
// 删除键值对
delete(ages, "Eve")
fmt.Println("After Deletion:", ages)
// 获取值和检查键是否存在
age, exists := ages["Alice"]
if exists {
fmt.Println("Alice's Age:", age)
} else {
fmt.Println("Alice not found")
}
// 遍历映射
for name, age := range ages {
fmt.Printf("Name: %s, Age: %d\n", name, age)
}
}
type Student struct {
Name string
Age int
Grade string
}
func TestComplex(t *testing.T) {
// 声明和初始化映射,用于存储学生信息和成绩
studentScores := make(map[string]int)
studentInfo := make(map[string]Student)
// 添加学生信息和成绩
studentInfo["Alice"] = Student{Name: "Alice", Age: 18, Grade: "A"}
studentScores["Alice"] = 95
studentInfo["Bob"] = Student{Name: "Bob", Age: 19, Grade: "B"}
studentScores["Bob"] = 85
// 查找学生信息和成绩
aliceInfo := studentInfo["Alice"]
aliceScore := studentScores["Alice"]
fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", aliceInfo.Name, aliceInfo.Age, aliceInfo.Grade, aliceScore)
// 遍历学生信息和成绩
for name, info := range studentInfo {
score, exists := studentScores[name]
if exists {
fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", info.Name, info.Age, info.Grade, score)
} else {
fmt.Printf("No score available for %s\n", name)
}
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
func TestBasic(t *testing.T) { ... }
: マッピングをテストするための基本操作。ages
人の名前と年齢のキーと値のペアを格納するマップを宣言して初期化します。- 初期マッピング内容を出力します。
- 新しいキーと値のペアを追加するために使用します
ages["Charlie"]
。 ages["Bob"]
既存のキーの値を変更するために使用します。delete
関数を使用してキーと値のペアを削除します。age, exists
値を取得し、キーが存在するかどうかを確認するために使用します。- ループを使用して
for range
マップを走査し、各キーと値のペアの情報を出力します。
-
type Student struct { ... }
:Student
学生情報を保存するために名前を付けた構造体を定義します。 -
func TestComplex(t *testing.T) { ... }
: 複素数値を含むマッピング操作をテストします。studentScores
学生のスコアと学生情報を保存するために使用される2 つのマッピングを宣言して初期化しますstudentInfo
。- 生徒の情報とスコアをマップに追加します。
- 生徒の情報を取得するために使用し
studentInfo["Alice"]
、studentScores["Alice"]
生徒のスコアを取得するために使用します。 - ループを使用して
for range
マップをループし、各生徒の情報とスコアを出力します。
これらのテスト関数は、キーと値のペアの作成、追加、変更、削除、キーが存在するかどうかの確認、マップされたキーと値のペアの走査など、Go 言語でのマッピングのさまざまな操作を示します。
4. 作業セット
前提条件: Chapter3 ディレクトリにセットを作成する 学習の概要は次のとおりです。
Go 言語では、標準ライブラリに組み込みの Set 型が提供されていませんが、さまざまな方法を使用して Set 関数を実装できます。Set を実装する一般的な方法は次のとおりです。
スライスを使用する
set_slice_test.go 演習を作成する
スライスを使用して要素を保存し、スライスを走査して要素が存在するかどうかを確認します。これは、小規模なコレクションに適した単純な実装です。
package set
import (
"fmt"
"testing"
)
type IntSet struct {
elements []int
}
func (s *IntSet) Add(element int) {
if !s.Contains(element) {
s.elements = append(s.elements, element)
}
}
func (s *IntSet) Contains(element int) bool {
for _, e := range s.elements {
if e == element {
return true
}
}
return false
}
func TestSet(t *testing.T) {
set := IntSet{}
set.Add(1)
set.Add(2)
set.Add(3)
set.Add(2) // Adding duplicate, should be ignored
fmt.Println("Set:", set.elements) // Output: [1 2 3]
}
マッピングを使用する
set_map_test.go 演習を作成する
マップを使用して要素を保存します。マップのキーはコレクションの要素を表し、値は任意の型にすることができます。この実装は、マッピング検索の複雑さが O(1) であるため、より高速で大規模なコレクションに適しています。
package set
import (
"fmt"
"testing"
)
type Set map[int]bool
func (s Set) Add(element int) {
s[element] = true
}
func (s Set) Contains(element int) bool {
return s[element]
}
func TestSetMap(t *testing.T) {
set := make(Set)
set.Add(1)
set.Add(2)
set.Add(3)
set.Add(2) // Adding duplicate, should be ignored
fmt.Println("Set:", set) // Output: map[1:true 2:true 3:true]
}
サードパーティのライブラリを使用する
set_three_test.go 演習を作成する
自分で実装することを避けるために、たとえば、より豊富な Set 関数を提供するいくつかのサードパーティ ライブラリを使用できますgithub.com/deckarep/golang-set
。
プロキシを追加します: go env -w GOPROXY=https://goproxy.io,direct
次に、パッケージをインストールします: go get github.com/deckarep/golang-set
package set
import (
"fmt"
"github.com/deckarep/golang-set"
"testing"
)
func TestSetThird(t *testing.T) {
intSet := mapset.NewSet()
intSet.Add(1)
intSet.Add(2)
intSet.Add(3)
intSet.Add(2) // Adding duplicate, will be ignored
fmt.Println("Set:", intSet) // Output: Set: Set{1, 2, 3}
}
上記は Set を実装するためのいくつかの方法です。ニーズとパフォーマンスの考慮事項に基づいて、適切な実装方法を選択できます。サードパーティのライブラリを使用すると、大規模なデータ収集にさらに多くの機能とパフォーマンスの最適化を提供できます。
5. 文字列
前提条件: Chapter3 ディレクトリに文字列を作成する 学習の概要は次のとおりです。
文字列の宣言と初期化
Go 言語では、文字列は一連の文字で構成され、二重引用符"
またはバックティック ``` を使用して文字列を宣言および初期化できます。
package main
import "fmt"
func main() {
str1 := "Hello, World!" // 使用双引号声明
str2 := `Go Programming` // 使用反引号声明
fmt.Println(str1) // Output: Hello, World!
fmt.Println(str2) // Output: Go Programming
}
文字列の長さ
組み込み関数を使用して、len()
文字列の長さ、つまり文字列内の文字数を取得します。
package main
import "fmt"
func main() {
str := "Hello, 世界!"
length := len(str)
fmt.Println("String Length:", length) // Output: String Length: 9
}
文字列のインデックス付けとスライス
文字列内の文字には、0 から始まるインデックスによってアクセスできます。スライス操作を使用して、文字列の部分文字列を取得できます。
package main
import "fmt"
func main() {
str := "Hello, World!"
// 获取第一个字符
firstChar := str[0]
fmt.Println("First Character:", string(firstChar)) // Output: First Character: H
// 获取子串
substring := str[7:12]
fmt.Println("Substring:", substring) // Output: Substring: World
}
文字列の連結
+
演算子を使用して、2 つの文字列を新しい文字列に連結します。さらに、strings.Join
この関数は文字列スライスを新しい文字列に連結するために使用され、複数の文字列を結合するために使用できます。
最後に、バイト バッファリングを使用すると、冗長な文字列のコピーを作成せずに効率的に文字列を連結できます。
package main
import (
"fmt"
"strings"
"bytes"
)
func main() {
str1 := "Hello, "
str2 := "World!"
result := str1 + str2
fmt.Println("Concatenated String:", result) // Output: Concatenated String: Hello, World!
strSlice := []string{"Hello", " ", "World!"}
result := strings.Join(strSlice, "")
fmt.Println(result) // Output: Hello World!
var buffer bytes.Buffer
buffer.WriteString(str1)
buffer.WriteString(str2)
result := buffer.String()
fmt.Println(result) // Output: Hello, World!
}
複数行の文字列
複数行の文字列を作成するには、バッククォート ``` を使用します。
package main
import "fmt"
func main() {
multiLineStr := `
This is a
multi-line
string.
`
fmt.Println(multiLineStr)
}
文字列の反復
for range
ループを使用して、文字列の各文字を繰り返し処理します。
package main
import "fmt"
func main() {
str := "Go语言"
for _, char := range str {
fmt.Printf("%c ", char) // Output: G o 语 言
}
}
文字列とバイト配列間の変換
Go 言語では、文字列とバイト配列を相互に変換できます。
package main
import "fmt"
func main() {
str := "Hello"
bytes := []byte(str) // 转换为字节数组
strAgain := string(bytes) // 字节数组转换为字符串
fmt.Println("Bytes:", bytes) // Output: Bytes: [72 101 108 108 111]
fmt.Println("String Again:", strAgain) // Output: String Again: Hello
}
文字列比較
==
文字列はand演算子を使用して比較できます!=
。もちろん、直接適用できる他の関数タイプもあります。たとえば、strings.Compare
2 つの文字列を比較し、比較結果に基づいて整数を返す関数です。
カスタム比較関数を使用して文字列を比較し、独自のニーズに応じて比較ロジックを定義することもできます。
package main
import (
"fmt"
"strings"
)
func customCompare(str1, str2 string) bool {
// 自定义比较逻辑
return str1 == str2
}
func main() {
str1 := "Hello"
str2 := "World"
if str1 == str2 {
fmt.Println("Strings are equal")
} else {
fmt.Println("Strings are not equal") // Output: Strings are not equal
}
result := strings.Compare(str1, str2)
if result == 0 {
fmt.Println("Strings are equal")
} else if result < 0 {
fmt.Println("str1 is less than str2")
} else {
fmt.Println("str1 is greater than str2") // Output: str1 is less than str2
}
if customCompare(str1, str2) {
fmt.Println("Strings are equal")
} else {
fmt.Println("Strings are not equal") // Output: Strings are not equal
}
}
これらの基本的な概念と操作は、Go 言語の文字列をよりよく理解し、使用するのに役立ちます。文字列の不変性、および他のデータ型との変換や比較に注意してください。
string_test.go 演習を作成する
package string
import (
"strconv"
"strings"
"testing"
)
func TestString(t *testing.T) {
var s string
t.Log(s) //初始化为默认零值“”
s = "hello"
t.Log(len(s))
//s[1] = '3' //string是不可变的byte slice
//s = "\xE4\xB8\xA5" //可以存储任何二进制数据
s = "\xE4\xBA\xBB\xFF"
t.Log(s)
t.Log(len(s))
s = "中"
t.Log(len(s)) //是byte数
c := []rune(s)
t.Log(len(c))
// t.Log("rune size:", unsafe.Sizeof(c[0]))
t.Logf("中 unicode %x", c[0])
t.Logf("中 UTF8 %x", s)
}
func TestStringToRune(t *testing.T) {
s := "中华人民共和国"
for _, c := range s {
t.Logf("%[1]c %[1]x", c)
}
}
func TestStringFn(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",")
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-"))
}
func TestConv(t *testing.T) {
s := strconv.Itoa(10)
t.Log("str" + s)
if i, err := strconv.Atoi("10"); err == nil {
t.Log(10 + i)
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
func TestString(t *testing.T) { ... }
: 文字列をテストするための基本的な操作。s
デフォルト値のゼロを出力する文字列変数を宣言します。- 文字列値を「hello」に割り当て、文字列の長さを出力します。
- 文字列は不変であるため、文字列内の文字を変更しようとするとエラーが発生します。
- 文字列を使用してバイナリ データと Unicode エンコーディングを保存します。
- 文字列を使用して中国語の文字を格納し、その長さを出力します。
- 文字列を
rune
スライス型に変換し、スライス長と中国語文字の Unicode および UTF-8 エンコーディングを出力します。
-
中国語の文字を含む文字列を宣言しfunc TestStringToRune(t *testing.T) { ... }
: 文字列をrune
変換するテスト。s
、トラバーサルによってrange
文字列をrune
型に変換して出力します。 -
カンマ区切りを含む文字列を宣言しfunc TestStringFn(t *testing.T) { ... }
: 文字列関連の関数をテストします。s
、strings.Split
関数を使用して文字列を分割し、各部分を出力します。strings.Join
関数を使用して分割した部分を新しい文字列に結合して出力します。 -
func TestConv(t *testing.T) { ... }
: 文字列から他の型への変換をテストします。strconv.Itoa
整数を文字列に変換するために使用します。- 文字列と整数を連結して結果を出力します。
strconv.Atoi
文字列を整数に変換し、加算を実行し、エラー条件を処理するために使用します。
rune
これらのテスト関数は、文字列の長さ、UTF-8 エンコード、型変換、文字列の分割と結合、文字列の他の型への変換など、Go 言語の文字列に対するさまざまな操作を示します。
(4) 機能
src ディレクトリに第 4 章を作成します。Go 言語では、関数は特定のタスクを実行するために使用されるコード ブロックであり、複数回呼び出すことができます。以下に、Go 言語関数に関する重要な知識ポイントをいくつか示します。
1. 関数宣言
Go では、関数宣言はfunc
キーワードで始まり、その後に関数名、パラメーター リスト、戻り値、関数本体が続きます。
func functionName(parameters) returnType {
// 函数体
// 可以包含多个语句
return returnValue
}
2. 関数パラメータ
関数には、パラメーター名とパラメーターの型で構成されるパラメーターを 0 個以上含めることができます。パラメータを区切るにはカンマを使用します。
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
3. 複数の戻り値
Go 言語の関数は複数の値を返すことができます。戻り値は括弧で囲み、カンマで区切ります。
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
4. 戻り値に名前を付けます
return
関数は名前付きの戻り値を宣言でき、これらの名前は関数本体内の代入に直接使用でき、最後に、キーワードを明示的に使用する必要はありません。
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
5.パラメータの可変数
...
Go 言語は、可変数のパラメーターを表す構文の使用をサポートしています。これらのパラメーターは、関数本体内のスライスとして使用されます。
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
6. パラメータとしての機能
Go 言語では、関数をパラメータとして他の関数に渡すことができます。
func applyFunction(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
func add(a, b int) int {
return a + b
}
func main() {
result := applyFunction(add, 3, 4)
fmt.Println(result) // Output: 7
}
7. 匿名関数とクロージャ
Go 言語は、クロージャとも呼ばれる匿名関数をサポートしています。これらの関数は他の関数の内部で定義でき、外部関数から変数にアクセスできます。
func main() {
x := 5
fn := func() {
fmt.Println(x) // 闭包访问外部变量
}
fn() // Output: 5
}
8.deferステートメント
defer
ステートメントは関数の実行を遅らせるために使用され、通常は関数が戻る前にクリーンアップ操作を実行します。
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
以上がGo言語の関数に関する基礎知識です。関数は Go で非常に重要な役割を果たし、コードを整理し、関数のモジュール化を実装し、コードの保守性を向上させるために使用されます。
検証 1: 基本的なユースケースの検証
第 4 章で新しい Basic を作成し、func_basic_test.go 演習を作成します。
package basic
import (
"errors"
"fmt"
"testing"
)
// 普通函数
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// 多返回值函数
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 命名返回值函数
func divideNamed(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
// 可变数量的参数函数
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 函数作为参数
func applyFunction(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
// 匿名函数和闭包
func closureExample() {
x := 5
fn := func() {
fmt.Println(x)
}
fn() // Output: 5
}
// defer语句
func deferExample() {
defer fmt.Println("World")
fmt.Println("Hello") // Output: Hello World
}
func TestBasic(t *testing.T) {
greet("Alice") // Output: Hello, Alice!
q, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Quotient:", q) // Output: Quotient: 5
}
qNamed, errNamed := divideNamed(10, 0)
if errNamed != nil {
fmt.Println("Error:", errNamed) // Output: Error: division by zero
} else {
fmt.Println("Quotient:", qNamed)
}
total := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", total) // Output: Sum: 15
addResult := applyFunction(func(a, b int) int {
return a + b
}, 3, 4)
fmt.Println("Addition:", addResult) // Output: Addition: 7
closureExample()
deferExample()
}
検証 2: 中小企業の例
第 4 章で新しいビズを作成し、func_biz_test.go 演習を作成します。単純な注文処理システムを開発していて、注文内の商品の合計価格を計算して割引を適用する必要があるとします。関数を使用して、このビジネス ロジックを処理できます。簡単な例を次に示します。
package biz
import (
"fmt"
"testing"
)
type Product struct {
Name string
Price float64
}
func calculateTotal(products []Product) float64 {
total := 0.0
for _, p := range products {
total += p.Price
}
return total
}
func applyDiscount(amount, discount float64) float64 {
return amount * (1 - discount)
}
func TestBiz(t *testing.T) {
products := []Product{
{Name: "Product A", Price: 10.0},
{Name: "Product B", Price: 20.0},
{Name: "Product C", Price: 30.0},
}
total := calculateTotal(products)
fmt.Printf("Total before discount: $%.2f\n", total)
discountedTotal := applyDiscount(total, 0.1)
fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}
(5) オブジェクト指向プログラミング
src ディレクトリに第 5 章を作成します。Go 言語はオブジェクト指向プログラミング (OOP) をサポートしていますが、従来のオブジェクト指向プログラミング言語 (Java や C++ など) と比較すると、Go の実装は若干異なる場合があります。Go 言語にはクラスの概念はありませんが、オブジェクト指向の機能は構造とメソッドによって実現できます。
1. 構造の定義
Go 言語では、構造体は、異なる型のフィールド (メンバー変数) を結合して新しいデータ型を作成するために使用されるカスタム データ型です。struct ディレクトリを作成し、struct_test.go を記述します。構造体の定義、使用、検証の例は次のとおりです。
package _struct
import (
"fmt"
"testing"
)
// 定义一个结构体
type Person struct {
FirstName string
LastName string
Age int
}
func TestStruct(t *testing.T) {
// 创建结构体实例并初始化字段
person1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
// 访问结构体字段
fmt.Println("First Name:", person1.FirstName) // Output: First Name: Alice
fmt.Println("Last Name:", person1.LastName) // Output: Last Name: Smith
fmt.Println("Age:", person1.Age) // Output: Age: 25
// 修改结构体字段的值
person1.Age = 26
fmt.Println("Updated Age:", person1.Age) // Output: Updated Age: 26
}
構造体の定義には複数のフィールドを含めることができ、各フィールドは異なるデータ型にすることができます。構造内に他の構造をネストして、より複雑なデータ構造を形成することもできます。struct_cmpx_test.go の作成例:
package _struct
import (
"fmt"
"testing"
)
type Address struct {
Street string
City string
ZipCode string
}
type PersonNew struct {
FirstName string
LastName string
Age int
Address Address
}
func TestCmpxStruct(t *testing.T) {
person2 := PersonNew{
FirstName: "Bob",
LastName: "Johnson",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Cityville",
ZipCode: "12345",
},
}
fmt.Println("Full Name:", person2.FirstName, person2.LastName)
fmt.Println("Address:", person2.Address.Street, person2.Address.City, person2.Address.ZipCode)
}
2.インスタンスの作成と初期化
Go 言語では、構造体のインスタンスを作成および初期化する方法が数多くあります。creatinit ディレクトリを作成します。次に、いくつかの一般的なインスタンスの作成および初期化方法を示します。具体的なコードは creatinit_test.go です。
- リテラル初期化: 中括弧を使用して
{}
構造体インスタンスのフィールドを初期化できます。 - 部分的なフィールドの初期化:構造体の一部のフィールドのみを初期化する場合は、他のフィールドを省略できます。
- フィールド名を使用した初期化:シーケンシャルな初期化を行わずに、フィールド名に基づいてフィールド値を指定できます。
- デフォルト値の初期化:構造体のフィールドは、その型のデフォルト値に従って初期化できます。
- 新しい関数の使用:この関数を使用して、
new
構造体へのポインターを作成し、そのポインターを返すことができます。 - フィールドシーケンスの初期化:フィールド名はオプションで省略できますが、このときは構造フィールドの順序で値を割り当てる必要があります。
package creatinit
import (
"fmt"
"testing"
)
type Person struct {
FirstName string
LastName string
Age int
}
/**
* @author zhangyanfeng
* @description 字面量初始化
* @date 2023/8/26 15:09
**/
func TestCreateObj1(t *testing.T) {
person1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
fmt.Println(person1.FirstName, person1.LastName, person1.Age) // Output: Alice Smith 25
}
/**
* @author zhangyanfeng
* @description 部分字段初始化
* @date 2023/8/26 15:10
**/
func TestCreateObj2(t *testing.T) {
person2 := Person{
FirstName: "Bob",
Age: 30,
}
fmt.Println(person2.FirstName, person2.LastName, person2.Age) // Output: Bob 30
}
/**
* @author zhangyanfeng
* @description 使用字段名初始化
* @date 2023/8/26 15:12
**/
func TestCreateObj3(t *testing.T) {
person3 := Person{
LastName: "Johnson",
FirstName: "Chris",
Age: 28,
}
fmt.Println(person3.FirstName, person3.LastName, person3.Age) // Output: Chris Johnson 28
}
/**
* @author zhangyanfeng
* @description 默认值初始化
* @date 2023/8/26 15:13
**/
func TestCreateObj4(t *testing.T) {
var person4 Person
fmt.Println(person4.FirstName, person4.LastName, person4.Age) // Output: 0
}
/**
* @author zhangyanfeng
* @description 使用 new 函数
* @date 2023/8/26 15:14
**/
func TestCreateObj5(t *testing.T) {
person5 := new(Person)
person5.FirstName = "David"
person5.Age = 22
fmt.Println(person5.FirstName, person5.LastName, person5.Age) // Output: David 22
}
/**
* @author zhangyanfeng
* @description 字段顺序初始化
* @date 2023/8/26 15:24
**/
func TestCreateObj6(t *testing.T) {
// 使用字段顺序初始化
person := Person{"Alice", "Smith", 25}
fmt.Println(person.FirstName, person.LastName, person.Age) // Output: Alice Smith 25
}
3. 動作(メソッド)の定義
Go では、メソッドとは、特定の型に関連付けられ、その型のインスタンスで呼び出すことができる関数です。メソッドを使用すると、型定義と一緒に型操作を配置できるため、コードの可読性と保守性が向上します。
コーディングの練習用にメソッド ディレクトリを作成します。Go 言語のメソッドの定義、使用、分析は次のとおりです。
メソッド定義
Go 言語では、メソッドは関数にレシーバーを追加することで定義されます。レシーバーは通常のパラメーターですが、メソッドがどの型に関連付けられているかを指定するためにメソッド名の前に配置されます。Method_define_test.go を作成する
package method
import (
"fmt"
"testing"
)
type Circle struct {
Radius float64
}
// 定义 Circle 类型的方法
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func TestMethodDef(t *testing.T) {
c := Circle{Radius: 5}
area := c.Area()
fmt.Printf("Circle area: %.2f\n", area) // Output: Circle area: 78.54
}
上の例では、Circle
構造体を定義してから、Area
それに名前を付けたメソッドを定義しました。このメソッドはc.Area()
を介して呼び出すことができます。 はtype のインスタンスc
です。Circle
メソッド呼び出し
メソッド呼び出しの構文は です实例.方法名()
。つまり、インスタンスを通じてメソッドを呼び出します。Method_rpc_test.go を作成する
package method
import (
"fmt"
"testing"
)
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func TestMethonRpc(t *testing.T) {
rect := Rectangle{Width: 3, Height: 4}
area := rect.Area()
fmt.Printf("Rectangle area: %.2f\n", area) // Output: Rectangle area: 12.00
}
ポインタ受信機
Go 言語はメソッドのレシーバーとしてポインターの使用をサポートしているため、レシーバー インスタンスのフィールド値を変更できます。Method_rec_test.go を作成する
package method
import (
"fmt"
"testing"
)
type Counter struct {
Count int
}
func (c *Counter) Increment() {
c.Count++
}
func TestMethonRec(t *testing.T) {
counter := Counter{Count: 0}
counter.Increment()
fmt.Println("Count:", counter.Count) // Output: Count: 1
}
上記の例では、メソッドが呼び出された後にフィールドの値が変更されるIncrement
ように、メソッドはポインター レシーバーを使用します。Count
メソッドと関数の違い
メソッドと関数の主な違いは、メソッドは特定の型の関数であり、その型により密接に関連しており、その型のフィールドや他のメソッドにアクセスできることです。関数は、特定のタイプから独立したコードのブロックです。メソッドは特定の種類の動作を実装するためによく使用されますが、関数は一般的な操作に使用できます。
メソッドを定義すると、型の操作がより自然で一貫性のあるものになり、コードの可読性とモジュール性が向上します。
这里可以说明一下,method_rpc_test.go中,我们为 Rectangle
结构体定义了一个名为 Area
的方法,该方法可以通过 rect.Area()
的方式调用。方法直接与类型 Rectangle
关联,可以访问 Rectangle
的字段(Width
和 Height
)。
我们为了与方法作对比,在对应方法体中创建一个方法如下
// 定义一个函数来计算矩形的面积
func CalculateArea(r Rectangle) float64 {
return r.Width * r.Height
}
在这个示例中,我们定义了一个名为 CalculateArea
的函数,它接受一个 Rectangle
类型的参数来计算矩形的面积。函数是独立于 Rectangle
类型的,因此它无法直接访问 Rectangle
的字段。
总结: 方法与函数的区别在于方法是特定类型的函数,与类型的关系更加紧密,可以访问类型的字段和其他方法。而函数是独立于特定类型的代码块,通常用于通用的操作。在上述示例中,方法与矩形相关联,可以直接访问矩形的字段;函数则是一个独立的计算过程,不与任何特定类型直接关联。
通过使用方法,我们可以使代码更加自然和一致,提高代码的可读性和模块化,特别是在实现特定类型的行为时。
4.接口定义使用
在Go语言中,接口是一种定义方法集合的方式,它规定了一组方法的签名,而不涉及实现细节。通过接口,可以实现多态性和代码解耦,使不同类型的对象能够按照一致的方式进行操作。
创建interface目录用于后续练习,以下是关于Go语言接口的讲解:
定义接口
接口是一组方法的集合,通过 type
关键字定义。接口定义了一组方法签名,但不包含方法的实现。创建interface_test.go进行代码练习
package interface_test
import (
"fmt"
"testing"
)
// 定义一个简单的接口
type Shape interface {
Area() float64
}
// 定义两个实现 Shape 接口的结构体
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func TestInterface(t *testing.T) {
shapes := []Shape{
Circle{Radius: 2},
Rectangle{Width: 3, Height: 4},
}
for _, shape := range shapes {
fmt.Printf("Area of %T: %.2f\n", shape, shape.Area())
}
}
上記の例では、図の面積を計算するメソッドShape
の実装を必要とする という名前のインターフェイスを定義します。Area
次に、Circle
と の2 つの構造体を定義しRectangle
、Area
それぞれメソッドを実装しました。インターフェイスを使用すると、さまざまなタイプのグラフィックス オブジェクトを同じスライスに配置し、Area
ループを通じてそれらのメソッドを呼び出すことができます。
インターフェースの実装
インターフェイスで定義されているすべてのメソッドを実装している限り、どの型でもインターフェイスを実装しているとみなされます。インターフェイスの実装は暗黙的であり、明示的な宣言は必要ありません。メソッド シグネチャがインターフェイス内のメソッド シグネチャと同じである限り、その型はインターフェイスを実装しているとみなされます。
インターフェース多態性
インターフェイスの多態性により、インターフェイスを実装するオブジェクトをインターフェイス自体とみなすことができます。上の例では、shapes
さまざまなタイプのオブジェクトがスライスに格納されていますが、それらはすべてShape
インターフェースを実装しているため、統一された方法でメソッドを呼び出すことができますArea
。
インターフェイスを使用すると、コードを抽象化して分離できるため、コードの柔軟性と拡張性が高まります。インターフェイスは、共通の動作と制約を定義するために Go 言語で広く使用されています。
5. 拡張と再利用
Go 言語では、コードを拡張および再利用する方法が従来のオブジェクト指向言語 (Java など) とは異なります。Go では、クラスの継承ではなく、コードの拡張と再利用を実現するために、合成、インターフェイス、匿名フィールドなどの機能の使用を推奨しています。
後続の演習用に extend ディレクトリを作成します。以下は、Go 言語での拡張と再利用の詳細な説明です。
結合とネスト
Go 言語の合成により、ある構造タイプを別の構造タイプ内にネストして、コードを再利用できます。ネストされた構造体は、フィールド名を通じてそのメンバーに直接アクセスできます。Composition_test.go を作成する
package extend
import (
"fmt"
"testing"
)
type Engine struct {
Model string
}
type Car struct {
Engine
Brand string
}
func TestComposition(t *testing.T) {
car := Car{
Engine: Engine{Model: "V6"},
Brand: "Toyota",
}
fmt.Println("Car brand:", car.Brand)
fmt.Println("Car engine model:", car.Model) // 直接访问嵌套结构体的字段
}
この例では、合成を使用して、内部にCar
ネストされた構造体を含む構造体を作成します。Engine
ネストを通じて、Car
構造体はEngine
構造体のフィールドに直接アクセスできます。
インターフェースの実装
インターフェイスを使用すると、さまざまな型で実装できる一連のメソッドを定義できます。これにより、ポリモーフィズムとコードの分離が可能になり、異なるタイプのオブジェクトを同じインターフェイスを通じて操作できるようになります。Interface_ext_test.go を作成する
package extend
import (
"fmt"
"math"
"testing"
)
// 定义 Shape 接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义 Circle 结构体
type Circle struct {
Radius float64
}
// 实现 Circle 结构体的方法,以满足 Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 定义 Rectangle 结构体
type Rectangle struct {
Width float64
Height float64
}
// 实现 Rectangle 结构体的方法,以满足 Shape 接口
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func TestInterfaceExt(t *testing.T) {
circle := Circle{Radius: 3}
rectangle := Rectangle{Width: 4, Height: 5}
shapes := []Shape{circle, rectangle}
for _, shape := range shapes {
fmt.Printf("Shape Type: %T\n", shape)
fmt.Printf("Area: %.2f\n", shape.Area())
fmt.Printf("Perimeter: %.2f\n", shape.Perimeter())
fmt.Println("------------")
}
}
上の例では、 とShape
呼ばれるインターフェイスを定義しました。これには 2 つのメソッドと がありArea()
、Perimeter()
それぞれ形状の面積と周囲長を計算するために使用されます。次に、Circle
と構造体の 2 つのメソッドをそれぞれインターフェイスRectangle
を満たすように実装しました。Shape
さまざまなタイプのシェイプ インスタンスをスライスに配置することで、と のメソッドを[]Shape
統一された方法で呼び出すことができ、コードのポリモーフィズムと分離を実現します。このようにして、後から新しい形状を追加しても、インターフェイス メソッドを実装している限り、シームレスに電卓に統合できます。Area()
Perimeter()
Shape
匿名フィールドとメソッドの再利用
匿名フィールドを使用すると、ある構造体は別の構造体のフィールドとメソッドを継承できます。other_ext_test.go を作成する
package extend
import (
"fmt"
"testing"
)
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Println("Animal speaks")
}
type Dog struct {
Animal
Breed string
}
func TestOtherExt(t *testing.T) {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
fmt.Println("Dog name:", dog.Name)
dog.Speak() // 继承了 Animal 的 Speak 方法
}
上記の例では、Dog
struct はAnimal
struct をネストし、それによって のAnimal
フィールドとメソッドを継承します。
これらの方法で、Go 言語のコードを拡張して再利用できます。Go は従来のオブジェクト指向言語ほどクラスの継承を重視していませんが、合成、インターフェイス、匿名フィールドなどの機能を通じて、コードをより柔軟で読みやすくし、低結合を維持するなど、同様の効果を達成できます。
6. 空のインターフェイスとアサーション
空のインターフェイスとアサーションは、未定義の型と型変換を処理するための Go 言語の重要な概念です。
後続の演習用に emptyassert ディレクトリを作成します。以下は、空のインターフェイスとアサーションについての学習の概要です。
空のインターフェース
空のインターフェースは Go 言語の最も基本的なインターフェースであり、メソッド宣言は含まれません。したがって、空のインターフェイスを使用して、あらゆる種類の値を表すことができます。空のインターフェースの宣言方法は ですinterface{}
。
空のインターフェイスの主な用途は、未定義の型を処理する必要があるシナリオです。空のインターフェイスを使用すると、他のプログラミング言語の動的型付けと同様に、あらゆる種類の値を受け入れて保存できます。ただし、空のインターフェイスを使用すると、コンパイル時に具象型をチェックできないため、型の安全性が低下する可能性があることに注意してください。
断言(Type Assertion)
アサーションは、空のインターフェイスの具象型を回復するメカニズムです。これにより、実行時に空のインターフェイスの値の実際の型を確認し、対応する型に変換できます。アサーションの構文は、value.(Type)
はvalue
インターフェイス値、Type
はアサートされる具体的な型です。
検証用に emptyassert_test.go を作成します。
package emptyassert
import (
"fmt"
"testing"
)
func DoSomething(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("String", v)
default:
fmt.Println("Unknow Type")
}
}
func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10)
DoSomething("10")
}
func TestEmptyAssert(t *testing.T) {
var x interface{} = "hello"
str, ok := x.(string)
if ok {
fmt.Println("String:", str)
} else {
fmt.Println("Not a string")
}
}
以下に各テスト機能の内容を一つずつ説明します。
-
func DoSomething(p interface{}) { ... }
DoSomething
:空のインターフェイス パラメーターを受け入れp
、インターフェイス値の実際の型に基づいて型アサーションを実行し、さまざまな型に基づいてさまざまな情報を出力する関数を定義します。 -
を呼び出しfunc TestEmptyInterfaceAssertion(t *testing.T) { ... }
: 空のインターフェイスのアサーション操作をテストします。DoSomething(10)
、整数を10
関数に渡すと、関数は型アサーションに基づいて整数型情報を出力します。を呼び出しDoSomething("10")
、文字列を"10"
関数に渡すと、関数は型アサーションに基づいて文字列型情報を出力します。 -
空のインターフェイス変数を宣言し、それにfunc TestEmptyAssert(t *testing.T) { ... }
: 空のインターフェイスの型アサーション操作をテストします。x
文字列値を割り当てます。"hello"
型アサーションを使用して文字列型かどうかx.(string)
を判断しx
、文字列型である場合は変数に代入してstr
文字列値を出力し、文字列型でない場合は「文字列ではない」と出力します。
これらのテスト関数は、Go 言語での空のインターフェイスのアサーション操作を示します。型アサーションを通じて、空のインターフェイスの特定の型を決定し、対応する操作を実行できます。
概要:空のインターフェイスとアサーションは、未定義の型と型変換を処理するための Go 言語の強力なツールです。空のインターフェイスでは、任意のタイプの値を保存できますが、アサーションを使用すると、実行時にインターフェイス値の実際のタイプを確認して変換できます。これらのメカニズムを使用すると、さまざまな種類の値を処理する必要がある場合に、より柔軟で汎用性の高いコードが可能になります。ただし、空のインターフェイスとアサーションを使用する場合は、型の安全性を維持し、適切なエラー処理を実行するように注意してください。
7.GO インターフェースのベストプラクティス
Go 言語では、インターフェイスを使用するベスト プラクティスにより、コードの可読性、保守性、柔軟性を向上させることができます。Go インターフェイスのベスト プラクティスをいくつか示します。
- 小規模なインターフェイスと大規模なインターフェイス:小規模なインターフェイスを設計するようにしてください。インターフェイスには、大規模で包括的なインターフェイスを設計するのではなく、少数のメソッドのみを含める必要があります。これにより、インターフェイスの実装における不必要な負担が回避され、インターフェイスがより一般的になります。
- 使用シナリオに基づいてインターフェイスを設計する:インターフェイスを設計するときは、特定の実装から始めるのではなく、使用シナリオを考慮する必要があります。アプリケーションでインターフェイスがどのように使用されるか、およびそれらのユースケースを満たすためにインターフェイスがどのようなメソッドを提供する必要があるかを考えてください。
- 適切な名前を使用する:インターフェイスとメソッドの目的と機能が伝わるように、明確な名前を使用します。他の開発者がインターフェイスの目的を簡単に理解できるように、名前は読みやすく表現力豊かなものにする必要があります。
- 不要なインターフェイスを避ける:タイプごとにインターフェイスを作成せず、複数のタイプ間で実際に動作と機能が共有される場合にのみインターフェイスを使用します。不必要な複雑さを避けるために、インターフェイスを過度に使用しないでください。
- インターフェイスを関数パラメータおよび戻り値として使用する:インターフェイスを関数パラメータおよび戻り値として使用すると、関数の汎用性が高まり、さまざまなタイプのパラメータを渡したり、さまざまなタイプの結果を返すことができます。これにより、コードの再利用性とスケーラビリティが向上します。
- コメントとドキュメント:インターフェイスの目的、メソッドの機能、および予想される動作を説明する、インターフェイスに関する明確なドキュメントとコメントを提供します。これは、他の開発者がインターフェイスの使用方法をよりよく理解するのに役立ちます。
- ケース主導の設計を使用する:インターフェイスを設計するときは、使用法の観点から開始し、最初に実際のシナリオでインターフェイスがどのように呼び出されるかを検討し、次にインターフェイスのメソッドとシグネチャを設計します。
- インターフェイスの実装と定義を分離する:インターフェイスの実装をインターフェイスの定義から分離すると、実装がより柔軟になり、インターフェイス定義を変更せずに新しい型を実装できます。
- デフォルトの実装:インターフェイス定義で、特定のメソッドのデフォルト実装を提供できるため、インターフェイス実装時の作業負荷が軽減されます。これは、オプションのメソッドまたは特定のメソッドのデフォルトの動作に役立ちます。
- 空のインターフェイスは注意して使用してください。空のインターフェイス (
interface{}
) を使用すると、型の安全性が低下するため、注意して使用してください。空のインターフェイスは、実際に異なる型の値を処理する必要がある場合にのみ使用し、型のアサーションとエラー処理に注意してください。
つまり、インターフェイスを設計して使用するときは、実際のニーズとプロジェクトの特性に基づいて適切なソリューションを選択する必要があります。上記のベスト プラクティスに従うと、より保守しやすく、スケーラブルで、読みやすい Go コードを作成するのに役立ちます。
(6) 適切なエラーメカニズムを作成する
src ディレクトリに第 6 章を作成する Go 言語のエラー処理メカニズムは、例外を使用する代わりにエラー値を返すことで実装されています。このエラー処理メカニズムは非常に明確で制御可能であるため、開発者はさまざまなエラー状況を正確に処理できます。
1.基本的な使い方の紹介
基本ディレクトリを作成し、basic_error_test.go を書き込みます。
エラーの種類
Go では、エラーはerror
インターフェイスを実装する型として表されます。error
インターフェイスにはメソッドが 1 つだけあります。つまり、Error() string
エラーを説明する文字列を返します。
type error interface {
Error() string
}
戻りエラー値
関数でエラー条件が発生すると、通常はエラー値が返されます。このエラー値は、インターフェイスを実装するカスタム タイプにすることもerror
、 によって作成されるエラーなど、Go 標準ライブラリで事前定義されたエラー タイプにすることもできますerrors.New()
。
エラーチェック
通常、呼び出し元は、関数によって返されたエラーを明示的にチェックして、エラーが発生したかどうかを判断する必要があります。これは、関数を呼び出した後にステートメントを使用することで実現できますif
。
上の 2 つは、次のようにコードを直接記述します。
package basic
import (
"errors"
"fmt"
"testing"
)
var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwoError
}
if n > 100 {
return nil, LargerThenHundredError
}
fibList := []int{1, 1}
for i := 2; /*短变量声明 := */ i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
if v, err := GetFibonacci(1); err != nil {
if err == LessThanTwoError {
fmt.Println("It is less.")
}
t.Error(err)
} else {
t.Log(v)
}
}
2.エラーチェーン
チェーンディレクトリを作成し、error_chain_test.go を書き込みます。
場合によっては、エラーの原因をより深く理解するための追加情報がエラーに含まれることがあります。fmt.Errorf()
追加情報を含むエラーは、関数を使用して作成できます。
ファイルの読み取りおよび書き込み関数を含む、ファイル操作用のライブラリを構築しているとします。ファイルの読み取りまたは書き込み中に、ファイルが存在しない、権限の問題など、さまざまなエラーが発生することがあります。エラーに関するより詳しいコンテキスト情報を提供できるようにしたいと考えています。
package chain
import (
"errors"
"fmt"
"testing"
)
// 自定义文件操作错误类型
type FileError struct {
Op string // 操作类型("read" 或 "write")
Path string // 文件路径
Err error // 原始错误
}
// 实现 error 接口的 Error() 方法
func (e *FileError) Error() string {
return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}
// 模拟文件读取操作
func ReadFile(path string) ([]byte, error) {
// 模拟文件不存在的情况
return nil, &FileError{Op: "read", Path: path, Err: errors.New("file not found")}
}
func TestChain(t *testing.T) {
filePath := "/path/to/nonexistent/file.txt"
_, err := ReadFile(filePath)
if err != nil {
fmt.Println("Error:", err)
// 在这里,我们可以检查错误类型,提取上下文信息
if fileErr, ok := err.(*FileError); ok {
fmt.Printf("Operation: %s\n", fileErr.Op)
fmt.Printf("File Path: %s\n", fileErr.Path)
fmt.Printf("Original Error: %v\n", fileErr.Err)
}
}
}
コードの説明は次のとおりです。
-
FileError
FileError
構造:次のフィールドを含むカスタム エラー タイプを定義します。Op
: 操作タイプ。読み取り (「読み取り」) 操作であるか書き込み (「書き込み」) 操作であるかを示します。Path
: ファイル パス。どのファイルが関係しているかを示します。Err
: 元のエラー。基礎となるエラー情報が含まれています。
-
Error()
メソッド:エラーのテキスト説明を生成するために使用される、構造体のインターフェイスのメソッドFileError
を実装します。error
Error()
-
ReadFile()
機能: ファイル読み取り操作をシミュレートします。この例では、関数はFileError
タイプ のエラーを返し、ファイルが存在しない状況をシミュレートします。 -
TestChain()
テスト関数: エラー処理でカスタム エラー タイプを使用する方法を示します。- ファイル パスが定義され
filePath
、ReadFile(filePath)
ファイル読み取り操作をシミュレートするために関数が呼び出されます。 - エラーをチェックし、エラーが発生した場合はエラーメッセージを出力します。
- エラー処理では、タイプ アサーションを使用して、エラーが特定の
*FileError
タイプであるかどうかを確認し、そうであれば、操作タイプ、ファイル パス、元のエラー情報など、より多くのコンテキスト情報を抽出できます。
- ファイル パスが定義され
3.パニックとリカバリー
Go 言語では、panic
および はrecover
例外状況を処理するためのメカニズムですが、通常のエラー処理メカニズムの代替としてではなく、特定の状況でのみ使用するように注意してください。ここでは、特定の使用例を示した と のpanic
詳細な説明を示します。recover
パニック
panic
ディレクトリを作成し、 panic
_test.go と書き込みます。panic
は、実行時パニックを引き起こすために使用される組み込み関数です。プログラムで実行を続行できない致命的なエラーが発生した場合、 を使用してpanic
プログラムの通常のフローを中断できます。ただし、誤用は避けてください。panic
わかりやすいエラー メッセージが表示されずにプログラムがクラッシュする可能性があります。通常は、panic
範囲外のスライス インデックスなど、プログラム内の回復不能なエラーを示すために使用されます。
package panic
import (
"fmt"
"testing"
)
func TestPanic(t *testing.T) {
arr := []int{1, 2, 3}
index := 4
if index >= len(arr) {
panic("Index out of range")
}
element := arr[index]
fmt.Println("Element:", element)
}
上の例では、インデックスがスライスの範囲index
を超えるとarr
トリガーされpanic
、プログラムがクラッシュします。この場合、panic
プログラム内の回復不可能なエラーを示すために使用されます。
回復する
recover
ディレクトリを作成し、 recover
_test.go と書き込みます。また、によって引き起こされる実行時パニックrecover
から回復するための組み込み関数。panic
これはdefer
遅延関数 ( ) 内でのみ使用でき、エラーを処理するためではなく、プログラムの制御フローを復元するために使用されます。通常、発生後panic
、recover
遅延関数でそれを捕捉しpanic
、クリーンアップを実行すると、プログラムは実行を続行します。
package recover
import (
"fmt"
"testing"
)
func cleanup() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}
func TestRecover(t *testing.T) {
defer cleanup()
panic("Something went wrong")
fmt.Println("This line will not be executed")
}
上記の例では、panic
トリガー後、cleanup
関数内の がrecover
キャプチャされpanic
、エラー メッセージが出力されます。panic
その後、プログラムは実行を続けますが、制御フローはトリガーされた場所に戻らないため、fmt.Println
実行されないことに注意してください。
要約すると、panic
回復recover
不可能なエラーや遅延関数のクリーンアップ操作などの特別な場合にのみ、注意して使用する必要があります。ほとんどの場合、例外を処理するよりもエラー戻り値を使用する必要があります。これは、このアプローチの方が安全で制御しやすく、より適切なエラー情報とエラー処理が提供されるためです。panic
および の使用は、回復不可能なエラーが発生した場合など、特定の状況でのみ検討してくださいrecover
。
4. カスタムエラータイプ
define
ディレクトリを作成し、 error_define
_test.go と書き込みます。
Go では、error
インターフェイスの要件を満たすだけで、必要に応じて独自のエラー タイプを定義できます。これにより、より説明的で状況に応じたエラー タイプを作成できます。
Go では、カスタム エラー タイプは、より説明的で状況に応じたエラーを作成して、より適切なエラー情報を提供するための強力な方法です。カスタム エラー タイプは、error
インターフェイスの要件を満たす必要があります。つまり、Error() string
メソッドを実装する必要があります。以下に、エラー タイプをカスタマイズし、そのユースケースを検証する方法を示す例を示します。
package define
import (
"fmt"
"testing"
"time"
)
// 自定义错误类型
type TimeoutError struct {
Operation string // 操作名称
Timeout time.Time // 超时时间
}
// 实现 error 接口的 Error() 方法
func (e TimeoutError) Error() string {
return fmt.Sprintf("Timeout error during %s operation. Timeout at %s", e.Operation, e.Timeout.Format("2006-01-02 15:04:05"))
}
// 模拟执行某个操作,可能会超时
func PerformOperation() error {
// 模拟操作超时
timeout := time.Now().Add(5 * time.Second)
if time.Now().After(timeout) {
return TimeoutError{Operation: "PerformOperation", Timeout: timeout}
}
// 模拟操作成功
return nil
}
func TestDefineError(t *testing.T) {
err := PerformOperation()
if err != nil {
// 检查错误类型并打印错误信息
if timeoutErr, ok := err.(TimeoutError); ok {
fmt.Println("Error Type:", timeoutErr.Operation)
fmt.Println("Timeout At:", timeoutErr.Timeout)
}
fmt.Println("Error:", err)
} else {
fmt.Println("Operation completed successfully.")
}
}
コードの説明は次のとおりです。
-
TimeoutError
TimeoutError
構造:次のフィールドを含むカスタム エラー タイプを定義します。Operation
: タイムアウトになった操作を示す操作名。Timeout
: タイムアウト時間。操作がタイムアウトした時点を示します。
-
Error()
メソッド:エラーのテキスト説明を生成するために使用される、構造体のインターフェイスのメソッドTimeoutError
を実装します。error
Error()
-
PerformOperation()
機能: 操作の実行をシミュレートするため、タイムアウトになる可能性があります。この例では、現在時刻がタイムアウトを超えると、次TimeoutError
のタイプのエラーが返されます。 -
TestDefineError()
テスト関数: エラー処理でカスタム エラー タイプを使用する方法を示します。- 関数を呼び出して
PerformOperation()
操作をシミュレートし、エラーが発生したかどうかを確認します。 - エラーが発生した場合は、まずエラーの種類が であるかどうかを確認し
TimeoutError
、エラーの種類が である場合は、タイムアウト操作とタイムアウト時間を抽出し、関連する情報を出力します。 - 最後に、エラーが発生したかどうかに関係なく、エラーメッセージまたは正常終了メッセージが出力されます。
- 関数を呼び出して
この例では、エラー タイプをカスタマイズする方法と、エラー処理でこれらのカスタム エラー タイプを活用してより多くのコンテキスト情報を提供し、エラー処理をより有益かつ柔軟にする方法を示します。ここでは、TimeoutError
タイムアウト操作とタイムアウト期間に関する追加情報が提供されます。