TaNA LABO
エンジニアリング

May 23, 2020

Go
初心者

Goプログラミング言語のチュートリアル 〜 基礎的な文法・Part2

post 41

前回チュートリアル の続き。


本書では以下のように一通りの言語仕様が解説されている。

■ Chapter1 - Go言語による開発の概要
■ Chapter2 - Go言語の基本
■ Chapter3 - 関数とメソッド
■ Chapter4 - 構造体
■ Chapter5 - インタフェース ★
■ Chapter6 - 配列・スライス・マップ
■ Chapter7 - エラーハンドリング ★
■ Chapter8 - 並列処理 ★
■ Chapter9 - 逆引きリファレンス

本記事では★印の3分野に絞って確認。

インタフェース

Goではクラスの概念が存在しないが、インタフェースは用意されている。

Javaのようにimplementsで明示的に宣言する必要もなく、インターフェースに定義されている関数を、メソッドとして定義している型(構造体)は、自動的にインターフェースが実装される。

package main

import "fmt"

type Calculator interface {
	Calculate(a int, b int) int
}

// 構造体(足し算・引き算)
type Add struct {
}
type Sub struct {
}

// Add型にCalculate関数を実装
func (x Add) Calculate(a int, b int) int {
	return a + b
}
// Sub型にCalculate関数を実装
func (x Sub) Calculate(a int, b int) int {
	return b - a
}

func main() {
	var add Add
	var sub Sub
	var cal Calculator
	
	cal = add
	fmt.Println("和:", cal.Calculate(1, 2))
	cal = sub
	fmt.Println("差:", cal.Calculate(1, 2))
}

以下はサンプルプログラムの実装例。

package main

import (
	"fmt"
)

type Eater interface {
	PutIn()   // 口に入れる
	Chew()    // 噛む
	Swallow() // 飲み込む
}

// 人間の構造体
type Human struct {
	Height int // 身長
}

// カメの構造体
type Turtle struct {
	Kind string // 種類
}

// 人間用のインターフェースの実装
// Golangではレシーバを通して構造体に関数が実装されている
func (h Human) PutIn() {
	fmt.Println("道具を使って丁寧に口に運ぶ")
}
func (h Human) Chew() {
	fmt.Println("歯でしっかり噛む")
}
func (h Human) Swallow() {
	fmt.Println("よく噛んだら飲み込む")
}

// カメ用のインターフェースの実装
func (t Turtle) PutIn() {
	fmt.Println("獲物を見つけたら首をすばやく伸ばして噛む")
}
func (t Turtle) Chew() {
	fmt.Println("クチバシで噛み砕く")
}
func (t Turtle) Swallow() {
	fmt.Println("小さく噛み砕いたら飲み込む")
}

// インターフェースが引数になる食べるメソッド
func EatAll(e Eater) {
	e.PutIn()
	e.Chew()
	e.Swallow()
}

func main() {
	var man Human = Human{Height: 300}
	var cheloniaMydas Turtle = Turtle{Kind: "アオウミガメ"}
	var eat Eater

	// Human型がインターフェースであるEater型に変換される
	fmt.Println("<人間が食べる>")
	eat = man
	EatAll(eat)

	// Turtle型がインターフェースであるEater型に変換される
	fmt.Println("<カメが食べる>")
	eat = cheloniaMydas
	EatAll(eat)
}

エラーハンドリング

Goでは戻り値に多値を返す仕様を利用し、標準関数の中ではerrorを返すものがある。

package main

import "fmt"
import "os"

func main() {
	file, err := os.Open("test.txt")
	if err != nil {
		// エラーの詳細情報を出力
		// open test.txt: no such file or directory
		fmt.Println(err.Error())
		// 終了
		os.Exit(1)
	}
	
	// ファイルのクローズ
	file.Close()
	fmt.Println("OK")
}

自作関数のエラー処理も実装可能。

package main

import "fmt"

type MyError struct {
	message string // エラー詳細
}

// Errorメソッドの実装
func (err MyError) Error() string {
	// エラーメッセージを返す
	return err.message
}

func main() {
	val, err := hex2int("1")
	fmt.Println(val, err)
}

func hex2int(hex string) (val int, err error) {
	// エラーの場合
	return 0, MyError{"不正な文字です。:" + string(r)}
	
	// 戻り値errには初期値であるnilが返る
	return
}

パニックとリカバリの使い方は別途学習が必要。

ゴルーチンとは!?

Goでは並列処理に ゴルーチン という仕組みが用意されており、goキーワードで関数を呼び出す。

Go言語では ランタイムが暗黙的にゴルーチンを1つ作成し、ゴルーチン上でmain関数を実行、mainのゴルーチンが終了すると、他のゴルーチンが動作中であってもプログラムは終了。

package main

import "fmt"
import "time"

/**
 * main:開始
 * testを通常の関数として起動
 * 01234testをゴルーチンとして起動
 * 0123main:終了
 */
func main() {
	fmt.Println("main:開始")
	fmt.Println("testを通常の関数として起動")
	test()
	
	fmt.Println("testをゴルーチンとして起動")
	go test()
	
	// 3秒待つ
	time.Sleep(3 * time.Second)
	fmt.Println("main:終了")
}

// ゴルーチンとして起動する関数
func test() {
	for i := 0; i < 5; i++ {
		// 連番出力
		fmt.Print(i)
		// 1秒待つ
		time.Sleep(1 * time.Second)
	}
}

チャネルやselect文は並列処理を書く状況になり次第、別途学習が必要。


©Copyright2020 TaNA LABO. All Rights Reserved.