1
1

More than 1 year has passed since last update.

Go言語の導入から基本まで

Posted at

なぜ本記事を書いたのか?

Goの仕様や使い方について自分の把握している範囲を整理するために書いています。

本記事の対象読者

  • Goは使ったことがないけれども、他言語は使ったことがある。
  • Goをとりあえず始めるための環境構築を知りたい。
  • forやwhileなどはわかるが、Goでの書き方を知りたい。

などと考えているプログラミングライトユーザー向けのレベルの記事として仕上げるつもりです。
正直文法云々はGoの公式チュートリアルをやってもいいとは思いますが、僕が追加で気になって実験した多少の+αを残す意味で書いてます。チートシート+αくらいでみていってください。
足りないところだらけだけど、間違い、補足等コメントください。

筆者の環境

  • CPU : Apple M1
  • OS : macOS Monterey v12.3.1
  • Goバージョン:1.18.3

Goの環境構築

Goのインストール

前提知識的なやつ

GOPATHとは

外部パッケージなどが保存される場所、実行バイナリもここに保存される。

$ go env GOPATH

で確認ができます。
設定方法などは後述
似たようなものにGOROOTというのがありますが、IDEでの設定時や複数バージョンを使用する時につかうくらいであまり意識することはありません(=筆者は詳しくないです。)

単一インストール

最新verのインストール方法を記載します。複数バージョンのインストールなどは後述
Go/Downloads をクリックしてリンク先でGo ver1.18のpkgをダウンロードしてください。IntellMacとM1Macでpkgが違うので注意してください。
もしくはターミナルで

$ brew install go

でも大丈夫です。
ダウンロードしたpkgを実行するとインストールが終わります。最後に

$ go version
go version go1.18.3 darwin/arm64

を実行して正常に設置されているかとGoのverを確認してください。

複数verのインストール

この節を実行する前に

Goは結構前からマイナーアップデートばかりで後方互換性がある。(らしい)
よっぽど前のGoのverと最新verで別々の開発をしたい!とかでなければ複数verのインストールはいらない可能性大
むしろgoenvの環境変数設定などの分煩雑になる恐れあり、とりあえず最新verのみ使っとけ

goenvの導入

Go言語のバージョン管理ツール goenv を導入します。
GitHubから直接持ってくる方法とHomebrewを使う方法がありますが、今回はHomebrewで導入します。

$ brew install goenv

bash,zshのどちらかの環境に合わせて.bash_profile.zshrcに以下の環境変数を追記

export GOPATH=$HOME/go ##これ以外にgopathを設定してもいい
export GOENV_ROOT=$HOME/.goenv
export PATH="$GOENV_ROOT/bin:$PATH"
eval "$(goenv init -)"
export PATH=$PATH:$GOPATH/bin

ターミナル再起するか、

$ source ~/.bash_profile

or

$ source ~/.zshrc

バージョン確認

$ goenv --version
goenv 2.0.0beta11

できなかった場合brewでのインストールのログ見直してエラーが出ていないか、pathが通っているかを確認してください。

Goのインストール

goenvを用いてGo本体のインストールをします。
以下のコマンドで利用可能なGolangのverのリストが閲覧できます。

$ goenv install -l
Available versions:
  1.2.2
  1.3.0
  ...
  1.18.3

1.18.3をインストールしてみる

$ goenv install 1.18.3

とりあえずはglobal環境のGoバージョンを指定してみる。

$ goenv global 1.18.3

Dirごとのlacal指定も以下でできます。

$ goenv local 1.18.3

自分が現在いる場所でのGoのverを確認しましょう。

$ go version
go version go1.18.3 darwin/arm64

goenvのリストに最新verが表示されない!

まずはgoenvのバージョンアップしてリストを見てみる。

$ brew update
$ brew upgrade goenv
$ goenv install -l

それでも最新verがない!=>参考URL

$ brew uninstall goenv
$ brew install --HEAD goenv

こんなことするくらいなら大人しく単体インストールした方が楽

準備完了?

以下を実行して正しくインストールされているか確認して次の章に

$ go version
go version go1.18.5 darwin/arm64

