Golang チュートリアル: 初心者向けの Go プログラミング言語を学ぶ
Goとは何ですか?
Go (Golang とも呼ばれる) は、Google が開発したオープンソースのプログラミング言語です。静的に型付けされたコンパイル言語です。Go は並行プログラミングをサポートしており、複数のプロセスを同時に実行できます。これは、チャネル、ゴルーチンなどを使用して実現されます。Go 言語にはガベージ コレクションがあり、それ自体がメモリ管理を行い、関数の遅延実行を可能にします。
この「Go 言語学習チュートリアル」では、Golang の基本をすべて学びます。
GOをダウンロードしてインストールする方法
ステップ1) に行く https://golang.org/dl/。 OS のバイナリをダウンロードします。
ステップ2) Double インストーラーをクリックし、「実行」をクリックします。
ステップ3) [次へ]をクリックします
ステップ4) インストールフォルダーを選択し、「次へ」をクリックします。
ステップ5) インストールが完了したら、[完了] をクリックします。
ステップ6) インストールが完了したら、ターミナルを開いて次のように入力して確認できます。
go version
これにより、インストールされている go のバージョンが表示されます
初めての Go プログラム – Go Hello World!
StudyGo という名前のフォルダーを作成します。 この Go 言語チュートリアルでは、このフォルダー内に Go プログラムを作成します。 Go ファイルは次の拡張子で作成されます 。行く。 次の構文を使用して Go プログラムを実行できます。
go run <filename>
first.go というファイルを作成し、以下のコードを追加して保存します。
package main import ("fmt") func main() { fmt.Println("Hello World! This is my first Go program\n") }
ターミナルでこのフォルダーに移動します。 コマンドを使用してプログラムを実行します
まず走ってください。
出力された印刷を確認できます
Hello World! This is my first Go program
それでは、上記のプログラムについて説明しましょう。
package main – すべての Go 言語プログラムはパッケージ名で始まる必要があります。 Go を使用すると、別の Go プログラムでパッケージを使用できるため、コードの再利用がサポートされます。 Go プログラムの実行は、main という名前のパッケージ内のコードから始まります。
import fmt – パッケージ fmt をインポートします。 このパッケージは I/O 機能を実装します。
func main() – これはプログラムの実行が開始される関数です。 main 関数は常に main パッケージに配置する必要があります。 main() の下の { } 内にコードを記述できます。
fmt.Println – これは、fmt の Println 関数によって画面上のテキストを印刷します。
注: この Go チュートリアルの以下のセクションで、「コードを実行する」という場合は、コードを .go 拡張子が付いたファイルに保存し、次の構文を使用して実行することを意味します。
go run <filename>
データ型
型(データ型)は、変数に格納される値の型、関数が返す値の型などを表します。
Go 言語には XNUMX つの基本的なタイプがあります
数値型 – 整数、浮動小数点、複素数値などの数値を表します。さまざまな数値型は次のとおりです。
int8 – 8 ビットの符号付き整数。
int16 – 16 ビットの符号付き整数。
int32 – 32 ビットの符号付き整数。
int64 – 64 ビットの符号付き整数。
uint8 – 8 ビットの符号なし整数。
uint16 – 16 ビットの符号なし整数。
uint32 – 32 ビットの符号なし整数。
uint64 – 64 ビットの符号なし整数。
float32 – 32 ビット浮動小数点数。
float64 – 64 ビット浮動小数点数。
complex64 – float32 の実数部と虚数部を持ちます。
complex128 – float32 の実数部と虚数部を持ちます。
文字列型 – バイト(文字)のシーケンスを表します。文字列の連結、部分文字列の抽出など、文字列に対してさまざまな操作を行うことができます。
ブール型 – true または false の 2 つの値を表します。
Golang インターフェース
Golang インターフェース オブジェクトの動作を実装するために Type によって使用されるメソッド シグネチャのコレクションです。 Golang インターフェイスの主な目的は、メソッド シグネチャに名前、引数、戻り値の型を提供することです。 メソッドを宣言して実装するのは Type 次第です。 Golang のインターフェイスは、キーワード「interface」を使用して宣言できます。
変数
変数は、ある種の値を格納するメモリの場所を指します。 type パラメーター (以下の構文内) は、メモリー位置に保管できる値のタイプを表します。
変数は次の構文を使用して宣言できます。
var <variable_name> <type>
型の変数を宣言すると、その変数をその型の任意の値に割り当てることができます。
次を使用して、宣言自体中に変数に初期値を与えることもできます。
var <variable_name> <type> = <value>
初期値を使用して変数を宣言すると、割り当てられた値の型から変数の型を推測します。 したがって、次の構文を使用して宣言中に型を省略できます。
var <variable_name> = <value>
また、次の構文で複数の変数を宣言できます。
var <variable_name1>, <variable_name2> = <value1>, <value2>
この Go チュートリアルの以下のプログラムには、変数宣言の Golang の例がいくつか含まれています
package main import "fmt" func main() { //declaring a integer variable x var x int x=3 //assigning x the value 3 fmt.Println("x:", x) //prints 3 //declaring a integer variable y with value 20 in a single statement and prints it var y int=20 fmt.Println("y:", y) //declaring a variable z with value 50 and prints it //Here type int is not explicitly mentioned var z=50 fmt.Println("z:", z) //Multiple variables are assigned in single line- i with an integer and j with a string var i, j = 100,"hello" fmt.Println("i and j:", i,j) }
出力は次のようになります
x: 3 y: 20 z: 50 i and j: 100 hello
Go 言語では、次のように var キーワードを省略することで、値を持つ変数を簡単に宣言する方法も提供されます。
<variable_name> := <value>
使用したことに注意してください := =。 := は、すでに宣言されている変数に値を割り当てるためだけに使用することはできません。 := は値の宣言と割り当てに使用されます。
次のコードを含むassign.goというファイルを作成します。
package main import ("fmt") func main() { a := 20 fmt.Println(a) //gives error since a is already declared a := 30 fmt.Println(a) }
go run assign.go を実行すると、結果が次のように表示されます。
./assign.go:7:4: no new variables on left side of :=
初期値なしで宣言された変数は、数値型の場合は 0、ブール型の場合は false、文字列の場合は空の文字列になります。
定数
定数変数は、一度割り当てられると値を変更できない変数です。 Go プログラミング言語の定数はキーワード「const」を使用して宣言されます。
constant.goというファイルを作成し、次のコードを記述します。
package main import ("fmt") func main() { const b =10 fmt.Println(b) b = 30 fmt.Println(b) }
go run constant.go を実行すると、結果が次のように表示されます。
.constant.go:7:4: cannot assign to b
For ループの例
ループは、条件に基づいてステートメントのブロックを繰り返し実行するために使用されます。 ほとんどのプログラミング言語は、for、while、do while の 3 種類のループを提供します。 ただし、Go プログラミング言語は for ループのみをサポートします。
Golang の for ループの構文は次のとおりです。
for initialisation_expression; evaluation_expression; iteration_expression{ // one or more statement }
initialisation_expression は、Golang for ループで最初に (一度だけ) 実行されます。
次に、evaluation_expression が評価され、それが true の場合、ブロック内のコードが実行されます。
iteration_expression id が実行され、evaluation_expression が再度評価されます。 true の場合、ステートメント ブロックが再度実行されます。 これは、evaluation_expression が false になるまで続きます。
以下のプログラムをファイルにコピーして実行すると、Golangのforループが1から5までの数字を出力するのがわかります。
package main import "fmt" func main() { var i int for i = 1; i <= 5; i++ { fmt.Println(i) } }
出力は
1 2 3 4 5
それ以外の場合
If else は条件文です。 シナックスは、
if condition{ // statements_1 }else{ // statements_2 }
ここで条件が評価され、それが true の場合、statement_1 が実行され、そうでない場合は、statements_2 が実行されます。
else なしで if ステートメントを使用することもできます。 if else ステートメントを連鎖させることもできます。 以下のプログラムで if else について詳しく説明します。
以下のプログラムを実行します。 数値 x が 10 未満かどうかをチェックします。10 未満の場合は、「x は XNUMX 未満です」と出力します。
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x < 10 fmt.Println("x is less than 10") } }
ここでは、x の値が 10 より大きいため、if ブロック条件内のステートメントは実行されません。
次に、以下のプログラムを見てください。 この Go プログラミング言語チュートリアルでは、if 評価の失敗時に実行される else ブロックがあります。
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else { //Executes if x >= 10 fmt.Println("x is greater than or equals 10") } }
このプログラムは出力を提供します
x is greater than or equals 10
この Go チュートリアルでは、複数の if else ブロック (if else チェーン) を含むプログラムを見ていきます。 以下の Go の例を実行します。 数値が 10 未満であるか、10 ~ 90 の間であるか、または 90 を超えているかをチェックします。
package main import "fmt" func main() { var x = 100 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else if x >= 10 && x <= 90 { //Executes if x >= 10 and x<=90 fmt.Println("x is between 10 and 90") } else { //Executes if both above cases fail i.e x>90 fmt.Println("x is greater than 90") } }
ここでは、最初に if 条件で x が 10 未満であるかどうかをチェックし、そうでないことを確認します。 したがって、次の条件 (else if) が 10 から 90 の間であるかどうかをチェックしますが、これも false です。 したがって、else セクションの下のブロックが実行され、出力が得られます。
x is greater than 90
スイッチ
Switch も条件文です。 Switch ステートメントは式を評価し、その結果が利用可能な値 (ケース) のセットと比較されます。 一致が見つかると、その match(case) に関連付けられたステートメントが実行されます。 一致するものが見つからない場合は、何も実行されません。 他に一致するものが見つからなかった場合に実行されるデフォルトのケースを switch に追加することもできます。 スイッチの構文は次のとおりです。
switch expression { case value_1: statements_1 case value_2: statements_2 case value_n: statements_n default: statements_default }
ここでは、式の値がそれぞれの場合の値と比較されます。 一致が見つかると、そのケースに関連付けられたステートメントが実行されます。 一致するものが見つからない場合は、デフォルトのセクションにあるステートメントが実行されます。
以下のプログラムを実行します
package main import "fmt" func main() { a,b := 2,1 switch a+b { case 1: fmt.Println("Sum is 1") case 2: fmt.Println("Sum is 2") case 3: fmt.Println("Sum is 3") default: fmt.Println("Printing default") } }
次のように出力を取得します
Sum is 3
a と b の値を 3 に変更すると、結果は次のようになります。
Printing default
カンマで区切ることにより、XNUMX つのケースに複数の値を含めることもできます。
配列
配列は、同じ型の要素の固定サイズの名前付きシーケンスを表します。 整数と文字の両方を含む配列を持つことはできません。 サイズを定義した後は、配列のサイズを変更することはできません。
配列を宣言する構文は次のとおりです。
var arrayname [size] type
各配列要素には、次の構文を使用して値を割り当てることができます。
arrayname [index] = value
配列インデックスの開始位置 0 ~ サイズ-1.
次の構文を使用して、宣言中に配列要素に値を割り当てることができます。
arrayname := [size] type {value_0,value_1,…,value_size-1}
サイズを次のように置き換えることにより、配列を値で宣言するときにサイズ パラメーターを無視することもできます。 ... コンパイラは値の数から長さを見つけます。 構文は
arrayname := […] type {value_0,value_1,…,value_size-1}
配列の長さは、次の構文を使用して確認できます。
len(arrayname)
配列を理解するには、以下の Go 例を実行します。
package main import "fmt" func main() { var numbers [3] string //Declaring a string array of size 3 and adding elements numbers[0] = "One" numbers[1] = "Two" numbers[2] = "Three" fmt.Println(numbers[1]) //prints Two fmt.Println(len(numbers)) //prints 3 fmt.Println(numbers) // prints [One Two Three] directions := [...] int {1,2,3,4,5} // creating an integer array and the size of the array is defined by the number of elements fmt.Println(directions) //prints [1 2 3 4 5] fmt.Println(len(directions)) //prints 5 //Executing the below commented statement prints invalid array index 5 (out of bounds for 5-element array) //fmt.Println(directions[5]) }
出力
Two 3 [One Two Three] [1 2 3 4 5] 5
Golang のスライスと追加関数
スライスは配列の一部またはセグメントです。 または、それが指す基礎となる配列のビューまたは部分ビューです。 配列の場合と同様に、スライス名とインデックス番号を使用してスライスの要素にアクセスできます。 配列の長さは変更できませんが、スライスのサイズは変更できます。
スライスの内容は、実際には配列の要素へのポインタです。 その意味は スライス内の要素を変更すると、基礎となる配列の内容も影響を受けます。
スライスを作成するための構文は次のとおりです。
var slice_name [] type = array_name[start:end]
これにより、インデックス start から end-1 にある要素を持つ array_name という名前の配列から、slice_name という名前のスライスが作成されます。
この Golang チュートリアルでは、以下のプログラムを実行します。 プログラムは配列からスライスを作成し、それを印刷します。 また、スライス内の内容を変更すると、実際の配列が変更されることがわかります。
package main import "fmt" func main() { // declaring array a := [5] string {"one", "two", "three", "four", "five"} fmt.Println("Array after creation:",a) var b [] string = a[1:4] //created a slice named b fmt.Println("Slice after creation:",b) b[0]="changed" // changed the slice data fmt.Println("Slice after modifying:",b) fmt.Println("Array after slice modification:",a) }
これは結果を次のように出力します
Array after creation: [one two three four five] Slice after creation: [two three four] Slice after modifying: [changed three four] Array after slice modification: [one changed three four five]
Golang len、Golang append など、スライスに適用できる特定の関数があります
len(スライス名) – スライスの長さを返します
append(スライス名, 値_1, 値_2) – Golang append は、value_1 と value_2 を既存のスライスに追加するために使用されます。
append(slice_nale1,slice_name2…) – スライス名2をスライス名1に追加します
次のプログラムを実行します。
package main import "fmt" func main() { a := [5] string {"1","2","3","4","5"} slice_a := a[1:3] b := [5] string {"one","two","three","four","five"} slice_b := b[1:3] fmt.Println("Slice_a:", slice_a) fmt.Println("Slice_b:", slice_b) fmt.Println("Length of slice_a:", len(slice_a)) fmt.Println("Length of slice_b:", len(slice_b)) slice_a = append(slice_a,slice_b...) // appending slice fmt.Println("New Slice_a after appending slice_b :", slice_a) slice_a = append(slice_a,"text1") // appending value fmt.Println("New Slice_a after appending text1 :", slice_a) }
出力は次のようになります
Slice_a: [2 3] Slice_b: [two three] Length of slice_a: 2 Length of slice_b: 2 New Slice_a after appending slice_b : [2 3 two three] New Slice_a after appending text1 : [2 3 two three text1]
プログラムは最初に 2 つのスライスを作成し、その長さを出力します。 次に、あるスライスを別のスライスに追加し、結果のスライスに文字列を追加します。
機能
関数は、特定のタスクを実行するステートメントのブロックを表します。 関数宣言では、関数名、戻り値の型、入力パラメーターが示されます。 関数定義は、関数に含まれるコードを表します。 関数を宣言するための構文は次のとおりです。
func function_name(parameter_1 type, parameter_n type) return_type { //statements }
パラメータと戻り値の型はオプションです。 また、関数から複数の値を返すこともできます。
さて、この Golang チュートリアルでは、次の Golang の例を実行してみましょう。ここで、calc という関数は 2 つの数値を受け入れ、加算と減算を実行して、両方の値を返します。
package main import "fmt" //calc is the function name which accepts two integers num1 and num2 //(int, int) says that the function returns two values, both of integer type. func calc(num1 int, num2 int)(int, int) { sum := num1 + num2 diff := num1 - num2 return sum, diff } func main() { x,y := 15,10 //calls the function calc with x and y an d gets sum, diff as output sum, diff := calc(x,y) fmt.Println("Sum",sum) fmt.Println("Diff",diff) }
出力は次のようになります
Sum 25 Diff 5
パッケージ
パッケージはコードを整理するために使用されます。 大規模なプロジェクトでは、単一のファイルにコードを記述することは現実的ではありません。 Go プログラミング言語を使用すると、さまざまなパッケージの下にコードを整理できます。 これにより、コードの可読性と再利用性が向上します。 実行可能な Go プログラムには main という名前のパッケージが含まれている必要があり、プログラムの実行は main という名前の関数から開始されます。 次の構文を使用して、プログラムに他のパッケージをインポートできます。
import package_name
この Golang チュートリアルでは、次の Golang の例でパッケージを作成して使用する方法について説明します。
ステップ1) package_example.go というファイルを作成し、以下のコードを追加します。
package main import "fmt" //the package to be created import "calculation" func main() { x,y := 15,10 //the package will have function Do_add() sum := calculation.Do_add(x,y) fmt.Println("Sum",sum) }
上記のプログラムでは、fmt は Go プログラミング言語が主に I/O 目的で提供するパッケージです。 また、calculation という名前のパッケージも表示されます。 main() 内には、ステップサム := Calculation.Do_add(x,y) が表示されます。 これは、パッケージ計算から関数 Do_add を呼び出していることを意味します。
ステップ2) まず、パッケージ計算を、go の src フォルダーの下にある同じ名前のフォルダー内に作成する必要があります。 go のインストールされたパスは、PATH 変数から見つけることができます。
Mac の場合、echo $PATH を実行してパスを見つけます。
したがって、パスは /usr/local/go です。
Windowsの場合は、echo %GOROOT%を実行してパスを見つけます。
ここでのパスは C:\Go\ です。
ステップ3) src フォルダー (Mac の場合は /usr/local/go/src、Windows の場合は C:\Go\src) に移動します。コードから、パッケージ名は計算です。Go では、パッケージを src ディレクトリの下の同じ名前のディレクトリに配置する必要があります。src フォルダーに、calculation という名前のディレクトリを作成します。
ステップ4) 計算ディレクトリ内に calc.go という名前のファイルを作成し (任意の名前を付けることができますが、コード内のパッケージ名が重要です。ここでは計算とします)、以下のコードを追加します。
package calculation func Do_add(num1 int, num2 int)(int) { sum := num1 + num2 return sum }
ステップ5) 計算ディレクトリから go install コマンドを実行すると、calc.go がコンパイルされます。
ステップ6) 次に、package_example.go に戻り、 go run package_example.go を実行します。 出力は Sum 25 になります。
関数 Do_add の名前は大文字で始まることに注意してください。 これは、Go では関数名が大文字で始まる場合、他のプログラムが参照 (アクセス) できることを意味し、それ以外の場合は他のプログラムがアクセスできないことを意味するためです。 関数名が do_add の場合、エラーが発生します。
エクスポートされていない名前の Calculation.calc. を参照することはできません。
遅延とスタッキング遅延
defer ステートメントは、defer ステートメントを含む関数が実行を完了するまで関数呼び出しの実行を延期するために使用されます。
これを例で学びましょう:
package main import "fmt" func sample() { fmt.Println("Inside the sample()") } func main() { //sample() will be invoked only after executing the statements of main() defer sample() fmt.Println("Inside the main()") }
出力は次のようになります
Inside the main() Inside the sample()
ここで、sample() の実行は、外側の関数 (main()) の実行が完了するまで延期されます。
スタッキング defer は、複数の defer ステートメントを使用します。 関数内に複数の defer ステートメントがあるとします。 Go はすべての遅延関数呼び出しをスタックに配置し、外側の関数が返されると、スタックされた関数がスタック内で実行されます。 後入れ先出し(LIFO)順序。 以下の例でこれを確認できます。
以下のコードを実行します
package main import "fmt" func display(a int) { fmt.Println(a) } func main() { defer display(1) defer display(2) defer display(3) fmt.Println(4) }
出力は次のようになります
4 3 2 1
ここでは、main() 内のコードが最初に実行され、次に遅延関数呼び出しが逆の順序 (つまり 4、3,2,1、XNUMX、XNUMX) で実行されます。
ポインタ
ポインターについて説明する前に、まず '&' 演算子について説明します。'&' 演算子は、変数のアドレスを取得するために使用されます。つまり、'&a' は変数 a のメモリ アドレスを出力します。
この Golang チュートリアルでは、以下のプログラムを実行して、変数の値とその変数のアドレスを表示します。
package main import "fmt" func main() { a := 20 fmt.Println("Address:",&a) fmt.Println("Value:",a) }
結果は
Address: 0xc000078008 Value: 20
ポインタ変数には、別の変数のメモリ アドレスが格納されます。 次の構文を使用してポインタを定義できます。
var variable_name *type
アスタリスク(*)は変数がポインタであることを表します。 以下のプログラムを実行するとさらに理解できます
package main import "fmt" func main() { //Create an integer variable a with value 20 a := 20 //Create a pointer variable b and assigned the address of a var b *int = &a //print address of a(&a) and value of a fmt.Println("Address of a:",&a) fmt.Println("Value of a:",a) //print b which contains the memory address of a i.e. &a fmt.Println("Address of pointer b:",b) //*b prints the value in memory address which b contains i.e. the value of a fmt.Println("Value of pointer b",*b) //increment the value of variable a using the variable b *b = *b+1 //prints the new value using a and *b fmt.Println("Value of pointer b",*b) fmt.Println("Value of a:",a)}
出力は次のようになります
Address of a: 0x416020 Value of a: 20 Address of pointer b: 0x416020 Value of pointer b 20 Value of pointer b 21 Value of a: 21
構造
構造体はユーザー定義のデータ型であり、それ自体に同じ型または異なる型の要素がもう XNUMX つ含まれています。
構造体の使用は 2 段階のプロセスです。
まず、構造体の型を作成(宣言)します。
次に、その型の変数を作成して値を保存します。
構造体は主に、関連するデータをまとめて保存する場合に使用されます。
名前、年齢、住所が含まれる従業員情報について考えてみましょう。 これは2つの方法で処理できます
3 つの配列を作成します。XNUMX つの配列には従業員の名前が格納され、XNUMX つは年齢が格納され、XNUMX 番目の配列には年齢が格納されます。
名前、住所、年齢の 3 つのフィールドを含む構造体のタイプを宣言します。 各要素が名前、住所、および年齢を持つ構造体オブジェクトであるその構造体タイプの配列を作成します。
最初のアプローチは効率的ではありません。 このようなシナリオでは、構造の方が便利です。
構造体を宣言する構文は次のとおりです。
type structname struct { variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type }
構造体宣言の例は次のとおりです。
type emp struct { name string address string age int }
ここでは、emp という名前の新しいユーザー定義型が作成されます。 これで、次の構文を使用して emp 型の変数を作成できます。
var variable_name struct_name
例を次に示します。
var empdata1 emp
empdata1 の値を次のように設定できます。
empdata1.name = "John" empdata1.address = "Street-1, Bangalore" empdata1.age = 30
構造体変数を作成し、次のように値を割り当てることもできます。
empdata2 := emp{"Raj", "Building-1, Delhi", 25}
ここでは、要素の順序を維持する必要があります。 Raj は名前に、次の要素はアドレスに、最後の要素は age にマップされます。
以下のコードを実行します
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //function which accepts variable of emp type and prints name property func display(e emp) { fmt.Println(e.name) } func main() { // declares a variable, empdata1, of the type emp var empdata1 emp //assign values to members of empdata1 empdata1.name = "John" empdata1.address = "Street-1, London" empdata1.age = 30 //declares and assign values to variable empdata2 of type emp empdata2 := emp{"Raj", "Building-1, Paris", 25} //prints the member name of empdata1 and empdata2 using display function display(empdata1) display(empdata2) }
出力
John Raj
メソッド(関数ではない)
メソッドは、レシーバー引数を持つ関数です。 Archi構造的には、 func キーワードとメソッド名の間にあります。メソッドの構文は次のとおりです
func (variable variabletype) methodName(parameter1 paramether1type) { }
上記のプログラム例を、関数の代わりにメソッドを使用するように変換してみましょう。
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //Declaring a function with receiver of the type emp func(e emp) display() { fmt.Println(e.name) } func main() { //declaring a variable of type emp var empdata1 emp //Assign values to members empdata1.name = "John" empdata1.address = "Street-1, Lodon" empdata1.age = 30 //declaring a variable of type emp and assign values to members empdata2 := emp { "Raj", "Building-1, Paris", 25} //Invoking the method using the receiver of the type emp // syntax is variable.methodname() empdata1.display() empdata2.display() }
Go はオブジェクト指向言語ではなく、クラスの概念がありません。 メソッドは、クラスの関数が objectname.functionname() という構文を使用して呼び出されるオブジェクト指向プログラムで何を行うかの感覚を与えます。
並行性
Go はタスクの同時実行をサポートしています。つまり、Go は複数のタスクを同時に実行できます。これは並列処理の概念とは異なります。並列処理では、タスクは小さなサブタスクに分割され、並列に実行されます。一方、同時実行では、複数のタスクが同時に実行されます。Go では、Goroutine とチャネルを使用して同時実行が実現されます。
ゴルーチン
goroutine は、他の関数と同時に実行できる関数です。 通常、関数が呼び出されると制御は呼び出された関数に転送され、実行が完了すると制御は呼び出し元の関数に戻ります。 その後、呼び出し側関数は実行を継続します。 呼び出し側関数は、呼び出された関数の実行が完了するまで待機してから、残りのステートメントに進みます。
ただし、ゴルーチンの場合、呼び出し関数は、呼び出された関数の実行が完了するまで待機しません。 次のステートメントで引き続き実行されます。 プログラム内に複数のゴルーチンを含めることができます。
また、メインプログラムはステートメントの実行が完了すると終了し、呼び出されたゴルーチンの完了を待ちません。
Goroutine は、キーワード go に続いて関数呼び出しを使用して呼び出されます。
例
go add(x,y)
以下の Golang の例でゴルーチンを理解できます。 以下のプログラムを実行します
package main import "fmt" func display() { for i:=0; i<5; i++ { fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() //The main() continues without waiting for display() for i:=0; i<5; i++ { fmt.Println("In main") } }
出力は次のようになります
In main In main In main In main In main
ここでは、ゴルーチンが開始される前に、メインプログラムが実行を完了しました。 display() は、次の構文を使用して呼び出されるゴルーチンです。
go function_name(parameter list)
上記のコードでは、main() は display() の完了を待機せず、display() がコードを実行する前に main() が実行を完了します。 そのため、display() 内の print ステートメントは印刷されませんでした。
次に、display() からのステートメントも出力するようにプログラムを変更します。 main() の for ループに 2 秒の遅延を追加し、display() の for ループに 1 秒の遅延を追加します。
package main import "fmt" import "time" func display() { for i:=0; i<5; i++ { time.Sleep(1 * time.Second) fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() for i:=0; i<5; i++ { time.Sleep(2 * time.Second) fmt.Println("In main") } }
出力は次のようになります。
In display In main In display In display In main In display In display In main In main In main
ここでは、同時実行により両方のループが重複して実行されていることがわかります。
チャンネル
チャネルは、関数が相互に通信するための方法です。 これは、あるルーチンがデータを配置し、Golang サーバー内の別のルーチンによってアクセスされる場所への媒体として考えることができます。
チャネルは次の構文で宣言できます。
channel_variable := make(chan datatype)
サブスクリプション型フィットネスアプリでは、
ch := make(chan int)
次の構文を使用してデータをチャネルに送信できます。
channel_variable <- variable_name
例
ch <- x
次の構文を使用してチャネルからデータを受信できます。
variable_name := <- channel_variable
例
y := <- ch
上記の Go 言語の goroutine の例では、メイン プログラムが goroutine を待機しないことがわかりました。 しかし、チャネルが関与している場合はそうではありません。 goroutine がチャネルにデータをプッシュする場合、main() はデータを取得するまでチャネル データを受信するステートメントを待機するとします。
これは、以下の Go 言語の例でわかります。 まず、通常のゴルーチンを作成して動作を確認します。 次に、チャネルを使用するようにプログラムを変更し、動作を確認します。
以下のプログラムを実行します
package main import "fmt" import "time" func display() { time.Sleep(5 * time.Second) fmt.Println("Inside display()") } func main() { go display() fmt.Println("Inside main()") }
出力は次のようになります
Inside main()
main() は実行を終了し、ゴルーチンが実行される前に終了しました。 そのため、display() 内の print は実行されませんでした。
次に、チャネルを使用するように上記のプログラムを変更し、動作を確認します。
package main import "fmt" import "time" func display(ch chan int) { time.Sleep(5 * time.Second) fmt.Println("Inside display()") ch <- 1234 } func main() { ch := make(chan int) go display(ch) x := <-ch fmt.Println("Inside main()") fmt.Println("Printing x in main() after taking from channel:",x) }
出力は次のようになります
Inside display() Inside main() Printing x in main() after taking from channel: 1234
ここで何が起こるかというと、x に到達すると main() が実行されます。:= <-ch はチャネル ch 上のデータを待ちます。 display() は 5 秒待ってからデータをチャネル ch にプッシュします。 main() はチャネルからデータを受信するとブロックが解除され、実行を継続します。
データをチャネルにプッシュする送信者は、チャネルを閉じることで、これ以上データがチャネルに追加されないことを受信者に通知できます。 これは主に、ループを使用してデータをチャネルにプッシュする場合に使用されます。 チャネルは次を使用して閉じることができます
close(channel_name)
また、受信側では、次のコマンドを使用してチャネルからデータをフェッチしながら、追加の変数を使用してチャネルが閉じているかどうかを確認できます。
variable_name, status := <- channel_variable
ステータスが True の場合は、チャネルからデータを受信したことを意味します。 false の場合、閉じられたチャネルから読み取ろうとしていることを意味します
ゴルーチン間の通信にチャネルを使用することもできます。 2 つのゴルーチンを使用する必要があります。XNUMX つはチャネルにデータをプッシュし、もう XNUMX つはチャネルからデータを受信します。 以下のプログラムを参照してください
package main import "fmt" import "time" //This subroutine pushes numbers 0 to 9 to the channel and closes the channel func add_to_channel(ch chan int) { fmt.Println("Send data") for i:=0; i<10; i++ { ch <- i //pushing data to channel } close(ch) //closing the channel } //This subroutine fetches data from the channel and prints it. func fetch_from_channel(ch chan int) { fmt.Println("Read data") for { //fetch data from channel x, flag := <- ch //flag is true if data is received from the channel //flag is false when the channel is closed if flag == true { fmt.Println(x) }else{ fmt.Println("Empty channel") break } } } func main() { //creating a channel variable to transport integer values ch := make(chan int) //invoking the subroutines to add and fetch from the channel //These routines execute simultaneously go add_to_channel(ch) go fetch_from_channel(ch) //delay is to prevent the exiting of main() before goroutines finish time.Sleep(5 * time.Second) fmt.Println("Inside main()") }
ここでは2つのサブルーチンがあり、0つはチャネルにデータをプッシュし、もう9つはチャネルにデータを出力します。関数add_to_channelはXNUMXからXNUMXまでの数字を追加し、チャネルを閉じます。同時に関数fetch_from_channelは
x, flag := <- ch を実行し、データが利用可能になると、データを出力します。 フラグが false (チャネルが閉じられていることを意味する) になると終了します。
main() 内の wait は、ゴルーチンが実行を終了するまで main() が終了しないようにするために与えられます。
コードを実行すると、次のような出力が表示されます。
Read data Send data 0 1 2 3 4 5 6 7 8 9 Empty channel Inside main()
選択する
Select は、チャネルで機能する switch ステートメントとして考えることができます。ここで、case ステートメントはチャネル操作になります。通常、各 case ステートメントはチャネルから読み取られます。いずれかのケースが準備完了 (チャネルが読み取られる) になると、そのケースに関連付けられたステートメントが実行されます。複数のケースが準備完了の場合は、ランダムに 1 つが選択されます。いずれのケースも準備完了でない場合、実行されるデフォルトのケースを設定できます。
以下のコードを見てみましょう
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements wait for data in the chan1 or chan2. //chan2 gets data first since the delay is only 2 sec in data2(). //So the second case will execute and exits the select block select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } }
上記のプログラムを実行すると、次の出力が得られます。
from data2()
ここで、select ステートメントは、いずれかのチャネルでデータが使用可能になるまで待機します。 data2() は、2 秒間のスリープ後にチャネルにデータを追加し、これにより XNUMX 番目のケースが実行されます。
同じプログラム内の選択にデフォルトのケースを追加し、出力を確認します。 ここで、選択ブロックに到達すると、チャネル上にデータの準備ができているケースがない場合、どのチャネルでもデータが利用可能になるのを待たずにデフォルトのブロックが実行されます。
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements check for data in chan1 or chan2. //But data is not available (both routines have a delay of 2 and 4 sec) //So the default block will be executed without waiting for data in channels. select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) default: fmt.Println("Default case executed") } }
このプログラムでは次のような出力が得られます。
Default case executed
これは、選択ブロックに到達したときに、読み出すデータがチャネルに存在しなかったためです。 したがって、デフォルトのケースが実行されます。
ミューテックス
Mutex は相互排他性の短縮形です。Mutex は、複数のサブルーチンが同時にリソースにアクセスできないようにする場合に使用します。Mutex には、Lock と Unlock の 2 つのメソッドがあります。Mutex は sync パッケージに含まれています。そのため、sync パッケージをインポートする必要があります。相互に排他的に実行する必要があるステートメントは、mutex.Lock() と mutex.Unlock() 内に配置できます。
ループの実行回数をカウントする例でミューテックスについて学びましょう。 このプログラムでは、ルーチンがループを 10 回実行することを想定しており、カウントは合計に保存されます。 このルーチンを 3 回呼び出すと、合計カウントは 30 になります。カウントはグローバル変数 count に保存されます。
まず、ミューテックスなしでプログラムを実行します
package main import "fmt" import "time" import "strconv" import "math/rand" //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
結果を見る
Count after i=1 Count: 11 Count after i=3 Count: 12 Count after i=2 Count: 13 Final Count: 13
実行すると結果が異なる可能性がありますが、最終結果は 30 にはなりません。
ここで何が起こっているかというと、3 つのゴルーチンが変数 count に格納されているループ数を増やそうとしていることです。 現時点でカウントが 5 で、Goroutine1 がカウントを 6 に増分しようとしているとします。主な手順は次のとおりです。
カウントを一時にコピーする
温度を上げる
温度をカウントに戻して保存する
goroutine3 によってステップ 1 を実行した直後だとします。 別のゴルーチンには、3 が上記の手順を実行して 4 を格納し直すという古い値が含まれている可能性がありますが、これは間違っています。 これは、あるルーチンがすでに変数を使用しているときに他のルーチンを待機させるミューテックスを使用することで防ぐことができます。
次に、mutex を使用してプログラムを実行します。 ここでは、上記の 3 つのステップがミューテックス内で実行されます。
package main import "fmt" import "time" import "sync" import "strconv" import "math/rand" //declare a mutex instance var mu sync.Mutex //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) //lock starts here mu.Lock() temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp //lock ends here mu.Unlock() } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
これで、出力は次のようになります
Count after i=3 Count: 21 Count after i=2 Count: 28 Count after i=1 Count: 30 Final Count: 30
ここで、最終出力として期待される結果が得られます。 これは、カウントの読み取り、インクリメント、および書き戻しのステートメントがミューテックスで実行されるためです。
エラー処理
エラーとは、開かれていないファイルを閉じる、存在しないファイルを開くなどの異常な状態です。通常、関数は最後の戻り値としてエラーを返します。
以下の例でエラーについて詳しく説明します。
package main import "fmt" import "os" //function accepts a filename and tries to open it. func fileopen(name string) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { fmt.Println(er) return }else{ fmt.Println("file opened", f.Name()) } } func main() { fileopen("invalid.txt") }
出力は次のようになります。
open /invalid.txt: no such file or directory
ここでは、存在しないファイルを開こうとしましたが、er 変数にエラーが返されました。 ファイルが有効な場合、エラーは null になります。
カスタムエラー
この機能を使用すると、カスタム エラーを作成できます。 これは、エラーパッケージの New() を使用して行われます。 カスタム エラーを利用できるように上記のプログラムを書き直します。
以下のプログラムを実行します
package main import "fmt" import "os" import "errors" //function accepts a filename and tries to open it. func fileopen(name string) (string, error) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { //created a new error object and returns it return "", errors.New("Custom error message: File name is wrong") }else{ return f.Name(),nil } } func main() { //receives custom error or nil after trying to open the file filename, error := fileopen("invalid.txt") if error != nil { fmt.Println(error) }else{ fmt.Println("file opened", filename) } }
出力は次のようになります。
Custom error message:File name is wrong
ここで、 area() は正方形の面積を返します。 入力が 1 未満の場合、area() はエラー メッセージを返します。
ファイルの読み取り
ファイルはデータを保存するために使用されます。 Go を使用すると、ファイルからデータを読み取ることができます
まず、現在のディレクトリに以下の内容のファイル data.txt を作成します。
Line one Line two Line three
次に、以下のプログラムを実行して、ファイル全体の内容が出力として表示されることを確認します。
package main import "fmt" import "io/ioutil" func main() { data, err := ioutil.ReadFile("data.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
ここで、データ err := ioutil.ReadFile(“data.txt”) はデータを読み取り、バイト シーケンスを返します。 印刷中に文字列形式に変換されます。
ファイルを書く
これはプログラムで見ることができます
package main import "fmt" import "os" func main() { f, err := os.Create("file1.txt") if err != nil { fmt.Println(err) return } l, err := f.WriteString("Write Line one") if err != nil { fmt.Println(err) f.Close() return } fmt.Println(l, "bytes written") err = f.Close() if err != nil { fmt.Println(err) return } }
ここでファイル test.txt が作成されます。 ファイルがすでに存在する場合、ファイルの内容は切り詰められます。 Writeline() は、内容をファイルに書き込むために使用されます。 その後、Close() を使用してファイルを閉じました。
チートシート
この Go チュートリアルでは、以下について説明しました。
ご用件 | 説明 | 構文 |
---|---|---|
基本タイプ | 数値、文字列、ブール値 | |
変数 | 値を宣言して変数に代入する | var 変数名タイプ var 変数名 タイプ = 値 var 変数名 1、変数名 2 = 値 1、値 2 変数名 := 値 |
定数 | 一度代入すると値を変更できない変数 | const変数 = 値 |
ループ | ループ内でステートメントを実行します。 | 初期化_式の場合; 評価式; 反復_式{ // XNUMX つ以上のステートメント } |
それ以外の場合 | 条件文です | if 条件{ // ステートメント_1 場合} else { // ステートメント_2 } |
スイッチ | 複数のケースを含む条件文 | スイッチ式 { ケース値_1: ステートメント_1 ケース値_2: ステートメント_2 ケース値_n: ステートメント_n デフォルト: ステートメント_デフォルト } |
配列 | 同じ型の要素の固定サイズの名前付きシーケンス | 配列名 := [サイズ] タイプ {value_0,value_1,…,value_size-1} |
スライス | 配列の一部またはセグメント | var スライス名 [] タイプ = 配列名 [開始:終了] |
機能 | 特定のタスクを実行するステートメントのブロック | func 関数名(パラメータ1型、パラメータn型) return_type { //ステートメント } |
パッケージ | コードを整理するために使用されます。 コードの可読性と再利用性が向上します | パッケージ名をインポートする |
延期する | 含まれている関数の実行が終了するまで、関数の実行を延期します。 | defer 関数名(パラメータリスト) |
ポインタ | 別の変数のメモリアドレスを格納します。 | var 変数名 *タイプ |
Structure | 同じ型または異なる型の要素をさらに XNUMX つ含むユーザー定義のデータ型 | 型構造体名 struct { 変数_1 変数_1_タイプ 変数_2 変数_2_タイプ 変数_n 変数_n_type } |
メソッド | メソッドはレシーバ引数を持つ関数です | func (変数 variabletype) メソッド名(parameter_list) { } |
ゴルーチン | 他の関数と同時に実行できる関数。 | go 関数名(パラメータリスト) |
チャネル | 関数が相互に通信するための方法。 あるルーチンがデータを配置し、別のルーチンによってアクセスされる媒体。 | 宣言: ch := make(chan int) データをチャネルに送信します: チャネル変数 <- 変数名 チャンネルから受信: 変数名 := <- チャネル変数 |
選択する | チャネル上で動作するスイッチ文。ケース文はチャネル操作になります。いずれかのチャネルでデータの準備ができたら、そのケースに関連付けられた文が実行されます。 | 選択する { ケース x := <-chan1: fmt.Println(x) ケース y := <-chan2: fmt.Println(y) } |
ミューテックス | ミューテックスは、複数のサブルーチンによるリソースへの同時にアクセスを許可したくない場合に使用されます。 Mutex には、Lock と Unlock の 2 つのメソッドがあります | ミューテックス.Lock() //ステートメント mutex.Unlock()。 |
ファイルの読み取り | データを読み取り、バイト シーケンスを返します。 | データ、エラー:= ioutil.ReadFile(ファイル名) |
ファイルの書き込み | データをファイルに書き込みます | l、エラー:= f.WriteString(text_to_write) |