🤖

Progateで習うGoな文法たち

2024/01/20に公開

はじめに

こんにちは!当記事をご覧くださりありがとうございます!
この記事はProgateで学んだGoの知識をアウトプットする場としてまとめたものです。
PythonやRubyなど比較的簡単な言語の基礎知識を学んでいることを前提とし、比較演算子や文字の連結、四則演算などについては概ねどの言語でも同じなので省略しています。
また、筆者がPython畑出身ですのでPythonから見たGoの感想をところどころ述べています。
指摘などございましたら是非コメントお願いいたします。
では、始めていきましょう!!

Hello,Worldからif,elseまで

ここではほかの言語でも共通である文法についてざっと箇条書きにします。
ざっと読み飛ばして進んでください

Hello, World!

package main

func main() {
	println("Hello, World!")
}

コードは上記のようになります。Pythonだとprint("Hello,World!")で済んだのが、Goだとグローバルスコープで書けない仕様になっています。

変数定義と代入

package main

func main() {
	var n int
	n = 100
	println(n)
	n = 200
	println(n)
}
結果
100
200

var(variant)で変数を定義し、=で代入します。varで宣言した変数は更新も可能です。
次のように省略した変数の定義方法もあります。

package main

func main() {
	var l int = 100
	var m = 100 // intが省略されています。Goによって動的型付けが行われます。
	n := 100 // varとintが省略されています。:=によって変数であると解釈されます。
	println(l, m ,n)
}

また余談ですが、Goではmain関数内で定義した変数を使用しないと実行時にエラーになります。

= と := の違い

おそらくGoで初めて出会うであろう:=ですが、あくまでもこれは変数の宣言時にのみ使うことができます。更新に使うことはできないことに注意してください。
例えば、次のように書くことはできません。

package main

func main() {
	a := 100
	a := 200 ← エラーが起きます。
}

条件分岐

package main

func main() {
	score := 80
	if score >= 90 {
		println("Perfect!")
	} else if score >= 60 {
		println("Good")
	} else {
		println("BAD")
	}
}

こんな感じになります。言うなれば、ifやelse ifのあとの条件文を()でくくる必要がないことくらいでしょうか。他の比較演算子も他言語と特に変わりはなく、==!=を使います。
しかし、GoにはJavaScriptやPHPにあるような===はありません。

論理演算子