$ go env GOPATH
/Users/username/go #人による

前提知識2

コンパイル・ビルド?

  • コンパイル => ソースコードをオブジェクトコードに変換すること
  • リンク => オブジェクトコードをくっつけて一つのバイナリコードにする
  • ビルド => コンパイル + リンク

モジュール?パッケージ?

Goの中のソースコードのまとまりについてまず記述する。最初超こんがらがったし、なんなら今も理解してないので僕なりの理解です。悪しからず、、、

関数・クラス

他の言語やってる方ならご存知ソースコードのどこかで定義された関数やクラスです。自作する中では最小の単位なのではないでしょうか。

パッケージ

  • 同じディレクトリに存在するソースコードファイル群。
  • 上のソースコードファイル群は一緒にコンパイルされます。
  • 同じPackageに所属するソースファイル間では、関数や変数などで共有されます。
    一つのdirには一つのパッケージしか定義できない
    例として以下をdir1に作成する。
dir1/ex1.go
package main

func main() {
    test2()
}
dir1/ex2.go
package main

import "fmt"

func test2() {
    fmt.Println("package")
}

以下のコードでビルドできる。(dir1にいるとして)

$ go build .

モジュール

  • いっしょに開発された関連するプログラム(パッケージ)の集合体
  • パッケージは単一dirを指すがモジュールは後述のgo.modに記述されているpathの配下全てを対象とする

ライブラリ

ここが正直よくわかってない、明確にこうやで!って知ってる人連絡ください、、、

モジュール管理

GOPATHモードとモジュールモードが現在存在する。
今からGoを始めるなら大人しくGo moduleを利用してモジュール管理をすることをおすすめする。

  • GOPATHモード
    • 全部のソースコードやパッケージを置いて管理するモード
    • Go1.11以前の標準
    • 好きなところにプロジェクトを作成できない(GOPATH配下に全部作成してね!)
  • モジュールモード
    • コード管理とビルドは任意のディレクトリで可能  
    • Go1.11から標準で仕様可能
    • こっちの方が楽だしスタンダート

go.modファイル

モジュールのパスとバージョン情報を記述しておくファイル。アプリケーションの依存関係を記述しておくもの。
中身の概念は以下のよう

go.mod
module 
#自分自身のモジュール名

go 
#goのバージョン

replace github.com/githubName/app/middleware => ../middleware
#モジュールのインポートpathを別の場所に置き換えることができる。相対path指定してローカルモジュールをインポートできるようにする。(らしい)

require (
  github.com/github-name/repository-name/app/middleware v0.0.0-000101...
  github.com/go-sql-driver/mysql v1.6.0
  github.com/jinzhu/copier v0.3.5 // indirect
)
#依存先のモジュールのインポートpathやバージョンを記述する。gitのコミットハッシュでバージョン指定してるらしい、自分で直接編集はできるが、自動生成にまかせておきたいところ。//indirectは間接的な依存先を示してる。

exclude 
#使用できないようにするモジュールを指定する。バグとかのあるバージョンを使わないように指定できたりする。

retract
#一度公開したモジュールを撤回して利用停止させるために使うらしい(使ったことなんてない)

go.sumファイル

go.modに記述してある依存先モジュールのチェックサム値を記録しておくファイル。go.modを利用して取得したモジュールがgo.mod作成時のものと同じものであるかをチェックするのに使用される。なくても再現上の問題はない。

$ go mod tidy

のコマンドで自動的に作成される。中身をいじるな

モジュール管理モードの確認

以下のコマンドを実行

$ go env | grep GO111MODULE
GO111MODULE=""

""の中身により今のモジュール管理モードが何になっているか把握できる。値ごとの詳細は以下

モード説明
on または 空 常にモジュールモードで動作
off 常にGOPATHモードで動作
auto $GOPATH/src以下のディレクトリに配置され、go.modファイルを含まないパッケージはGOPATHモードで、それ以外はモジュールモードで動作

このGO111MODULE変数も環境変数として書き換えられるのでonか空白かautoにしておきましょう。
GOPATHモードでやる場合このページあてにならないかも
そもそも当てにならないという話は置いておく

