ゆるりのこと。

文系営業マンから新米データサイエンティストしています。

A Tour of Goのざっくりまとめ② <Flow control statements: for, if, else, switch and defer編>

こちらの続編です。

mattsun-plapla.hatenablog.com

A Tour of Goはこちら。あそべます。Playaround最高。

go-tour-jp.appspot.com


今回はfor, if文などについてです。

forループ

for 文は下記の3つのパートで分解できる。

https://go-tour-jp.appspot.com/flowcontrol/1


この中で、条件式パートがないと、無限ループが発生してしまう。

package main

import (
	"fmt"
)

func main() {
	sum := 0 // 初期値0のsum変数(int型) 
	// i変数を初期化, i < 10までループ処理をおこなう, 最後にi+1をおこなう
        for i := 1; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum) // 45

       // 初期化と後処理を省略したVer. ";"も省略可
       for sum < 10 {
		sum++
       }
       fmt.Println(sum) // 10
}

Goにはwhile文がないため、代わりに2つ目のような書き方をするようです。

if-else

if文はこんなかんじ。

// 基本構文
if condition {
	// do something	
	}


for文と組み合わせるとこんなかんじ。

func main() {
	sum := 0
	for sum < 10 {
		if sum == 5 {
			break // sumが5のときbreak
		}
		sum++
	}
	fmt.Println(sum) // 5
}

if, else, else if といった複数条件の書き方。

func main() {
	x := "Go"
	if x == "Go" {
		fmt.Println("OK")
	} else if x == "Python" {
		fmt.Println("NG")
	} else {
		fmt.Println("Again")
	}
}

switch

つづいて、switch文。
if-elseを短く書く方法らしい。

// 基本構文
switch expression {
	case condition:
		// do something
	}
func main() {
	os := "mac"
	switch os {
	case "mac":
		fmt.Println("mac")
	case "linux":
		fmt.Println("lunux")
	default:
		fmt.Println("windows")
	}
}
// mac

// 変数osをswitch文以外で使用しない場合、このような書き方もできる. (if文でも可)
func main() {
	switch os := "max"; os {
	case "mac":
		fmt.Println("mac")
	case "linux":
		fmt.Println("lunux")
	default:
		fmt.Println("windows")
	}
}
// windows

caseに各条件に応じた処理を書いていき、defaultでは条件に該当しない場合の処理を書く。
defaultがない場合、各条件に該当しなければ何も出力されずswitch文から出る。
また、caseは上から順番に条件に該当するまで下へいき、該当した条件があった時点で処理が終わり、switch文から出る。(breakする)

条件を書かなければ、while True文のような感じで表現できる。

func main() {
	currentHour := time.Now().Hour()
	switch {
	case current_hour < 12:
		fmt.Println("Morning")
	case current_hour < 18:
		fmt.Println("Noon")
	case current_hour > 18:
		fmt.Println("Evening")
	}

defer

遅延実行という意味らしい。
defer後にかかれた処理は、呼び出し元の関数内で処理が実行された後におこなわれる。

func main() {
	defer fmt.Println("Finally...")

	fmt.Println("First, do something")
}
// "First, do something" の出力後、"Finally..."が出力される.
// この場合、呼び出し元のmain()内処理が最後まで行われてから、deferが処理される.


// example-2
func sample() {
	defer fmt.Println("sample1")

	fmt.Println("sample2")
}
func main() {
	sample()
	defer fmt.Println("main1")
        fmt.Println("main2")
}
/*
実行順番は、
sample2
sample1
main2
main1
*/

stacking defer

deferを重ねて使う場合、はじめのものが最後に実行されるというもの。
LIFO (Last in First out) (後入れ先出し)という格納方式に基づく。

func main() {
	for i := 0; i <= 5; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("start!") // 最初に実行される
}
/*
start!
5
4
3
2
1
0
*/

A Tour of Goのざっくりまとめ① <Packages, variables, and functions編>

A Tour of Goはこちらから行けます。あそべます。日本語もあります。
tour.golang.org


今回は、Packages, variables, and functionsページで勉強していました。
以下、自分なりのまとめ。

package内のexported nameについて

Goのpackageには、外部から参照できるExported Nameと、参照できないもの(Not Exported Name)がある。
それらの違いは、名前の最初が大文字(Public)か小文字(Private)かである。
mathパッケージにあるPiは大文字のため、Exported Nameであり、外部パッケージから参照することができる。
一方で、piとした場合は、小文字のため、外部から参照することができない。

// package mainからmathパッケージ内のPiを使う場合

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(math.pi)
}
// Error: cannot refer to unexported name math.pi

func main() {
	fmt.Println(math.Pi)
}
// 3.141592653589793

関数の書き方

関数の書き方はいろいろあるけれど、変数名のうしろに型名を書く
返り値を指定する場合はさらにその後ろに書く。

package main

import "fmt"

// int型の引数x, yをとり、intのx+yを返すadd関数
func add(x,y int) int {
	return x + y
}

// string型の引数をとるx, yを反転させたy, xを返す (複数の返り値)
func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	fmt.Println(add(42, 13)) // 55

	a, b := swap("hello", "world")
	fmt.Println(a, b) // worldhello

}


なお、なぜ型を宣言するかについては、こちらをみてね、ということで下記ページが紹介されているので、チラ見してみた。

blog.golang.org


ここでは、何故伝統的?であるC的記述法とGoのそれが違うのか、という疑問に答えるべく、Cの記法とともに紹介している。(Cを知らないひとはどうしたらいいの)
そもそもCをしらないので、正直サッパリなんですが、要するに関数で引数や返り値を宣言するときにめっちゃ複雑になりやすいやん?読みにくいやん?ってことっぽい(-A-)