func main() {
    x := 20
    if 10 <= x && x <= 30 {
        println("xは10以上30以下です")
    }
    
    y := 60
    if y < 10 || 30 < y {
        println("yは10未満または30より大きいです")
    }

これも特に変わりありませんね。
ただ、10 <= x <= 30と書くことはできません。Pythonサイコー!

switch文

package main

func main() {
   n := 3
   switch n {
   case 1:
   	println("大吉です")
   case 2, 3:
   	println("吉です")
   default:
   	println("凶です")
   }
}

JavaScriptなどでよくみるswitch文とまるで同じですね。
Pythonerの私としてはwhileはないのにswitchはあんのかよって思ってます。
私のようなPython出身の方のために簡単に説明しますと、こんな感じです

package main

func main() {
	n := 3
	switch n { // switchのあとに参照したい値を持ってくる
	case 1:
		println("n==1 の処理")
	case 2, 3:
		println("n==2 or n==3 の処理")
	default:
		println("nがどれでもない時の処理")
	}
}

caseの条件に<<=などの比較演算子は使えません。==と同じ一致のみです。Pythonサイコー!

Packageとループ処理

fmtパッケージ

fmt.Printf

package main

import "fmt"

func main() {
    name := "Ahoxa"
    fmt.Printf("Hello, I'm %s", name)
}

fmtパッケージのPrintfメソッドでは変数や定数を第二引数に取ることで%s(string)にstring型の変数や定数を使用することができます。また、整数は%d(decimal)で表示できます。

package main

import "fmt"

func main() {
    month := 11
    day := 6
    fmt.Printf("今日は、%d月%d日です", month, day)
}
結果
今日は、11月6日です

上記のコードでは一つ目の%dと二つ目の%dにそれぞれmonthdayが表示されます。

fmt.Scan

package main

import "fmt"

func main() {
	var input string
	fmt.Scan(&input)
	println(input)
}

fmtパッケージのScanメソッドでは引数に変数を取って、その変数にコンソールで受け取った入力値に格納します。なので、上記ではコンソールで入力を受け取った後、その値がそのままコンソールに出力されます。

math/randパッケージ

package main

import "fmt"
import "math/rand"

func main() {
	fmt.Println(rand.Intn(10))
}

randパッケージはIntn(n)を使うことでn未満の整数をランダムに返します。
ここでは0以上9以下の整数をランダムに返します。

forループ

package main

import "format"

func main() {
	for i:=1; i<=3; i++ {
		fmt.Println(i)
	}
}
結果
1
2
3

forループはこのようになります。PHPっぽい書き方ですね。私はよく;を忘れて,で区切ってエラー起こしてます。
また、iですが、var i int = 1のような宣言はできず、i:=1でのみ変数の宣言が可能です。

関数

func

package main

import "fmt"

func main() {
	helloSomething("Go")
}

func helloSomething(str string) {
	fmt.Printf("Hello, %s!", str)
}
結果
Hello, Go!

Go独特の書き方として、引数に型を指定する際の記法が挙げられます
。ここでは引数strにstring型を指定していますね。もしここで型指定をしないとundefinedエラーになります。
もし複数の引数を渡したい場合、

func fn(a int, b string){
	...
}

として,区切りで複数の引数を渡すことができます。
またさらに、Goでは関数の戻り値にも型を指定することができます。

package main

func main() {
	println(AkioSeki(6, 6, 6))
}

func AkioSeki(a int, b int, c int) string {
	if (a+b+c)%6 == 0 {
		return "6・6・6、悪魔の数字フリーメイソンっ!"
	} else {
		return "ノットメイソンっ!"
	}
}

こんな感じでfunc kansu() 型 {}として戻り値の指定ができます。

ポインタ

さて、Go最初にして最大の難所、ポインタです。
変数を定義し、値を格納する。という工程を行うとき、その変数にはメモリが割り当てられ、さらにそのメモリには住所のような16進数の値が割り当てられます。
この値のことをGoではポインタと呼び、Go以外ではC系の言語で取り扱うことができます。
ポインタは数や文字列と同様に変数に格納したり、操作することができます。
次のようにして変数のポインタを取得し、コンソールに出力することができます。

package main

import "fmt"

func main() {
	name := "Ahoxa"
	fmt.Println(&name) // &変数名でポインタを取得
}

ポインタ型変数

ポインタの値が格納された変数をポインタ型変数と呼び、次のように変数のデータ型に*をつけて宣言します。

package main

import "fmt"

func main() {
	name := "Ahoxa"
	var namePtr *string = &name
	fmt.Println(namePtr)
}
結果
0xc000028070

逆に、*をポインタ型変数につけることで、ポインタの値から変数の値を参照することもできます。

package main

import "fmt"

func main() {
	name := "Ahoxa"
	var namePtr *string = &name
	fmt.Println(*namePtr)
}
結果
Ahoxa

&やら、*やらでややこしいですね。ポインタのあれこれは次のようにまとめることができます。

fmt.Println(〇〇) 出力値
name Ahoxa
&name 0xc000028070
namePtr 0xc000028070
*namePtr Ahoxa

関数の引数にポインタを指定する

ポインタを使う理由の一つにスコープを気にせずに使用できるというものがあります。
関数の引数にポインタを指定したいときも*が必要になります。

package main

import "fmt"

func main() {
	name := "Ahoxa"
	changeName(&name)
	fmt.Println(name)
}

func changeName(namePtr *string) {
	*namePtr = "Alexa"
}
結果
Alexa

さて、ここまでわかったところで、ポインタを使ったときと単に引数を使用したときでどのよように挙動が違うのか見てみましょう。次のようなコードがあるとします。

package main

import "fmt"

func main() {
	score := 0
	fn(score)
	fmt.Println(&score)

}

func fn(score int) {
	fmt.Println(&score)
}
結果
0xc00000a0e0
0xc00000a0c8

main関数内でscoreが宣言され、fn関数が呼び出されます。fn関数内では引数のscoreのポインタを出力します。そして、main関数に戻り、main関数内でもう一度scoreのポインタが出力されます。
同じscoreを使用しているので同じポインタが出力されるはずなのに実際に出力された値は異なっています。
つまり、これら2つは変数名は同じでも、異なる変数として扱われているのです。
これは関数の引数はコピーして参照されるために起こります。そのため、ポインタを参照せずに処理を行うと今回の場合では1つ余分にメモリが消費されてしまっているのです。
もう1つ例を見てみましょう。

package main

import "fmt"

func main() {
	a := 10
	b := 10
	increment(a, &b)
	fmt.Println("引数に整数を指定した場合:", a)
	fmt.Println("引数にポインタを指定した場合:", b)
}

func increment(a int, bPtr *int) {
	a += 1
	*bPtr += 1
}
結果
引数に整数を指定した場合: 10
引数にポインタを指定した場合: 11

aはポインタを用いない引数で、bはポインタ変数でインクリメントする関数を呼び出しています。bではしっかりインクリメントされていますね。

おわりに

Goの基礎文法、いかがでしたでしょうか。すこしポインタのあたりが雑になってしまったので今後もっとサンプルを増やして説明しようと思います。
最後までご覧いただきありがとうございました。

Discussion