モジュールインストール

go installコマンドを用いてモジュールを導入できます。

$ go install github.com/userName/modName~

入れたモジュールのバイナリ削除

$ which modName
/home/username/go/bin/modName
$ rm -rf /home/username/go/bin/modName

モジュール更新

$ go mod tidy

  • インポートしてないかつ使用していない依存モジュールは勝手に削除されます。
  • インポートしてるけどインストールされていない依存モジュールはインストールされます。
  • go.mod,go.sumファイルの中身を更新してくれます。

go getコマンド

僕が勉強しはじめた時はまだ使われてたコマンド今は機能の大部分をgo installコマンドにとって変わられています。今後廃止される予定。

このようにGoはまだ新しい言語で機能やスタンダートが刻々と変わっていくので記事が古いとGOPATHモードでの管理の話をしていたりgo getのみを使っていたりするのでできるだけ新しい情報を参考にしてください。もしこの記事を読んでいる時に最終更新から半年も経っていたらこの記事はあてにしないでください。

はじめの一歩

とりあえず、goを試してみるためにプロジェクトを作成してみます。
筆者の環境ではGOPATHは

$ go env GOPATH
GOPATH="/Users/username/go

に通っています。今回はホームdir下のDevというdirにgoEXというdirを作ってそこでプロジェクトを作成していきます。
Go Modulesを用いてプロジェクトを作成する場合、作成したいdir上で

$ go mod init <モジュール名>

を実行します。<モジュール名>の部分はモジュールを公開するつもりなら{レポジトリPATH}/モジュール名とします。公開しないならモジュール名のみで大丈夫です。

$ cd Dev/goEX
$ go mod init github.com/joryulife/GoTestPJ
go: creating new go.mod: module github.com/joryulife/GoTestPJ

今回モジュール名はGoTestPJにしました。ついでに後々のことを考えてpkgフォルダを作成しておきます。
ここにmain以外のパッケージをまとめていきます。

/Users/username/Dev/goEX $ mkdir pkg

この段階で以下のようになっているはずです。

-/Users/username/
 |-go #GOPATHの通っている位置
 |-Dev/goEX/
           |-go.mod
           |-pkg
go.mod
module github.com/joryulife/GoTestPJ

go 1.18

Goの基本文法

ようやっと本題です。Goの環境の話難しすぎ
と思ったらひとつだけ、変数名や関数名、ファイル名、dir名などの命名規則は言語ごとに違いますが(キャメルケースとかスネークケースとか)、筆者がGoの規約によって決められている分しか気をつけていないのでこちらの記事を参考にしてください。他言語プログラマが最低限、気にすべきGoのネーミングルール
この章終了時のdir構成は以下のようになっているはずです。

-/Users/username/
 |-go #GOPATHの通っている位置
 |-Dev/goEX/
           |-go.mod
           |-pkg
           |-main.go
           |-function_EX.go
           |-pointer_EX.go
           |-struct_EX.go
           |-array_EX.go
           |-map_EX.go
           |-range_EX.go

関数・変数の利用

main関数

GoTestPJ直下にmain.goを作成してください。

  • Goのプログラムはmainパッケージから実行される。
  • mainパッケージのmain関数がエントリーポイントになります。(実行の起点)
  • import文で利用するパッケージを読み込む。
  • 大文字で始まる名前はエクスポートされる(外部利用可能)
main.go
// ファイルの冒頭にpackage名を宣言する
package main

// パッケージのインポートをグループ化できる
import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("√3= %g\n", math.Sqrt(3))
    
    // 小文字で始まる名前は外部に公開されないので、エラーが発生する
    fmt.Println(math.pi)
    // 大文字で始まるPiはmathパッケージから利用可能            
    fmt.Println(math.Pi)
}

コード内でコメントしているように小文字の変数や関数名は外部から呼び出せないためこのコードを実行するとエラーが吐かれます。fmt.Println(math.pi)を消すかコメントアウトしてみてください。
実行してみます。実行はgo run <対象ファイル>でできます。

/Users/username/Dev/goEX $ go run main.go
√3= 1.7320508075688772
3.141592653589793