// 例えばこういうの
int (*(*fp)(int (*)(int, int), int))(int, int)

Naked Return

関数内にて、返り値に変数名を指定する場合、返す値を明示しなくとも、returnだけでいいよ、というもの。
ただし関数が長いとReadbilityに影響するので、短い関数とかで使うこと推奨。

package main

import "fmt"

// 返り値にint型のx, yを指定している
func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return // returnだけで先に指定したint型のx, yを返す
}

func main() {
	fmt.Println(split(17)) // 7, 10
}

変数のつくりかた

「var」を使う場合と、「:=」であらわすShort declarationsを使う場合がある。
Short declarationsは関数の外では使用できないので注意。

var x int
var y int = 100 // 初期化子(Initializer). この場合、最初から100という値をyに与えている.
var y = 100 // Initializerの場合、型は省略可

z := 1000 // Short variable declarations
func main(){
	x = 10
	fmt.Println(x) // 10
	fmt.Println(y) // 100
}

////////////////////////////////////////////////////////////////

test := "test" //関数外では使えないのでError: non-declaration statement outside function body

func test() string {
	x := "test" //関数内では使える
	return x
}

func main() {
	fmt.Println(test()) // test

Zero Values

変数ついでに、変数に初期値を与えない場合、Zero Valuesといわれる初期値?が勝手に与えられる。
型によって違うが、int型は0, bool型はfalse, string型は""(blank)など。

型変換・型推論

Goの型変換は非常に明快で、基本的には変換したいタイプ内に変換元の値を入れるだけ。
Pythonみたいに楽でいいですね。
ただし、Pythonでできるような、異なる型での演算等はコンパイルエラーとなる。
バグの原因となりやすいようです。int32 と intでもダメ。(仮にintがint32-bit型だとしても...)
つまり、明示した型や型推論されたもの同士でないといけない、っとことかな。

package main

import "fmt"

func main() {
	x := 12
	y := float64(x) // int -> float64に変換
	fmt.Printf("%T %v\n", x, x) // int 12
	fmt.Printf("%T %v", y, y) // float64 12
}

なお、ここでx := 12と型宣言なしで変数をつくった場合、型推論をしてくれるので勝手にint型としてくれている。
これは初期値に応じて変えてくれる。

定数 Constant

定数は const をつかってあらわす。
Short declaration「:=」を使って宣言はできない。
また、constが使えるのは文字・文字列・boolean・数値だけ。

const同士からconstをつくることも可能。

package main

import "fmt"

const Pi = 3.14
const Pi := 3.14 // error: unexpected :=, expecting =

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	fmt.Println("Go rules?", Truth)
}

また、goではconstを使う場合、型を宣言する必要はないそうです。なぜなら、毎回変換する際に面倒だから(?)
定数に関してはこちらの記事が勉強になりました。
qiita.com


元記事はこちら。
blog.golang.org

Goのtypesとstructについて

Go言語のメソッド構文とは

Go言語には、他の言語でいうところのクラスに相当する構文がないらしい。

ではどうするかというと、

type TwoValuesType struct {
	X, Y int
}

func (v TwoValuesType) TwoValuesPlus() int {
	return v.X + v.Y
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.TwoValuesPlus())}

こうしてあげるといいらしい。

まず最初に、type構文でstructを宣言する。
中身は、Integer型の変数X,Yである。

次に、このstructを引数にとる、関数TwoValuesPlus()をつくる。返り値はIntegerである。(関数名などが適当ですみません)
この関数では、引数にとったvにあるX, Yを足し合わせ、その結果を返り値としている。

最後に、メイン関数内で、v変数に数値3,4を入力したstructを渡したあと、
vを引数としたTwoValuesPlus関数を呼ぶ。

この際、vからメソッドのように、.TwoValuesPlusで関数を呼べる。
こうしてあげると、オブジェクト指向的な呼び方で
コードの可読性も上がるらしい。

ふむふむ。
となったはいいものの、色々不明瞭な部分がある。

typesって?

ちょっとまて、そもそもtypesってなに...? いきなり出てきたけど。。
funcとは何が違うの...? と、次々と疑問がでてくる。
ということでGoドキュメントを覗いてみる。

Package types declares the data types and implements the algorithms for type-checking of Go packages. Use Config.Check to invoke the type checker for a package. Alternatively, create a new type checker with NewChecker and invoke it incrementally by calling Checker.Files.

ふむふむ。よくわからん。
なにやら、データ型を宣言し、type-checkingと呼ばれるアルゴリズムを実行するらしい。
上述の例でいくと、construct型のtypeを宣言するために使うってことなのか?

structって?

ではstructとはなにか?というと、
ユーザーが定義する具体的な名前とその型の集合のことらしい。
日本語では「構造体」とよくいわれる。
前述のとおり、Goにはclassが存在しないため、structを使うことで同様のことができる。

具体的にみてみる。

type Person struct {
	name string
	age int
	city string
}

ここではまず、Personというstruct型を宣言している。
その中身で、stringのname変数...等と変数および型を宣言している。
これによって、Personという名のインスタンスを作成できるし、各変数に具体的な値を入れることもできるようになった。(クラス的にいうと)

var p Person
p.name="shiju"
p.age=35
p.city="Kochi"

// or //

p := Person {
  name="shiju",
  age=35,
  city="Ko.chi", // カンマを忘れないように
}

pという新たな変数名でPersonオブジェクトをつくり、pから各変数名へとアクセス・値代入できるようになった。


ただ、結局のところtypesとstructは別物で、必ずしも一緒に使う必要はないらしい。
こちらの記事が参考になりました。

Goを学びたての人が誤解しがちなtypeと構造体について #golang - Qiita


ただ内容を読んでもあまり理解できなかったので、しばらくしてから改めます...

/* */