概要
Go
言語の初学者である私が、Go
言語を使用した開発を行う為、自分なりに学んだ事や分かった事をまとめます。今回の記事は、私のメモにみたいな物のになりますので、有益な情報は存在しないかと思われます。予めご了承頂ければと思います。
必須構文
-
Go
言語の場合、エントリーポイントは、main関数となる -
Go
言語は、何らかのパッケージに属している必要がある (どのgo
ファイルもpackage 〇〇と書く必要がある) -
main
パッケージの中に、main
関数が存在する場合、それを実行する決まりである為、必ず記述する必要がある
// エントリーポイントとは、プログラムの実行を開始する為の特定の場所または関数の事を示す
package main
文字の表示
-
fmt
は、Goのパッケージの標準パッケージ
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
出力結果
hello world
変数定義
- 変数は、必ず型の定義をする必要がある
- 基本の書き方は、
var 変数名 型定義
となる - 変数名の1文字目が小文字の場合、そのファイル内のみで使用可能
- 変数名の1文字目が大文字の場合、別のファイルでも適用可能(グルーバル変数的な動きをする)
単一の変数定義
// stringを定義
var msg string
// その他の変数の書き方
msg = "hello world"
var msg = "hello world"
// よく使われる書き方
msg := "hello world"
複数の変数定義
// 複数の変数定義
var a int , b int
// 複数の変数を定義した際、型が同じ場合は、集約可能
var a , b int
// その他の書き方
a, b = 10, 15
a, b := 10, 15
型が異なる変数の定義
// 型が異なる変数の定義
var (
c int
d string
)
c = 20
d = "hoge"
Go
言語で使用するデータ型
頻繁に使う型の定義
// 文字
string "hello"
// 数値
int 53
// 浮動小数点数
float64 10.2
// 真偽値
bool false true
// null
nil
変数の値が空の場合の挙動
- string型の変数が空:""
- int型の変数が空:0
- boolの変数が空:false
var s string // 出力結果:""
var a int // 出力結果:0
var f bool // 出力結果:false
フォーマット文字列を使用した出力
-
fmt.Printf
は、指定されたフォーマットに従ってテキストをフォーマットし、標準出力に書き込む関数
package main
import "fmt"
func main() {
a := 10
b := 12.3
c := "hoge"
var d bool
fmt.Printf(
"a: %d, b:%f, c:%s, d:%t\n",
a, b, c, d
)
}
頻繁に使うフォーマット文字列一覧
フォーマット文字列 | 意味 |
---|---|
%v | 値を表示する |
%d, %b, %o, %x | 整数を10進数、2進数、8進数、16進数 |
%x, %X | 16進数(小文字)、16進数(大文字) |
%f | 浮動小数点数を小数点以下6桁まで表示する |
%e | 浮動小数点数を指数表記で表示する |
%s | 文字列を表示する |
%t | 真偽値を表示する |
%c | Unicodeコードポイントに対応する文字を表示する |
%p | ポインタの値を表示する |
%q | エスケープされた文字列を表示する |
定数
-
const
を使って定義する
const name = "taguchi"
name = "fkoji"
-
iota
を使う事で、0から始まる連番を自動で生成する事が可能
const (
apple = iota
banana
cherry
)
// 出力結果
// 0
// 1
// 2
基本的な演算
- 数値で使える記号:
+
、-
、*
、/
、%
- 文字列で使える記号:
+
- 論理値で使える記号:
AND(&&)
、OR(||)
、NOT(!)
// 数値計算
var x int
x = 10 % 3
// 省略した書き方
x += 3 // x = x + 3
x++ // x = x++ ++x
// 文字列の結合
var s string
s = "hello " + "world"
// 論理値の使い方
a := true
b := false
// a と bがture
fmt.Println(a && b)
// a または bがture
fmt.Println(a || b)
// aがtureではない
fmt.Println(!a)
ポインタ
- 変数が格納されたメモリのアドレスを示す特殊な型の変数
- ポインタは、変数を関数に渡す場合等に使用される
- 変数のアドレスを取得するために
&
を記述する
package main
import "fmt"
func main() {
a := 10
fmt.Println("aのアドレス:", &a) // aのアドレスを出力
// 出力結果 ⇨ aのアドレス: 0xc0000a2000
var p *int // int型のポインタpを宣言
p = &a // 変数aのアドレスをポインタpに代入
fmt.Println("ポインタpが示すアドレスの値:", *p) // ポインタpが示すアドレスの値を出力
// 出力結果 ⇨ ポインタpが示すアドレスの値: 10
*p = 20 // ポインタpが示すアドレスの値を更新
fmt.Println("変数aの値:", a) // aの値を出力
// 出力結果 ⇨ 変数aの値: 20
}
関数
関数の書き方
- 関数は
func
で定義する - 引数と型を必ず定義する必要がある
- 引数と戻り値の型は、複数定義する事も可能
// 構文
func 関数名(引数1 型1, 引数2 型2, ...) 戻り値の型 {
// 関数の処理
return 戻り値
}
// 例
// x = 3、 y = 5
func add(x int, y int) int {
// 合計値が`result`に格納される
return x + y
}
func main() {
result := add(3, 5)
fmt.Println(result) // 出力結果: 8
}
名前付き引数を使用した関数定義
- 名前付き引数を使う事で、どの引数に対して何の値を渡しているか一目で分かる
- 戻り値に変数を定義する事で、返却する変数と型が一目で分かる。また、変数定義の効果もある
// 構文
func 関数名(引数1 型1, 引数2 型2, ...) (戻り値の型1, 戻り値の型2, ...) {
// 関数の処理
return 戻り値1, 戻り値2, ...
}
// 例
func multiply(x int, y int) (result int) {
result = x * y
return result
}
func main() {
result := multiply(x: 5, y: 3)
fmt.Println(result) // 出力結果: 15
}
関数リテラル(無名関数)
- 関数を宣言して直ちに実行する構文
- 関数で定義した変数を代入することができる
- 簡潔で読みやすいコードを記述する際に使用される事もある
// 例
func main() {
add := func(x, y int) int {
return x + y
}
result := add(2, 3)
fmt.Println(result) // Output: 5
}
即時関数
- 関数を定義した直後に即座に呼び出す構文
-
Go
言語では、即時関数と関数リテラルを組み合わせて使用する事が可能 - 即時関数は、関数を実行し、その戻り値を使用しない場合に使用する事が多い
// 例
func main() {
// 即時関数
func(x, y int) int {
return x + y
}(2, 3)
}
array(配列)
配列の宣言
- 配列とは、同じ型の要素が固定長で並んだデータ構造
// 構文
var <配列名> [<要素数>]<型>
// 例
var a [5]int // 要素数が5のint型の配列を宣言
配列要素へのアクセス
- 配列の値を取得するには、インデックスを指定して行う
-
<配列名>[<インデックス>]
の書き方で配列の要素を取得できる
a := [3]int{1, 2, 3} // 配列を初期化
fmt.Println(a[0]) // 1を出力
fmt.Println(a[1]) // 2を出力
fmt.Println(a[2]) // 3を出力
配列で使用する場合のlen()
- 配列の長さ(要素数)を取得する
- 使用する頻度はかなり高い
a := [5]int{1, 2, 3, 4, 5} // 配列を初期化
fmt.Println(len(a)) // 5を出力
slice(スライス)
スライスの作り方
- スライスは、要素が可変長の為、
append
等で後から要素を追加する事が可能 - 要素数を動的に変更することができる
-
var <スライス名> []<型>
の書き方で配列の定義が可能
// 構文
var <スライス名> []<型>
// 配列からスライスを取り出す
var a = [5]int{1, 2, 3, 4, 5}
var s = a[1:4] // [2, 3, 4]のスライス
// スライスリテラルを使う
var s = []int{1, 2, 3, 4, 5} // [1, 2, 3, 4, 5]のスライス
スライスの要素へのアクセス
- 最初に指定した
index
は、要素として含まれず、最後のindex
より1つ前の要素が含まれる -
index
がスライスの範囲外である場合は、エラーが発生する
var s = []int{1, 2, 3, 4, 5}
fmt.Println(s[0]) // 1
fmt.Println(s[2]) // 3
fmt.Println(s[5]) // 実行時エラー: index out of range
スライスで使用する場合のlen()
-
len()
関数を使用する事で、スライスの長さが取得可能
var s = []int{1, 2, 3, 4, 5}
fmt.Println(len(s)) // 5
スライスで使用する場合のcap()
-
cap()
関数を使用する事で、キャパシティ(容量)を取得できる - 先頭要素から配列の末尾までの要素数が取得される
var a = [5]int{1, 2, 3, 4, 5}
var s = a[1:4]
fmt.Println(cap(s)) // 出力結果:4
// (元の配列の要素数 - スライスの先頭インデックス)
スライスで使用する場合のmake()
- スライスを動的に生成する為の組み込み関数
-
var s = make([]<型>, <長さ>, <容量>)
の書き方で配列の定義が可能
// 構文例
var s = make([]<型>, <長さ>, <容量>)
// スライスの作成例
// 長さが5のint型のスライスを生成
s := make([]int, 5)
スライスで使用する場合のappend()
- スライスに要素を追加するための組み込み関数
- 第1引数に追加対象のスライスを指定する。第2引数以降に追加する要素を指定する
// 構文
append(slice []<型>, elems ...<型>) []<型>
// slice: 追加する要素(元となるスライスの値)
// elems: 追加する要素
// スライスに要素を追加
s := []int{1, 2, 3}
s = append(s, 4, 5) // 4と5を追加
スライスで使用する場合のcopy()
- スライスの要素をコピーするための組み込み関数
- 第1引数にコピー先のスライスを指定する。第2引数にコピー元のスライスを指定する
- 値は、コピーされた要素数を返す
// 構文
copy(dest []<型>, src []<型>) int
// dest: 複製した要素をコピーする先のスライス
// src: コピー元のスライス
// スライスの要素をコピー
s1 := []int{1, 2, 3}
s2 := make([]int, 2)
n := copy(s2, s1) // s1からs2に2つの要素をコピー
fmt.Println(s2) // 出力結果:[1 2]
fmt.Println(n) // 出力結果:2
map(マップ)
- キーと値のペアを持つデータ構造
マップの宣言方法
// 構文
// キーと値の型を指定してマップを宣言
var m map[<キーの型>]<値の型>
// makeを使用してマップを初期化
m = make(map[<キーの型>]<値の型>)
// 以下の書き方で、宣言と初期化を同時に行う
m := map[<キーの型>]<値の型>{
<キー1>: <値1>,
<キー2>: <値2>,
// ...
}
// 例
// マップを使用した連想配列の定義
m := map[string]int{
"apple": 100,
"banana": 200,
"orange": 300,
}
// マップを使用した多重連想配列の定義
m := map[string]map[string]int{
"group1": {
"apple": 100,
"banana": 200,
},
"group2": {
"orange": 300,
"grape": 400,
},
}
要素へのアクセス
- マップの要素には、キーを指定してアクセスする
// 連想配列
// 要素に値を代入
m["apple"] = 100
m["banana"] = 200
m["cherry"] = 300
// 要素の値を取得
fmt.Println(m["apple"]) // 出力結果:100
fmt.Println(m["banana"]) // 出力結果:200
fmt.Println(m["cherry"]) // 出力結果:300
// 存在しないキーを指定した場合、ゼロ値が返される
fmt.Println(m["durian"]) // 出力結果:0
// 多重連想配列
value := m["group1"]["apple"]
fmt.Println(value) // 出力結果:100
m["group2"]["grape"] = 500
value := m["group2"]["grape"]
fmt.Println(value) // 出力結果:500
キーの存在有無を判定
// valueに値がキーで指定した値が格納される(変数名は任意。推奨はvalue)
// okに真偽値が格納される(変数名は任意。推奨はok)
// value, ok := マップ変数[キー]
value, ok := m["apple"]
if ok {
// キーが存在する場合の処理
} else {
// キーが存在しない場合の処理
}
マップで使用する場合のlen()
m := map[string]int{
"apple": 100,
"banana": 200,
"orange": 300,
}
// マップの要素数を取得
fmt.Println(len(m)) // 出力結果: 3
マップで使用する場合のdelete()
// mapの作成
m := map[string]int{
"apple": 100,
"banana": 200,
"orange": 300,
}
// マップから要素を削除
delete(m, "banana")
fmt.Println(m) // 出力結果: map[apple:100 orange:300]
if文(条件分岐)
- 使用頻度が最も高い条件分技を行う方法
// 構文1
if 条件式1 {
// 条件式1がtrueの場合に実行される処理
} else if 条件式2 {
// 条件式1がfalseかつ条件式2がtrueの場合に実行される処理
} else {
// 条件式1も条件式2もfalseの場合に実行される処理
}
// 構文1の例
x := 10
y := 20
if x == y {
fmt.Println("xとyは等しい")
} else if x > y {
fmt.Println("xはyより大きい")
} else {
fmt.Println("xはyより小さい")
}
// 構文2
if 変数の初期化式; 条件式 {
// 条件式1がtrueの場合に実行される処理
}
// 構文2の例
i := 0
if i < 10 {
fmt.Println("iは10未満です")
}
// 構文3
if 変数名 := 初期値; 条件式 {
// 条件式がtrueの場合に実行される処理
}
// 構文3の例
if i := 5; i > 0 {
fmt.Println("iは0より大きいです")
}
switch文(条件分岐)
-
switch
で記載した式とcase
の値を比較して、条件に一致した処理がある場合、その処理を実行する - 一致する
case
がない場合、default
に記載した処理が実行される
// 構文
switch 式 {
case 値1:
// 値1と一致した場合の処理
case 値2:
// 値2と一致した場合の処理
...
case 値n:
// 値nと一致した場合の処理
default:
// どのcaseにも一致しなかった場合の処理
}
// 例
fruit := "apple"
switch fruit {
case "apple":
fmt.Println("この果物はリンゴです")
case "banana":
fmt.Println("この果物はバナナです")
default:
fmt.Println("この果物はその他です")
}
for文
for文を使用した繰り返し(ループ)処理の書き方
- ループ処理を行う際に使用する
// 構文
for 初期化式(1.値を初期化) ; 条件式(2.条件) ; 増減式(3.値を更新) {
// 繰り返される箇所
}
// 例
for i := 0; i < 5; i++ {
fmt.Println(i) // 0 1 2 3 4が出力される
}
配列の値を取得
color1 := []string{"赤", "黄", "青"}
// len()を使用して、繰り返し処理の回数を制御する
for i := 0; i < len(color1); i++ {
fmt.Println(color1[i]) // 赤 黄 青が出力される
}
while文の無限ループにあたる書き方
-
Go言語
はwhile文
が存在しない - for文の条件(条件式(2.条件))を空白にすると無限ループが発生
for i := 0; ; i++ {
if i == 3 {
break
}
fmt.Println(i) // 0,1,2が出力される
}
ループ処理の途中抜け(break
)
-
break
は、ループの処理を途中で中断して、ループから抜ける
// 1から10までの整数を順に表示するが、iが5になったらループを抜ける
for i := 1; i <= 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
ループ処理の途中抜け(continue
)
-
continue
は、ループの先頭に戻って処理を継続する
// 1から10までの整数を順に表示するが、iが5の場合は処理をスキップする
for i := 1; i <= 10; i++ {
if i == 5 {
continue
}
fmt.Println(i)
}
range文
rangeの書き方
- スライスやマップ、配列等のデータ構造に対して、反復処理を行う為に使用する (類似処理として、
foreach
、each
の挙動に近い)
// コレクション:スライス、配列、マップ、文字列、チャネルなどのデータ構造を示す
for index(キー), value(要素値) := range コレクション {
// 要素に対する処理
}
// スライスを使用した例
fruits := []string{"apple", "banana", "orange", "grape"}
for index, fruit := range fruits {
fmt.Printf("%d番目のフルーツは%sです。\n", index+1, fruit)
}
ブランク修飾子したrangeの書き方
- ブランク修飾子を使用すると、index(キー)またはvalue(要素値)の値を無視する事が可能となる
- ブランク修飾子は、アンダースコア(
_
)の事を意味する
// コレクション:スライス、配列、マップ、文字列、チャネルなどのデータ構造を示す
for _, 要素 := range コレクション {
// 要素に対する処理
}
strSlice := []string{"apple", "banana", "cherry", "date"}
// インデックスの値は無視される
for _, s := range strSlice {
fmt.Println(s)
}
Go
言語の構造体
- 複数のデータ型を1つにまとめ、新しいデータ型を定義する為に使用する
構造体の定義
// 構文
type 構造体名 struct {
フィールド名1 型1
フィールド名2 型2
// ...
}
// 例
type Person struct {
Name string
Age int
}
var
を使用した構造体の宣言
- 変数
user
が宣言され、構造体User
のフィールドにアクセスする - 構造体にアクセスする際は、
.
を用いる
// 構造体の定義
type User struct {
Name string
Age int
}
// 構造体の宣言
// 小文字のuserは変数名を定義
// 大文字のUserは構造体を定義
var user User
// 構造体に対して、値を宣言する方法
user.Name = "John"
user.Age = 25
new()
を使用した構造体の宣言
-
new()
は、構造体のポインタを作成する為の関数 - 返り値は、ポインタ型になる
- 構造体の値を参照する為には、ポインタの値を参照する必要がある
// 構造体の定義
type User struct {
Name string
Age int
}
// 構造体の宣言
// ポインタ:変数が格納されたメモリのアドレスを示す特殊な型の変数
// User型のポインタを作成
u := new(User)
u.Name = "Alice"
u.Age = 25
fmt.Println(u) // &{Alice 25}
メソッドの定義と使い方
- メソッドとは、特定の型に対して定義された関数を示す
-
Go
言語では、構造体にメソッドを定義する事が可能
メソッドの宣言
// 構文
type 構造体名 struct {
フィールド名1 型1
フィールド名2 型2
// ...
}
// レシーバの処理の動き
// 変数名が構造体と紐付き、変数名を使用する事ででフィールド内にアクセスできる
// フィールド:関数内の事を示す
func レシーバ(変数名 構造体) メソッド名(引数) 戻り値の型 {
// メソッドの処理。別名はフィールド
}
// 例
type MyStruct struct {
field1 int
field2 string
}
// (m MyStruct)がレシーバとなる
func (m MyStruct) MyMethod() {
// メソッドの処理
}
利用方法(呼び出し方法)
- 構造体のインスタンスに対して、
.
で呼び出す事が可能
// 構文
インスタンス.メソッド名()
// 例(処理の流れを理解しやすくする為、構造体とメソッドを記載)
// 構造体
type MyStruct struct {
field1 int
field2 string
}
//メソッド
func (m MyStruct) MyMethod() {
// メソッドの処理
}
// 構造体に値を付与
myStruct := MyStruct{field1: 1, field2: "test"}
// MyMethodの処理を呼び出す
myStruct.MyMethod()
値レシーバー
- 値レシーバーの場合は、元の変数に影響を与えない
// 値レシーバーの書き方
// 構造体の先頭にアスタリスク()*を付与しない
func (変数名 構造体) メソッド名() {
// 処理
}
// 例(処理の流れを理解しやすくする為、構造体とメソッドを記載)
// 構造体
type MyStruct struct {
field1 int
field2 string
}
// メソッド
func (m MyStruct) ValueMethod() {
m.field1 = 2
fmt.Println("ValueMethod:", m.field1) // 出力結果:2
}
// MyStruct型の変数を宣言する
myStruct := MyStruct{field1: 1, field2: "test"}
// メソッドの呼び出し
myStruct.ValueMethod()
// 出力結果
fmt.Println("main:", myStruct.field1) // 出力結果:1
値レシーバーを行った際のfmt.Println()
の出力結果
ValueMethod: 2
main: 1
ポインタレシーバー
- ポインタレシーバーの場合は、元の変数に影響を与える
// ポインタレシーバーの書き方
// 構造体の先頭にアスタリスク(*)を付与する
func (変数名 *構造体) メソッド名() {
// 処理
}
// 例(処理の流れを理解しやすくする為、構造体とメソッドを記載)
// 構造体
type MyStruct struct {
field1 int
field2 string
}
// メソッド
func (m *MyStruct) PointerMethod() {
m.field1 = 2
fmt.Println("PointerMethod:", m.field1) // 出力結果:2
}
// MyStruct型の変数を宣言する
myStruct := MyStruct{field1: 1, field2: "test"}
// メソッドの呼び出し
myStruct.PointerMethod()
// 出力結果
fmt.Println("main:", myStruct.field1) // 2
ポインタレシーバーを行った際のfmt.Println()
の出力結果
ValueMethod: 2
main: 2
インターフェイスの定義と使い方
- メソッドの一覧を定義した型
- インターフェースを実装する際、型に関しては、自由に決定する事が可能
インターフェイスの構文
// 構文
// インターフェイス内にメソッドを定義する
type インターフェース名 interface {
メソッド名1(引数1 型1, 引数2 型2, ...) 戻り値の型1
メソッド名2(引数1 型1, 引数2 型2, ...) 戻り値の型2
// ...
}
インターフェイスの使用方法
// 例
// インターフェイスを定義
type MyInterface interface {
Method1() int
Method2(string) string
}
// 構造体を定義
type MyStruct struct{}
// インターフェイスで定義したMethod1を実装している
func (s MyStruct) Method1() int {
return 1
}
// インターフェイスで定義したMethod2を実装している
func (s MyStruct) Method2(someString string) string {
return someString
}
// var 変数名 型
// 左側:myInterfaceと言う名前の変数を定義
// 右側:MyInterface型を定義
var myInterface MyInterface
// 構造体MyStructをmyInterfaceの変数に代入する
myInterface = MyStruct{}
fmt.Println(myInterface.Method1()) // 出力結果: 1
fmt.Println(myInterface.Method2("test")) // 出力結果: test
空のインターフェース
空のインターフェースの定義と使い方
- 空のインターフェース型は、どの型の値でも受け入れる事が可能
// var 任意の名前 interface{}
var any interface{}
any = 1
fmt.Println(any) // 出力結果:1
any = "hello"
fmt.Println(any) // 出力結果:hello
any = true
fmt.Println(any) // 出力結果:true
型アサーション
- 型アサーション(type assertion)とは、インターフェース型の変数から具体的な型の値を取り出す為の方法
var x interface{} = 123
value, ok := x.(int)
if ok {
fmt.Println("x is an int with value", value)
} else {
fmt.Println("x is not an int")
}
goroutine
- 並列処理(別々の関数を同時に処理する)を実現する為の機能
goroutineの書き方
// 構文
// 無名関数でgoroutineを使う場合の書き方
// 単純な非同期処理を実行する場合に適している
go func() {
// ここに並行して実行したい処理を書く
}()
// 事前に定義された関数名を使って、goroutineを開始する書き方
// 複雑な処理を実行する場合や、再利用可能な関数を定義する場合に適している
func 関数名() {
// ここに並行して実行したい処理を書く
}
// 関数名の前に`go`を付与する
go 関数名()
go func() {}
を使用した例
package main
import (
"fmt"
"time"
)
// `func main()`と`go func()`は、同時に処理が実行されている
func main() {
fmt.Println("Start")
go func() {
// 2秒待ってに出力
time.Sleep(time.Second * 2)
fmt.Println("goroutine finished")
}()
// 3秒待ってに出力
time.Sleep(time.Second * 3)
fmt.Println("Main finished")
}
go 関数名()
を使用した例
package main
import (
"fmt"
"time"
)
// `go task()`の処理が実行されたタイミングで、`func task()`と`func main()`の処理が同時進行で行われる
func main() {
fmt.Println("Start")
go task()
time.Sleep(time.Second * 3)
fmt.Println("Main finished")
}
func task() {
time.Sleep(time.Second * 2)
fmt.Println("goroutine finished")
}
チャネル
-
goroutine
間で通信をする際に使用する -
goroutine
間でデータの受け渡しが可能となる
チャネルの構文と基本的な使い方
// 構文
// チャネルの作成記法
変数名 =: make(chan 型名)
// チャネルの送信、受信の設定
チャネル変数 <- 値 // チャネルに値を送信する
受信する変数 := <- チャネル変数 // チャネルから値を受信する
// 例
// チャネルの作成
ch := make(chan int)
// チャネルに値を送信する
ch <- 123
// チャネルから値を受信する
val := <- ch
チャネル
とgoroutine
を混合して使用する場合の使い方
package main
import (
"fmt"
"math/rand"
"time"
)
func sender(ch chan string, message string) {
for {
// メッセージをチャネルに送信する
ch <- message
// ランダムな待ち時間
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
}
func main() {
// チャネルを作成する
ch := make(chan string)
// 2つのgoroutineを作成し、それぞれ異なるメッセージを送信する
go sender(ch, "Hello")
go sender(ch, "World")
for {
// チャネルからメッセージを受信する
message := <-ch
// メッセージを表示する
fmt.Println(message)
}
}
Webサーバーの作成
- net/httpパッケージを使用する事で、Webサーバーの作成が可能
package main
// webサーバを作成する場合は、"net/http"を使用する
import (
"fmt"
"net/http"
)
func main() {
// ハンドラ関数を登録
// 第一引数には、登録するURLパスを指定する
// 第二引数には、処理関数を指定する
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
// サーバーを起動
// 第一引数にはポート番号を指定する
// 第二引数には、http.Handlerインターフェースを満たすハンドラーを指定する
// ハンドラーは、リクエストを受け取り、レスポンスを返す為の関数を示す(http.HandleFuncで指定した関数を指定している)
err := http.ListenAndServe(":8080", nil)
if err != nil {
// panic():想定外のエラーが発生した場合、処理を継続せずに、即座にプログラムを停止させる関数
panic(err)
}
// ワンラーナでの記述
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
感想
色々サイトを調べたり、chatGPTを駆使して、分からない部分をかなり深ぼって一つ一つ書き方や手法について書きました。基礎が一通り分かったとしても、開発が完全にできる訳ではないので、実務を積んでより技術研鑽をしていきたいです。