変数・定数の宣言

  • 変数はvarで宣言する
  • initializerで初期化可能、型を省略できる
  • 定数はconstで宣言する
  • 関数内ではvarで宣言せずに:=を使って暗黙的な変数と型の宣言ができる
  • 複数戻り値を受け取るときは戻り値の型と同じ並びで受け取る変数をカンマで区切って=の左辺におく
var x, y, z int
var s string

var i, j, b = 1, 2, true

const K int = 10000
const Truth = true

func test() {
    x,y = TowRetrunFunction(K,K) //二つのint型戻り値を返す関数とする
    foo := "bar"
    c := 2 + 0.5i // 複素数型
}

関数定義

  • 関数はfuncで定義する。
  • 引数の型は変数の後ろに書く。
  • 同じ型の引数が複数ある場合最後のみ書けば良い。
  • 複数の戻り値を返せる。
  • 戻り値の名前を指定できる。

基本は
func <関数名>(引数名 型) 戻り値の型 {処理内容}
の形で定義します。
goEX直下にfunction.goを作成します。パッケージはmainです。

function_EX.go
// mainパッケージで作成
package main

// 2つのintを受け取り、intを返す関数
func add(x int, y int) int {
	return x + y
}

// 型が同じ場合は、最後の型以外は省略できる
func addThree(x, y, z int) int {
	return x + y + z
}

// 複数の戻り値を返すことができる
func swap(x, y string) (string, string) {
	return y, x
}

// 戻り値に名前をつけることができる
func div(x, y int) (result int) {
	// result変数はこの時点で定義済み
	result = x / y
	// この場合、何も書かずにreturnできる(naked return)
	return
}

main.goを以下のように書き換えてfunction.goの関数を利用してみます。function.goはmainパッケージで宣言しているので関数名のみで直接main.goから使えます。

main.go
package main

import (
	"fmt"
	"math"
)

var a,b string
func main() {
	fmt.Printf("√3= %g\n", math.Sqrt(3))
	fmt.Println(math.Pi)
	fmt.Printf("%d\n", add(1,1))
	fmt.Printf("%d\n", addThree(1,1,3))
	a,b = swap("aaa","bbb")
	fmt.Printf("%s %s => %s %s\n","aaa","bbb",a,b)
	fmt.Printf("%d\n",div(8,4))
}

実行してみる。このmain.goにはfunction.goを利用しているためgo runコマンドで実行する場合関係するgoファイル名を全て引数に渡すか、*を使う。

$ go run *.go
√3= 1.7320508075688772
3.141592653589793
2
5
aaa bbb => bbb aaa
2

無名関数

Goも無名関数を用いることができます。以下のような形で定義できます。

func(必要なら引数){
    処理
}

使い方は2通りで変数に格納してから呼び出すパターンと自己実行させるパターンです。以下のコードの前者が変数を利用したパターン
後者が自己実行させるパターンです。

func main(){
    f := func(i int){
             fmt.Println("%d\n".i)
         }
    f(200)
    
    func(i int){
        fmt.Println("%d\n".i)
    }(400)
}

後者のように無名関数の定義のあとに()を書いて中に値をいれるとその場でその値を無名関数自身の引数として無名関数が実行されます。引数なしの無名関数ならこの()は空で構いません。

$ go run main.go
200
400

基本型

Goの組み込み型、説明、ゼロ値の一覧を記載します。

型名 説明 ゼロ値 最小値 最大値
bool 真偽値:trueかfalse false
string 文字列型 ""
int 整数値型bit数はOSやCPU依存 0
int8 8bit整数 0 -128 127
int16 16bit整数 0 -32768 32767
int32 32bit整数 0 -2147483648 2147483647
int64 64bit整数 0 -9223372036854775808 9223372036854775807
uint 符号なし整数値型bit数はOSやCPU依存 0
uint8 8符号なしbit整数 0 0 255
uint16 16符号なしbit整数 0 0 65535
uint32 32符号なしbit整数 0 0 4294967295
uint64 64符号なしbit整数 0 0 18446744073709551615
byte uint8の別名 0
rune int32の別名 0
float32 32bit浮動小数点型 0
float64 64bit浮動小数点型 0
complex64 64bit複素数型 0
complex128 128bit複素数型 0

