#はじめに
TechCommit Advent Calendar 2019
22日目担当のSatoshi
です。
####筆者について
エンジニア歴2年
22歳
業務ではJavaScriptを使ってなにやらやっています。
Goを使ってバックエンド開発がやりたいので、Goを学習中
####本記事の内容
本記事では、Go言語の入門編として、基礎的な内容を書いていきます。
筆者は、業務ではJavaScript(動的言語)を触っているので、それと比較してGo言語(静的言語)の良いところにもちょくちょく触れていけたらなと思います。
#本記事のゴール
- Go言語について、概要レベルで理解できる
- 基礎的な部分をさらっと学習、復習できる
#Go言語について
Go言語は、2009年にGoogleによって開発された言語です。
特徴としては、一部ですが以下のようなものがあります。
- シンプルな言語
- 静的型付け
- クロスプラットフォーム対応
- コンパイル・実行速度が早い
- 並列処理が得意
- フォーマットの統一性
etc...
Go言語では、標準でフォーマットを整えてくれる機能(go fmt)や、使ってない変数があるとコンパイルできないように設計されているので、属人性は出にくいのかなと思いました。
本記事では、クロスプラットフォーム対応
やコンパイル・実行速度が早い
、並列処理が得意
などには触れません。
#導入
サクッと簡単なプログラムだけ組んで遊んでみたいということであれば、Go公式ページのTry Go
もしくはOpen in Playground
リンクを踏むと、環境構築無しにWeb上でプログラムが実行可能です。
Go公式 - Downloadsにアクセスし、ご自身の環境に合ったファイルをダウンロードして下さい。
各環境の細かいインストール方法を記述すると大変なので、各自で調べてください。
- [GO言語をMacで使ってみる インストール] (https://qiita.com/Noah0x00/items/63e024f9b5a27276401b)
- WindowsにGo言語をインストールする方法まとめ
- CentOSにGo言語のインストール
####公式の導入方法
以下ページ(英語)に行くとシステム要件やGoツールのインストール方法、アンインストール方法などが載っています。
Getting Started
#チュートリアル
Goには、公式が提供しているチュートリアル、A Tour of Goというものがあります。
上記リンクを踏んで、ページを1つ進めると日本語や英語、中国語など様々な言語が選択できます。
ただ、説明がとても短い章があるので、別のサイトで調べつつ取り組むことをオススメします。
#世界に挨拶
以下のソース内で、import "fmt"
と記述していますが、fmt
とはGoが標準で提供しているパッケージです。
Goが提供しているパッケージの一覧は、Go公式 - Packagesで確認できます。
Goでは、以下のように挨拶します。
//ソースファイルがどこに所属しているか必ず宣言
package main
//入出力フォーマットを実装した標準パッケージ"フォーマット"をインポート
import "fmt"
//関数はfuncキーワードで定義
func main(){
fmt.Println("Hello, World!") // Hello, World!
}
Goでは、決まりとしてプログラムは何らかのpackageに属している必要があり、そのうちの 1 つは必ず main でなければならない。
というものがあります。
また、mainパッケージ
のmain関数
があれば、それが最初(初期化処理後)に実行されます。
別の関数を定義して呼び出したいときは、main関数の中で
呼び出します。
package main
import "fmt"
//stringの部分は、戻り値の型を示す
func Say() string{
return "Neighbor!"
}
func main(){
fmt.Println("Hello,", Say()) //Hello, Neighbor!
}
#特別な関数init
世界に挨拶
では、Goでは、mainパッケージのmain関数が最初(初期化処理後)に実行されます
と記述しました。
しかし、mainパッケージ
にinit
という関数を定義することで、main関数よりも先に関数を実行することができます。
このinit関数
を利用することで、変数の初期化
や外部リソースの読み込み
などを一番最初に実行できます。
package main
import "fmt"
func init(){
fmt.Println("Initializing...")
}
func main(){
fmt.Println("Hello, World!")
}
//Initializing...
//Hello, World!
#Import
Goでは、様々なパッケージをインポートしつつプログラムを書いていきます。
以下は、現在の時刻を出力するプログラムです。
package main
//import()と記述することで、複数のパッケージをインポートできる
import(
"fmt"
"time"
)
func main(){
fmt.Println("Now: ", time.Now()) // Now: 2009-11-10 23:00:00 +0000 UTC m=+0.000000001
}
#変数宣言
Goでは、変数の型を変数名の後ろに定義
する決まりとなっています。
また、様々な型が提供されています。Go - Packages - Variables
var
での型宣言は、関数外でも宣言できますが、ショートデクラレーション(:=)
は関数内でしか宣言できません。
JavaScriptだと型宣言はしないので、ソースの型宣言を見るだけで変数の型が分かるのは良いですね!
また、予期せぬ型が入ってくることも少ないので、安全です。
package main
import "fmt"
func main(){
//varで宣言
var i int = 1
//var()で複数宣言
var (
f64 float64 = 1.5
str string = "foo"
)
//カンマ区切りで複数宣言、かつショートデクラレーションで型推論
t, f := true, false
fmt.Println(i, f64, str, t, f) // 1 1.5 foo true false
//ちなみに、初期化だけして値を代入しなかった場合、それぞれの型の初期値が出力される
}
#関数
Goでは、関数の戻り値の型を指定します。
基本構文は
func <関数名>([引数]) [戻り値の型] { [関数の本体] }
です。
JavaScriptと違い、関数定義時に戻り値の型を指定できる
ので、実行せずともある程度どの型が戻り値かが分かります。
package main
import "fmt"
//int型の戻り値
func add(x int, y int) int{
return x + y
}
//複数の戻り値の型を指定
func sub(a, b int) (int, string){
return a - b, "subtraction!"
}
//戻り値"result"は予約語ではない。この関数内で戻り値としてresultを使用するということ
func calc(price, item int) (result int){
//resultをショートデクラレーション(:=)で再定義はできない
result = price * item
return result
}
func main(){
radd := add(10, 20)
fmt.Println(radd) // 30
rsub, rsubStr := sub(10 , 5)
fmt.Println(rsub) // 5
fmt.Println(rsubStr)// subtraction!
//以下のように書くことで、即時に関数を実行できる
func(a int){
fmt.Println(a) //9999
}(9999)
}
#型変換
以下に、いくつかの型変換を行うプログラムを記述します。
package main
//string->intへキャストする際に、strconvを使う
import (
"fmt"
"strconv"
)
func main(){
//int -> float
var x int = 1
f64x := float64(x)
//%Tは、型を出力、%vは値を出力、%fは指数なしの小数を出力する
fmt.Printf("%T %v %f\n", i, i, i) //int 1 %!f(int=1)
fmt.Printf("%T %v %f\n", f64x, f64x, f64x)// float64 1 1.000000
//float64 -> int
var y float64 = 1.5
inty := int(y)
fmt.Printf("%T %v %f\n", y, y, y) //float64 1.5 1.500000
fmt.Printf("%T %v %f\n", inty, inty, inty)// int 1 %!f(int=1)
}
//string -> int
var str string = "72"
//strconvの "Atoi(ASCII to integer)" は "int" と "error" の2つを返すため、 "_" でerrorを捨てる。
//今回はエラーハンドリングを実装しませんが、ハンドリングするときは"_"ではなく"err"などの変数を定義し、ハンドリングしてください。
i, _ := strconv.Atoi(str)
//stringで72を文字列型にキャストすることで、ASCIIコードに沿って文字が出力される
fmt.Printf("%T %v %v\n", i, i, string(i)) // int 72 H
#配列とスライス
配列は固定長
、スライスは可変長の配列のようなもの
です。
####配列
package main
import "fmt"
func main(){
//配列
//int型の配列で、要素数2
var x[2] int
x[0] = 1
x[1] = 2
fmt.Println(x) // [1 2]
//初期化時に値を入れたい場合、ブラケットを使って代入する
var y[3]int = [3]int{1, 2, 3}
fmt.Println(y) // [1 2 3]
fmt.Println(y[0:2]) // [1 2]
fmt.Println(y[1:2]) // [2]
fmt.Println(y[:2]) // [1 2]
fmt.Println(y[1:]) // [2 3]
}
####スライス
package main
import "fmt"
func main(){
//型宣言時に、要素数を指定しない
var z []int = []int{1, 2, 3} //z := []int {1, 2, 3} でもOK
fmt.Println(z)// [1 2 3]
//配列と違い、後から値を追加(append)できる
z = append(z, 4)
fmt.Println(z)// [1 2 3 4]
fmt.Println(z[0:2]) // [1 2]
fmt.Println(z[1:2]) // [2]
fmt.Println(z[:2]) // [1 2]
fmt.Println(z[1:]) // [2 3 4]
}
####makeとcap
makeの詳細は、実践Go言語 - makeによる割り当てを参照。
package main
import "fmt"
func main(){
//integerのスライスで、長さが3つ、キャパシティは5
a := make([]int, 3, 5)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=3 cap=5 val=[0 0 0]
//長さが3なので、valは0が3つ並んでおり、キャパシティは5なので、メモリ上にはあと2つ確保してある
a = append(a, 0, 1)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=5 cap=5 val=[0 0 0 0 1]
//appendで "0" と "1" を追加したので、長さが5になった。
//キャパシティ5以上追加してみる
a = append(a, 6, 7, 8)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) //len=8 cap=12 val=[0 0 0 0 1 6 7 8]
}
上記プログラムの最終行で実行しているfmt.Printfの出力結果に注目してください。
キャパシティも長さも5
のスライスに、appendで3つ
追加しただけのはずが、追加後のスライスのキャパシティが12
になっています。
こんな挙動をする原因を、以下のプログラムで暴きます。
package main
import "fmt"
func main(){
//引数を省略することで、以下だと長さもキャパシティも3ということになる
a := make([]int, 3)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a)
fmt.Printf("*p= %p\n", a) // *p= 0x40e020
a = append(a, 0)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a) // len=5 cap=5 val=[0 0 0 0 1]
fmt.Printf("*p= %p\n", a) // *p= 0x456020
//キャパシティ5以上追加してみる
a = append(a, 6, 7, 8)
fmt.Printf("len=%d cap=%d val=%v\n", len(a), cap(a), a)
fmt.Printf("*p= %p\n", a) // *p= 0x456020
}
上記の通り、キャパシティ以上に要素を追加しようとすると、append関数内部
でメモリ領域の再確保が行われ、新しい領域のアドレスが参照
されるようになります。
元の値は、新しい領域へコピー
されることになるので、メモリ領域の再確保にも、コピー処理にも計算コスト
がかかります。
#map
Goではハッシュ(連想配列)のことをmap
と呼びます。
package main
import "fmt"
func main(){
m := map[string]int{"foo": 1, "bar": 2}
fmt.Println(m) //map[bar:2 foo:1]
fmt.Println(m["foo"]) // 1
m["bar"] = 999
fmt.Println(m) //map[bar:999 foo:1]
//存在しないオプション名にアクセスすると、0が返ってくる
fmt.Println(m["nothing"]) //0
//mapに存在しているかチェックする方法は以下。戻り値は "値"と"bool"の2値
val, ok := m["foo"]
fmt.Println(val, ok)//1 true
}
#可変長引数
関数やメソッドやマクロの引数
が固定ではなく任意の個数
となっている引数のことです。
package main
import "fmt"
func foo(params ...int){
fmt.Println("len=",len(params), "param=",params)
//for rangeを使うことで、paramsをループできる
for _, param := range params{
fmt.Println(param)
}
}
func main(){
foo(10, 20) //len= 2 param= [10 20]
foo(10, 20, 30, 40, 50) //len= 5 param= [10 20 30 40 50]
}
#if文
特に根本的な動きや書き方で他言語と異なる部分はありません。
GOでは以下のように書きます。
また、Goではif文と同時に変数を宣言
し、宣言した変数のスコープをそのif文だけに限定
できる書き方があります。
この書き方にすることで変数のスコープが限定でき、コードを読む時も理解しやすくなります。
package main
import "fmt"
func main(){
age := 400
//普通にif文。 if(xxx){} ではく、if xxx {}。
if age == 400 {
fmt.Println("Vampire")
}else if age < 400 {
fmt.Println("Dragon")
}else{
fmt.Println("Cat")
}
//if文定義と同時に変数sayを宣言し、";"で区切った後に条件として使用
if say := "Hello,"; say == "Hello," {
fmt.Println(say, "World!") // Hello, World!
}
//以下で変数sayを出力しようとするが、sayが使えるスコープは上記のif文だけなので、コンパイルエラーとなる
fmt.Println(say)
}
#switch文
こちらもif文同様
、他言語と比べて異なる書き方はしません。
また、switch文にもif文同様、switch文の定義と同時に変数を宣言し、宣言した変数のスコープをそのswitch文だけに限定できる
書き方があります。
package main
import "fmt"
func main(){
weather := "red"
//普通にswitch文
switch weather {
case "blue":
fmt.Println("blue.")
case "yellow":
fmt.Println("yellow.")
case "red":
fmt.Println("yeahhhhh")
default:
fmt.Println("never mind.")
}
//同時に変数宣言。スコープをこのswitch文のみに限定
switch animal := "cat"; animal {
case "dog":
fmt.Println("dog")
case "cat":
fmt.Println("meow!meow!meow!meow!")
default:
fmt.Println("never mind.")
}
//switch文のスコープ外なのでコンパイルエラー
fmt.Println(animal)
}
#for文とrange
こちらもif文
、switch文
同様で、特別なことはありません。
continue
とbreak
も、使い方は変わらないと思いますので、調べてみてください。
拡張for文(foreach)を実現したい場合は、range
というものを使う必要があります。
####forとcontinue, break
package main
import "fmt"
func main(){
max := 10
//普通のfor文
for count := 0; count <= 10; count++ {
fmt.Println(count)
if count == 2 {
fmt.Println("now: 2")
continue
}
if count > 5 {
fmt.Println("Done!")
break
}
//残念ながら、このプログラムでは以下の分岐に入りません...
if count == max {
fmt.Println("Max!!")
}
}
}
####rangeを使った拡張for文
package main
import "fmt"
func main(){
strArray := []string{"foo", "bar", "bizz"}
//rangeはindexと値の2値を返すので、変数も2つ用意。
for i, v := range strArray {
fmt.Println(i, v)
/*
0 foo
1 bar
2 bizz
*/
}
//"_"を使うことで、値だけ取り出せる
for _, v := range strArray {
fmt.Println(v)
/*
foo
bar
bizz
*/
}
//mapでも使える
strMap := map[string]int{"Taro": 21, "Hanako": 19, "John": 22}
for k, v := range strMap {
fmt.Println(k, v)
/*
John 22
Taro 21
Hanako 19
*/
}
//keyだけ取得することも可能
for k := range strMap {
fmt.Println(k)
/*
Taro
Hanako
John
*/
}
}
#while文
GoではWhileを以下のように書きます。
package main
import "fmt"
func main(){
//無限ループ
for{
fmt.Println("GoにWhile文はありません...")
fmt.Println("騙してごめんなさい。")
}
//10回ループ
count := 10
for count > 0 {
fmt.Println(count)
count--
}
}
#最後に
唐突ですが、以上で入門編を終わりにしたいと思います。(続編を書くかは分かりませんが...)
本記事では細かい部分を結構削っています。
理由としては、話が脱線しそうだということと、さらっと学習、復習できる
ということを目標にしていたたためです。
ただ、入門として知っておくべきことは他にもあります
。
ここまで学習できれば、あとはトライ&エラー
で学習を進めていき、細かい部分のキャッチアップは可能かなと思っているので、一緒に頑張って行きましょう!
Qiita初投稿の記事で時間は結構かかりましたが、書くのは面白いですね。
これは絶対に入門として必要だ
というものがあれば、コメントで教えて下さい!
筆者は最近Goを触り始めたので、何か間違いなどあった場合には、遠慮なくご指摘いただけると大変嬉しいです...!
あと、Gopherくん可愛いですよね。
可愛すぎてLINEスタンプ買っちゃいました。
以上です。