for

  • Cと同じく初期化;条件;後処理の記述ができる。
  • ()で条件を括る必要はない
  • Goにはwhileはなく条件なしforで期限のないループを実現する
sum := 0

// カッコは不要
for i := 0; i < 10; i++ {
    sum += i
}

// 初期化と後処理は省略可能
for ;sum < 1000; {
    sum += sum
}

// セミコロンも省略可能 -> whileループ
for sum < 10000 {
    sum += sum
}

// 条件式も省略可能 -> 無限ループ
for {
    if sum > 100000 {
        break
    }
}

if

  • ifもforと同じく()で括る必要はない
  • 条件判定用の変数を初期化することができる
// カッコ不要
if i > 3 {
    return i
}

// xのスコープはif-elseブロック内のみ
if x := i * j + 3; x < 100 {
    return x
} else {
    return x - 100
}

// compile error
return x + 100

switch

  • caseごとのbreakは不要
  • ifと同じで条件判定用の変数を初期化することができる
  • caseは定数である必要はない
// os変数を初期化
switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("OS X.")
case "linux":
    fmt.Println("Linux.")
default:
    // freebsd, openbsd, plan9, windows...
    fmt.Printf("%s.\n", os)
}

// 条件のないswitchも可能(switch trueと同じ)
t := time.Now()
switch {
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

defer

  • deferが与えられた関数の上位ブロックの関数がreturnするまで関数の実行を遅延する。
  • 遅延実行される関数の引数は即時評価される。
  • 最後に渡したdeferから順番に実行される

とりあえず試してみる。

function.goに新しい関数を定義してmain.goから呼び出してみます。

function.go
package main

import "fmt" //追記
//省略

func deferTest() {
	defer fmt.Println("1")
	fmt.Println("2")
}
main.go
package main

func main() {
	//省略
	deferTest()
}

実行してみると

$ go run *.go
2
1

出力の順番が逆になりました。

遅延関数の引数の即時評価

関数deferTest()の中身を以下のように書き換えてみます。

func deferTest() {
	num := 1
	num = 2
	defer fmt.Println(num)
	fmt.Println("5")
}

実行すると

$ go run *.go
5
2

次に以下のように書き換えます。

func deferTest() {
	num := 1
	defer fmt.Println(num)
	num = 2
	fmt.Println("5")
}

実行してみます。

$ go run *.go
5
1

出力が変わりました!最初にdefer fmt.Println(num)に差し掛かったときのnumの値を評価すると1なので引数として1が渡された関数の実行が遅延しているんですね。

deferの積み上げ

さらにdeferTest()の中身を書き換えてみます。

func deferTest() {
	fmt.Println("start")
	for i:=0;i<10;i++{
		defer fmt.Println(i)
	}
	
	defer fmt.Println("end")
	fmt.Println("deferTest done")
}

実行

$ go run *.go
start
deferTest done
end
9
8
7
6
5
4
3
2
1
0

これが最後に渡したdeferから実行されるという言葉の意味です。
これ以上詳しくdeferについて知りたい場合はGo言語のdeferを正しく理解する | How defer in Golang worksを参考にしてください。

ポインタ

  • ポインタは値が格納されているメモリアドレスを指す。
  • ポインタのゼロ値はnil

とりあえず適当にテストコードを書いてみる。
pointer_EX.goを作成する。

pointer_EX.go
package main

import "fmt"

func pointerTest() {
	var a, b, c int
	a = 10
	b = 20
	c = 30

	var pA, pB, pC *int
	fmt.Printf("pA:%p\n", pA)
	fmt.Printf("pB:%p\n", pB)
	fmt.Printf("pC:%p\n", pC)
	pA = &a
	pB = &b
	pC = &c
	fmt.Printf("pA:%p *pA:%d\n", pA, *pA)
	fmt.Printf("&a:%p\n", &a)
	fmt.Printf("pB:%p *aB:%d\n", pB, *pB)
	fmt.Printf("&c:%p\n", &b)
	fmt.Printf("pC:%p *aC:%d\n", pC, *pC)
	fmt.Printf("&c:%p\n", &c)

	pA = pB
	*pC = *pA
	fmt.Printf("pA:%p *pA:%d\n", pA, *pA)
	fmt.Printf("&a:%p\n", &a)
	fmt.Printf("pB:%p *aB:%d\n", pB, *pB)
	fmt.Printf("&c:%p\n", &b)
	fmt.Printf("pC:%p *aC:%d\n", pC, *pC)
	fmt.Printf("&c:%p\n", &c)
}
main.go
package main

func main() {
	//省略
	pointerTest()
}

実行してみる。

$ go run *.go
pA:0x0
pB:0x0
pC:0x0
pA:0x1400012c008 *pA:10
&a:0x1400012c008
pB:0x1400012c010 *aB:20
&c:0x1400012c010
pC:0x1400012c018 *aC:30
&c:0x1400012c018
pA:0x1400012c010 *pA:20
&a:0x1400012c008
pB:0x1400012c010 *aB:20
&c:0x1400012c010
pC:0x1400012c018 *aC:20
&c:0x1400012c018

宣言だけして初期化していないポインタのゼロ値=nil値の出力は0x0(%p指定の16進数表記)となることがわかった。それ以外の挙動はCとかと変わらなさそう。ただしポインタ演算はないこれは賛否両論ありそうですが、、、

構造体 struct

  • 概念的にはCと変わらなさそう。
  • 構造体はフィールドの集まりである。
  • フィールドへのアクセスは.を使って行う
  • 本来の概念的には*構造体へのポインタ.xみたいなのが正規らしいがp.xでもかけるように設計されている。
    構造体の定義は以下のよう
type 構造体名 struct {
    フィールド名1 
    フィールド名2 
    ...
    フィールド名n 
}

実際に使ってみる。
struct_EX.goを作成する。

struct_EX.go
package main

import "fmt"

type VertEx struct {
	X int
	Y int
	Z int
}

func structTest() {
	// Vertexリテラル: field順に値を渡す
	v := VertEx{1, 2, 3}
	fmt.Printf("v.X:%d v.Y:%d v.Z:%d\n", v.X, v.Y, v.Z)
	//そのままアクセス
	v.X = 10
	// ポインタを使う
	p := &v
	(*p).Y = 20
	// 省略してp.Xとも書ける
	p.Z = 30
	fmt.Printf("v.X:%d v.Y:%d v.Z:%d\n", v.X, v.Y, v.Z)

	//初期いろいろ
	var v1 = VertEx{Y: 2, X: 1, Z: 3} // フィールドの順番は関係なし
	fmt.Printf("v1.X:%d v1.Y:%d v1.Z:%d\n", v1.X, v1.Y, v1.Z)
	var v2 = VertEx{Y: 2} // X,Zはゼロ値(0)をとる
	fmt.Printf("v2.X:%d v2.Y:%d v2.Z:%d\n", v2.X, v2.Y, v2.Z)
	var v3 = VertEx{} // X: 0, Y: 0
	fmt.Printf("v3.X:%d v3.Y:%d v3.Z:%d\n", v3.X, v3.Y, v3.Z)
	p2 := &VertEx{1, 2, 3}
	fmt.Printf("p2.X:%d p2.Y:%d p2.Z:%d\n", p2.X, p2.Y, p2.Z)
}
main.go
package main

func main() {
	//省略
	structtest()
}

実行する。

$ go run *.go
v.X:1 v.Y:2 v.Z:3
v.X:10 v.Y:20 v.Z:30
v1.X:1 v1.Y:2 v1.Z:3
v2.X:0 v2.Y:2 v2.Z:0
v3.X:0 v3.Y:0 v3.Z:0
p2.X:1 p2.Y:2 p2.Z:3

フィールドへの代入とゼロ値、アクセス方法がわかりましたね!!

arrayとslices

array

  • Goの配列概念その1
  • 長さを指定して宣言しなければならない
  • 長さは型情報として保持しているらしく後からいじれない

slices

  • Goの配列概念その2
  • こちらは可変長
  • arrayから切り出せる
  • データを格納してるのではなく、元のarrayの部分列を指している。
    sliceは元配列[:]で切り出せる。

sliceの長さと容量

  • 長さ(lemgth):スライスに含まれる要素数、len関数で取得可能
  • 容量(capacity):スライスの最初の要素から数えた元となる配列の要素数、cap関数で取得可能

make関数:スライスの新規作成

  • 組み込みのmake関数を利用してスライスを作成できる
  • 型 、長さ、(+容量)を引数にとる。

多次元スライス

  • スライスは任意の型を含むことができる
  • スライス型を要素とするスライスを作れる

append関数:スライスへの要素の追加

  • s = append(s:追加先slices,追加要素群)で要素の追加ができる。
  • 元のスライスに要素を追加したスライスを戻り値とする。
  • 追加した結果容量が足りなかった場合は容量を増やした新しい配列を割り当てる。

実験のためarray.goを作成

array_EX.go
package main

import "fmt"

func arrayTest() {
	fmt.Println("配列の宣言と切り出し")
	// int型の要素を持つ長さ10の配列の宣言
	var a1 [10]int
	a1[0] = 0
	a1[1] = 1
	a2 := [6]int{2, 3, 5, 7, 11, 13}

	var s1 []int
	s1 = a2[1:4] //配列添字1,2,3を切り出し
	fmt.Printf("s1:%d\n\n", s1)

	fmt.Println("切り出し方法色々")
	//以下等価
	s2 := a2[0:6]
	fmt.Printf("s2:%d\n", s2)
	s3 := a2[:6]
	fmt.Printf("s3:%d\n", s3)
	s4 := a2[0:]
	fmt.Printf("s4:%d\n", s4)
	s5 := a2[:]
	fmt.Printf("s5:%d\n\n", s5)

	//リテラル [3]int{0,1,2}というarrayを生成してs6というsliceはそれを参照する
	fmt.Println("リテラルの確認")
	s6 := []int{0, 1, 2}
	fmt.Printf("s6:%d\n\n", s6)

	//長さと容量の確認
	fmt.Println("長さと容量の確認")
	fmt.Printf("len(s2):%d cap(s2):%d\n", len(s1), cap(s1))
	s1 = a2[1:5]
	fmt.Printf("len(s2):%d cap(s2):%d\n\n", len(s1), cap(s1))

	fmt.Println("make関数の実験")
	// 型と長さを引数にとる
	s7 := make([]int, 2)
	// 容量も引数に渡すことができる
	s8 := make([]int, 0, 3)
	fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7), cap(s7))
	fmt.Printf("len(s8):%d cap(s8):%d\n\n", len(s8), cap(s8))

	fmt.Println("配列とスライスのメモリが共有されているかの確認")
	//s2[1]を変えると同じ参照のa2,s1も変わる
	s2[1] = 99
	fmt.Printf("a2:%d\n", a2)
	fmt.Printf("s1:%d\n", s1)
	fmt.Printf("s2:%d\n\n", s2)

	//多次元スライスを作成
	fmt.Println("多次元スライスの生成")
	board := [][]int{
		[]int{1, 2, 3},
		[]int{4, 5, 6},
		[]int{7, 8, 9},
	}
	fmt.Printf("a2:%d\n\n", board)

	//appendの確認
	fmt.Println("appendの仕様確認")
	a3 := [5]int{0, 1, 2, 0}
	s9 := a3[0:3]
	//それぞれの中身と割り当てられているアドレスと容量を確認する。
	fmt.Printf("a3:%d\n", a3)
	fmt.Printf("s9:%d\n", s9)
	fmt.Printf("a3_address:%p s9_address:%p\n", &a3, &s9)
	fmt.Printf("len(s9):%d cap(s9):%d\n\n", len(s9), cap(s9))

	//既に元配列に要素がある場所にappendしてみる
	s9 = append(s9, 3)
	fmt.Printf("a3:%d\n", a3)
	fmt.Printf("s9:%d\n", s9)
	fmt.Printf("a3_address:%p s9_address:%p\n", &a3, &s9)
	fmt.Printf("len(s9):%d cap(s9):%d\n\n", len(s9), cap(s9))

	//元配列の容量を超えて追加してみる
	s9 = append(s9, 4, 5)
	fmt.Printf("a3:%d\n", a3)
	fmt.Printf("s9:%d\n", s9)
	fmt.Printf("a3_address:%p s9_address:%p\n", &a3, &s9)
	fmt.Printf("len(s9):%d cap(s9):%d\n\n", len(s9), cap(s9))

	//スライスの要素を書き換えてみる
	s9[0] = 99
	fmt.Printf("a3:%d\n", a3)
	fmt.Printf("s9:%d\n", s9)

}
main.go
package main

func main() {
	//省略
	arrayTest()
}

実行する。

$ go run *.go
配列の宣言と切り出し
s1:[3 5 7]

切り出し方法色々
s2:[2 3 5 7 11 13]
s3:[2 3 5 7 11 13]
s4:[2 3 5 7 11 13]
s5:[2 3 5 7 11 13]

リテラルの確認
s6:[0 1 2]

長さと容量の確認
len(s2):3 cap(s2):5
len(s2):4 cap(s2):5

make関数の実験
len(s7):2 cap(s7):2
len(s8):0 cap(s8):3

配列とスライスのメモリが共有されているかの確認
a2:[2 99 5 7 11 13]
s1:[99 5 7 11]
s2:[2 99 5 7 11 13]

多次元スライスの生成
a2:[[1 2 3] [4 5 6] [7 8 9]]

appendの仕様確認
a3:[0 1 2 0 0]
s9:[0 1 2]
a3_address:0x14000132060 s9_address:0x1400011e138
len(s9):3 cap(s9):5

a3:[0 1 2 3 0]
s9:[0 1 2 3]
a3_address:0x14000132060 s9_address:0x1400011e138
len(s9):4 cap(s9):5

a3:[0 1 2 3 0]
s9:[0 1 2 3 4 5]
a3_address:0x14000132060 s9_address:0x1400011e138
len(s9):6 cap(s9):10

a3:[0 1 2 3 0]
s9:[99 1 2 3 4 5]

最後の実験より、スライスへの要素を追加時に容量が足りない場合新たに割り当てられるスライスは元の配列とは違う配列の部分列となっている

map

  • キーと値の組みでできている。
  • ゼロ値はnil
  • mapへの問い合わせの戻り値は二つ (値、存在するかどうか)で帰ってくる。
map_EX.go
package main

import (
	"fmt"
)

func mapTest() {
	var m map[string]int
	m = make(map[string]int)
	m["likes"] = 10
	m["stocks"] = 5

	// リテラル
	m = map[string]int{
		"likes":  10,
		"stocks": 5,
	}

	// 追加・更新
	m["foo"] = 1

	// 取得
	likes := m["likes"]
	fmt.Printf("%d\n", likes)

	// 削除
	delete(m, "likes")

	// 存在確認、二つ目の戻り値は、値が存在すればtrue、存在しなければfalse
	stocks, ok := m["stocks"]
	if ok {
		fmt.Printf("%d\n", stocks)
	}
}
main.go
package main

func main() {
	mapTest()
}

実行結果

% go run *.go 
10
5

range

  • mapやslicesなどを対象にループ処理をしたい時などに使う
  • (index,value)の返り値を返す。
  • 返り値は_で捨てたり、省略することができる。
range.go
package main

import "fmt"

func rangeTest() {
	var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

	// ループごとにindexとvalueを返してくれる
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}

	// 不要な場合はアンダースコアで捨てることができる
	for i, _ := range pow {
		fmt.Print(i)
	}
    fmt.Println()
	for _, value := range pow {
		fmt.Print(value)
	}
    fmt.Println()
	// indexだけが欲しい場合は2つ目を省略しても良い
	for i := range pow {
		fmt.Print(i)
	}
}
main.go
package main

func main() {
	rangeTest()
}

実行する

go run *.go 
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
01234567
1248163264128
01234567%    

続く

これでGoの基本的な文法は押さえれました!簡単!少ない!
けど、まだGoのチュートリアル的には残っている項目もあるので、次の記事で記載します!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1