LoginSignup
29
21

More than 3 years have passed since last update.

Golang入門:AtCoderなどの競技プログラミング問題を解き始めるための基本構文+その他おまけTips

Last updated at Posted at 2019-12-16

最近ちょっとGolangを入門しました。

アウトプットも兼ねて、ひとまず深いところは置いといて、
これが分かれば「AtCoder過去問精選10問などを調べながら取り組める」を目標に作成してみました。
サンプルコードを並べただけだったりしますが、参考になればと思います。

環境構築

環境構築については、以前書いた記事があるので、そちらを参照してください。
Golang環境構築手順メモ

環境構築が面倒な場合はオンラインで実行できる環境が公式で提供されています。(標準入力はできないっぽい?)
The Go Playground

AtCoderでもコードのテスト実行ができます。(要ログイン)
AtCoder Beginners Selection - コードテスト

Dockerスキーな方にはOfficial Imagesが用意されています。
golang - Docker Hub

※以降はLinuxで環境構築した前提で説明します。

HelloWorld

プログラミング言語の入門と言えばHelloWorldです。
このハンズオンでもHelloWorldから始めたいと思います。

hello.go
package main

import (
    "fmt"
)

// Golangではmainパッケージのmain関数が最初に実行されます。
func main() {
    fmt.Println("Hello World")
}

ビルド・実行

Golangはコンパイル言語です。go buildでコンパイルすることができます。
先ほどのHelloWorldのコードをビルドして実行してみます。

go_buildして実行
$ ls
hello.go
$ go build hello.go
$ ls
hello  hello.go
$ ./hello
Hello World

go runコマンドを使用すれば、スクリプト言語感覚での実行もできます。

go_runで実行
$ go run hello.go
Hello World

パッケージ

packageでソースファイルが所属するパッケージを必ず宣言します。

パッケージ
package main

参考にした本に記載されていた、Golangでのパッケージの特徴を列挙します。 (良く分からない場合は後回しで大丈夫です。)
・プログラムはmainパッケージのmain関数から開始される。
・Golangのプログラムは1つ以上のパッケージで構成される。
・パッケージは1つ以上のソースファイルで構成される。
・同じパッケージのソースファイルはすべて同じディレクトリに格納する。
・1つのディレクトリに複数のパッケージを配置できない。
・mainパッケージを除き、パッケージ名と格納するディレクトリ名は同じにする(必須ではなく慣習)。

インポート

別のパッケージに含まれる関数などを使用する場合はimportを使用します。

インポート1
// 一つずつ指定
import "fmt"
import "strings"
インポート2
// ひとまとめに指定
import (
    "fmt"
    "strings"
)

HelloWorldではfmtパッケージに含まれるPrintlnを使用しています。
Golangでは様々なパッケージが用意されています。
Go - Packages
パッケージ - Go 言語
Golang.jp - パッケージドキュメント (古め)

コメント

//(行コメント)または/* */(ブロックコメント)でコメントします。
ドキュメントによると、ブロックコメントはパッケージコメントで使用して、普段は行コメントがおすすめらしいです。

Line comments are the norm; block comments appear mostly as package comments,
but are useful within an expression or to disable large swaths of code.

グローバル変数・ローカル変数

変数は宣言する場所によってグローバル変数とローカル変数に区別されます。
・グローバル変数:ソースのトップレベルで宣言
・ローカル変数:関数内で宣言

globallocal.go
package main

import (
    "fmt"
)

var global int    // グローバル変数

func main() {
    var local int    // ローカル変数
    fmt.Println(global)
    fmt.Println(local)
}
実行結果
$ go run globallocal.go
0
0

ここでは出力が0になっています。
Golangではゼロ値という値があり、変数の初期化時に値を入れなかった場合はゼロ値が入ります。
このゼロ値については後続の変数・定数、型でも再度触れます。

変数・定数

変数はvar、定数はconstで宣言します。
変数名の先頭文字によって異なるパッケージでインポートして使用できるできないが変わります。
・先頭が小文字:使用できない
・先頭を大文字:使用できる

var01.go
package main

import (
    "fmt"
)

var a int     // 初期値の指定なし
var b int = 1 // 初期値の指定あり
var c = 2     // 型を省略

// まとめ宣言
var (
    d int
    e int = 3
    f     = 4
)

// 同じ型の場合は「,」区切りで複数指定可能
var a1, a2 int
var b1, b2 int = 5, 6
var c1, c2 = 7, 8

func main() {
    aa := 9 // ローカル変数でのみで可能
    bb, cc := 10, 11

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
    fmt.Println(e)
    fmt.Println(f)
    fmt.Println(a1, a2)
    fmt.Println(b1, b2)
    fmt.Println(c1, c2)
    fmt.Println(aa)
    fmt.Println(bb, cc)
}
実行結果
$ go run var01.go
0
1
2
0
3
4
0 0
5 6
7 8
9
10 11

初期値の指定なしで変数を宣言した場合、
型に応じてゼロ値という初期値(intの場合は「0」)が設定されます。

定数の場合は値指定が必須です。

const01.go
package main

import (
    "fmt"
)

const a int = 1 // 初期値の指定あり
const b = 2     // 型を省略

// まとめ宣言
const (
    c int = 3
    d     = 4
)

// 同じ型の場合は「,」区切りで複数指定可能
const a1, a2 int = 5, 6
const b1, b2 = 7, 8

func main() {
    aa := 9 // ローカル変数でのみで可能
    bb, cc := 10, 11

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
    fmt.Println(a1, a2)
    fmt.Println(b1, b2)
    fmt.Println(aa)
    fmt.Println(bb, cc)
}
実行結果
$ go run const01.go
1
2
3
4
5 6
7 8
9
10 11

演算子

演算子を種類別に列挙します。
必要に応じて参照してください。

算術演算子

算術演算子
+                         整数浮動小数点複素数文字列
-                         整数浮動小数点複素数
*                         整数浮動小数点複素数
/                         整数浮動小数点複素数
%    剰余                   整数

&    ビット演算 and          整数
|    ビット演算 or           整数
^    ビット演算 xor          整数
&^   ビットクリア(and not)   整数

<<   左シフト                整数 << 符号なし整数
>>   右シフト                整数 >> 符号なし整数

比較演算子

比較演算子
==    等しい
!=    等しくない
<     小なり
<=    小なりイコール
>     大なり
>=    大なりイコール

論理演算子

論理演算子
&    and条件
||   or条件
!    否定

優先順位

優先順位
    * / % << >> & &^
      + - | ^
      == != < <= > >=
      &&
    ||

単項演算子

単項演算子
++   インクリメント(x += 1)
--   デクリメント(x -= 1)
-    マイナス
^    ビットNOT
!    論理NOT

今回紹介した以外に、アドレス演算子や受信演算子などがあります。
詳細についてはドキュメントを参照してください。

基本の型を紹介します。
ここでは数値、文字列、論理値を紹介します。
また、型ではありませんがnilも紹介します。

数値

数値は符号付き、符号なしなどの他にもサイズによって細かく分かれています。
整数のゼロ値は0、浮動小数値のゼロ値も0です。
慣れるまでは、整数はint、浮動小数はfloat64を使うで良いと思います。

数値
int        32または64ビット(環境依存)
uint       uintと同じサイズ
uintptr    ポインタの値をそのまま格納するのに充分な大きさの符号なし整数

int8        符号あり  8ビット 整数 (-128 to 127)
int16       符号あり 16ビット 整数 (-32768 to 32767)
int32       符号あり 32ビット 整数 (-2147483648 to 2147483647)
int64       符号あり 64ビット 整数 (-9223372036854775808 to 9223372036854775807)

uint8       符号なし  8ビット 整数 (0 to 255)
uint16      符号なし 16ビット 整数 (0 to 65535)
uint32      符号なし 32ビット 整数 (0 to 4294967295)
uint64      符号なし 64ビット 整数 (0 to 18446744073709551615)

float32     IEEE-754 32ビット 浮動小数値
float64     IEEE-754 64ビット 浮動小数値

complex64   float32の実数部と虚数部を持つ複素数
complex128  float64の実数部と虚数部を持つ複素数

byte        uint8の別名(エイリアス)
rune        int32の別名(エイリアス)
number01.go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    fmt.Println("number sample")
    var a int
    var b float64
    var c float64 = 1.0
    var d float64 = 1.0

    // int, float64のゼロ値は0
    fmt.Println(a, b)
    fmt.Println(c + d) // 同じ型の数値はそのまま足し算できる

    fmt.Println(float64(a) + c + d) // 違う型は型変換が必要
    fmt.Println(reflect.TypeOf(a))  // reflect.TypeOf()で型を確認できる
    fmt.Printf("%v , %T\n", a, a)   // Printfで「%T」を使うと型の出力が可能
    fmt.Println()

    var e int
    e = 10
    fmt.Println(e)
    fmt.Println(e - 11)

    var f uint
    f = 10
    fmt.Println(f)
    fmt.Println(f - 11)

}
実行結果
$ go run number01.go
numberSample
0 0
2
2
int
0 , int

10
-1
10
18446744073709551615

文字列

文字列のゼロ値は""(空文字)です。

文字列
string   文字列型

// 例
str01 := "文字列"
str02 := `改行も
含められる`
string01.go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    fmt.Println("string sample")
    var s1 string
    s2 := "aaa"
    s3 := `bbb
bbbb
bbb`

    fmt.Printf("%v , %T\n", s1, s1)
    fmt.Printf("%v , %T\n", s2, s2)
    fmt.Printf("%v , %T\n", s3, s3)
    fmt.Println(s2 + " + " + s3) // 文字列は「+」で文字連結可能
    fmt.Println(reflect.TypeOf(s2))
    fmt.Println(reflect.TypeOf(s3))
}
実行結果
$ go run string01.go
stringSample
 , string
aaa , string
bbb
bbbb
bbb , string
aaa + bbb
bbbb
bbb
string
string

論理値

論理値のゼロ値は"false"です。

論理値
bool   論理値 trueまたはfalse
bool01.go
package main

import (
    "fmt"
    "reflect"
)

func main() {
    fmt.Println("bool sample")
    var a bool
    fmt.Printf("%v , %T\n", a, a)
    fmt.Println(reflect.TypeOf(a))
}
実行結果
$ go run bool01.go
boolSample
false , bool
bool

nil

nilは指し示す先が無いことを表す特別な値です。

※nullはありません。

配列

特定の型の値の集まりです。
配列は固定長のため、一度定義したら長さを変えることができません。

長さを変える必要がある場合は、後続で紹介するスライスを使用します。
配列ではなくスライスを使うことが多いと思います。

定義のみで値を入れなかった場合は、ゼロ値が設定されます。

array01.go
package main

import (
    "fmt"
)

func main() {
    // 配列の定義
    var a [3]int
    // 定義のみだとゼロ値が設定される
    fmt.Printf("%v , %T\n", a, a)

    // 定義と同時に値を設定
    b := [2]string{"aaa", "bbb"}

    // インデックス(0始まり)を指定して取得可能
    fmt.Println(b[1])

    // 初期値を指定すると「...」で長さを省略できる
    c := [...]int{1, 2}
    // lenで長さの取得可能
    fmt.Println(len(c))

    // インデックスを指定して値を代入
    d := [3]int{1: 1, 2: 10}
    fmt.Println(d)

    // 二重配列
    var e [3][2]int
    fmt.Printf("%v , %T\n", e, e)
    e[1][0] = 2
    e[1][1] = 3
    fmt.Println(e)

    // 二重配列を定義と同時に値を設定
    f := [3][2]int{{0, 1}, {2, 3}, {4, 5}}
    fmt.Println(f)

    x1 := [2]int{1, 2}
    x2 := [2]int{1, 2}
    fmt.Println("x1 == x2 : ", x1 == x2)
    x2[0] = 3
    fmt.Println("x1 == x2 : ", x1 == x2)

    hoge([2]int{1, 2})
    // hoge([3]int{1, 2, 3}) // これはエラーになる

}

// 配列は長さ含めて区別される。(funcについては後続で説明します。)
func hoge(a [2]int) {
    fmt.Println(a[0])
}
実行結果
$ go run array01.go
[0 0 0] , [3]int
bbb
2
[0 1 10]
[[0 0] [0 0] [0 0]] , [3][2]int
[[0 0] [2 3] [0 0]]
[[0 1] [2 3] [4 5]]
x1 == x2 :  true
x1 == x2 :  false
1

スライス

可変長の配列です。
内部的には配列を参照しています。

slice01.go
package main

import (
    "fmt"
)

func main() {
    // スライスの定義
    var a []int
    // 配列と違いゼロ値は設定されない
    fmt.Printf("%v, %T\n", a, a)

    // 定義と同時に値を設定
    b := []string{"hoge", "fuga"}
    // インデックス(0始まり)を指定して取得可能
    fmt.Println(b[0])

    // 定義と同時に値を設定
    c := []int{1, 2, 3, 4, 5}
    // lenで長さの取得可能
    fmt.Println(len(c))

    // makeでスライスの長さを指定して定義可能
    d := make([]int, 5)
    // 長さを指定するとゼロ値が設定される
    fmt.Println(d)

    // appendでaに値を追加可能
    fmt.Println(append(d, 6))
    // dは変更されない
    fmt.Println(d)
    // appendで複数の値を追加
    e := append(d, 6, 7, 8)
    fmt.Println(e)
    // インデックスを指定して値を代入
    e[2] = 3
    fmt.Println(e)

    // 二重スライス
    var f [][]int
    g := []int{1, 2}
    h := []int{3, 4, 5}
    // 二重スライスに代入。長さ関係なく代入できる
    i := append(f, g, h)
    fmt.Println(append(i, []int{6, 7, 8, 9}))

    // 比較はnilとしかできない
    x1 := []int{1, 2}
    var x2 []int
    fmt.Println("x1 == nil : ", x1 == nil)
    fmt.Println("x2 == nil : ", x2 == nil)
    // x3 := []int{1, 2}
    // fmt.Println("x1 == x3 : ", x1 == x3) // エラーになる。

    hoge([]int{1, 2})
    hoge([]int{3, 4, 5}) // 配列と違いエラーにならない

}

// スライスでは長さは区別されない。(funcについては後続で説明します。)
func hoge(a []int) {
    fmt.Println(a[0])
}
実行結果
$ go run slice01.go
[], []int
hoge
5
[0 0 0 0 0]
[0 0 0 0 0 6]
[0 0 0 0 0]
[0 0 0 0 0 6 7 8]
[0 0 3 0 0 6 7 8]
[[1 2] [3 4 5] [6 7 8 9]]
x1 == nil :  false
x2 == nil :  true
1
3

マップ

他の言語では辞書や連想配列などと呼ばれるものです。

map01.go
package main

import (
    "fmt"
    "reflect"
)

func main() {

    // マップの定義
    var a = make(map[string]int)
    fmt.Printf("a : %v , %T\n", a, a)
    fmt.Println(reflect.TypeOf(a))

    // マップの定義
    b := map[string]int{"a": 1, "b": 2}
    fmt.Printf("b : %v , %T\n", b, b)
    fmt.Println(reflect.TypeOf(b))

    // 要素への代入
    b["b"] = 10
    // 要素の参照
    fmt.Println(b["a"], b["b"])
    // 存在しない要素を指定してもエラーにならない。
    fmt.Println(b["c"])
}
実行結果
$ go run map01.go
a : map[] , map[string]int
map[string]int
b : map[a:1 b:2] , map[string]int
map[string]int
1 10
0

関数定義

関数はfuncで定義します。

関数名の先頭文字によって異なるパッケージでインポートして使用できるできないが変わります。
・先頭が小文字:使用できない
・先頭を大文字:使用できる

返却値がある場合はreturnで返却します。
Golangでは複数返却することもできます。

関数定義まとめ
func hoge() {}
func Hoge() {}
func hoge(num int) {}
func hoge(num1 int, num2 int) {}
func fuga(num1, num2 int) {}
func hoge(num int) string {}
func hoge(num1, num2 int) (string, string) {}
func hoge(s ...string) {}

以下、それぞれのサンプルコードです。

func01.go
package main

import (
    "fmt"
)

// 定義例
func hoge() {
    // 処理
    fmt.Println("hoge")
}

// 定義例
func Hoge() {
    // 処理
    fmt.Println("Hoge")
}

// 呼び出し例
func main() {
    hoge()
    Hoge()
}
実行結果
$ go run func01.go
hoge
Hoge

引数を受け取る場合は(変数名 型)で指定します。

func02.go
package main

import (
    "fmt"
)

// 定義例
func hoge(num int) {
    fmt.Println(num)
}

// 呼び出し例
func main() {
    hoge(5)
}
実行結果
$ go run func02.go
5

引数を複数受け取る場合は,区切りで指定します。
引数は型が同じ場合は型宣言を1つにまとめられます。

func03.go
package main

import (
    "fmt"
)

// 定義例
func hoge(num1 int, num2 int) {
    fmt.Println(num1 + num2)
}

// 定義例
func fuga(num1, num2 int) {
    fmt.Println(num1 - num2)
}

// 呼び出し例
func main() {
    hoge(2, 3)
    fuga(10, 5)
}
実行結果
$ go run func03.go
5
5

返却値がある場合は、返却値の型を指定します。

func04.go
package main

import (
    "fmt"
    "strings"
)

// 定義例
func hoge(num int) string {
    str := strings.Repeat("Go", num)
    return str
}

// 呼び出し例
func main() {
    str := hoge(5)
    fmt.Println(str)
}
実行結果
$ go run func04.go
GoGoGoGoGo

Golangでは複数返却値を返すことができます。複数返す場合は()で囲って指定します。

func05.go
package main

import (
    "fmt"
    "strings"
)

// 定義例
func hoge(num1, num2 int) (string, string) {
    str1 := strings.Repeat("Go", num1)
    str2 := strings.Repeat("Go", num2)
    return str1, str2
}

// 呼び出し例
func main() {
    str1, str2 := hoge(5, 10)
    fmt.Println(str1)
    fmt.Println(str2)
}
実行結果
$ go run func05.go
GoGoGoGoGo
GoGoGoGoGoGoGoGoGoGo

関数の返却値で使用しない値がある場合は_で使用しないことを明示します。

func06.go
package main

import (
    "fmt"
    "strings"
)

// 定義例
func hoge(num1, num2 int) (string, string) {
    str1 := strings.Repeat("Go", num1)
    str2 := strings.Repeat("Go", num2)
    return str1, str2
}

// 呼び出し例
func main() {
    str1, _ := hoge(5, 10)
    fmt.Println(str1)
}
実行結果
$ go run func06.go
GoGoGoGoGo

使われていない変数がある場合は、以下の様にメッセージが出力されビルドまたは実行ができません。

変数は使われていないとエラーになる
$ go build func06.go
# command-line-arguments
./func06.go:17:8: str2 declared and not used

$ go run func06.go
# command-line-arguments
./func06.go:17:8: str2 declared and not used

引数の型を...型とすると引数を可変にすることができます。

func07.go
package main

import (
    "fmt"
)

func main() {
    hoge("aa")
    hoge("bb", "cc", "dd")
}

func hoge(s ...string) {
    // forについては後続で説明。
    for i, v := range s {
        fmt.Println(i, v)
    }
}
実行結果
$ go run func07.go
0 aa
0 bb
1 cc
2 dd

条件分岐

条件分岐はifswitchがあります。

if01.go
package main

import (
    "fmt"
)

func main() {
    i := 1
    if i < 5 {
        fmt.Println("5以下")
    }
}
実行結果
$ go run if01.go
5以下

elseで条件にマッチしなかった場合の分岐ができます。

if02.go
package main

import (
    "fmt"
)

func main() {
    i := 10
    if i >= 5 {
        fmt.Println("5以上")
    } else {
        fmt.Println("5未満")
    }

}
実行結果
$ go run if02.go
5以上

else ifで、別の条件を指定できます。

if03.go
package main

import (
    "fmt"
)

func main() {
    i := 10
    if i == 5 {
        fmt.Println("5!")
    } else if i == 10 {
        fmt.Println("10!")
    } else {
        fmt.Println("5と10以外")
    }
}
実行結果
$ go run if03.go
10!

switchはマッチしたcaseの処理のみ実行されます。
マッチした次のcaseの処理も実行したい場合はfallthroughを処理の最後に付けます。

switch01.go
package main

import (
    "fmt"
)

func main() {
    num := 1
    switch num {
    case 1:
        fmt.Println("1!")
        // fallthrough // これを付けると次のcase内容も実行される
    case 2:
        fmt.Println("2!")
    case 3:
        fmt.Println("3!")
    default:
        fmt.Printf("default!")
    }
}
実行結果
$ go run switch01.go
1!
実行結果(fallthroughのコメントを外した場合)
$ go run switch01.go
1!
2!

繰り返し(ループ)

Golangでは繰り返し(ループ)処理の構文はforのみです。
他の言語と違いwhileがありません。

for01
package main

import (
    "fmt"
)

func main() {
    // 基本的なfor文
    for i := 0; i < 3; i++ {
        fmt.Println(i)
    }
    fmt.Println()

    // スライスの要素数だけループ
    for i, v := range []int{5, 6, 7} {
        fmt.Printf("i =%d, v =%d \n", i, v)
    }
    fmt.Println()

    // while文と同じ
    w := 0
    for w < 2 {
        fmt.Println(w)
        w++
    }
    fmt.Println()

    // 無限ループ
    a := 0
    for {
        a++
        if a > 5 {
            break // ループを抜ける
        } else if (a % 2) == 0 {
            continue // ループを継続(後続処理を飛ばす)
        }
        fmt.Println(a)
    }
}
実行結果
$ go run for01.go
0
1
2

i =0, v =5
i =1, v =6
i =2, v =7

0
1

1
3
5

その他おまけTips

以降の内容はおまけです。知っていると役立ちそうな内容をピックアップしてみました。
※主に競技プログラミングで。

標準入力

fmt パッケージのドキュメント

競技プログラミングをやる場合に欠かせない標準入力の仕方をいくつか紹介します。

inout.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // フォーマットを指定して取得
    // %dで整数(基数10)、%sで文字列を取得できる
    var a int
    var b string
    fmt.Scanf("%d:%s", &a, &b) // 「数値:数値」を入力で取得
    fmt.Println(a, b)

    // 空白区切りで取得(例:「数値 数値」)
    var d, e int
    fmt.Scan(&d, &e)
    fmt.Println(d, e)

    // 一行取得
    stdin := bufio.NewScanner(os.Stdin)
    stdin.Scan()
    s := stdin.Text()
    fmt.Println(s)
}
実行結果
$ go run inout.go
1:2    ←入力
1 2
3 4    ←入力
3 4
aiu eo kaki kukeko sashi    ←入力
aiu eo kaki kukeko sashi

配列、スライス、文字列の切り出し(スライス)

配列、スライス、文字列の一部分を取り出すことができるので紹介します。
スライスをスライスなんてちょっとややこしいです。

cutout01.go
package main

import (
    "fmt"
)

func main() {
    // スライスをスライス
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(s)
    fmt.Printf("ex.1 type: %T, value: %v\n", s[0:], s[0:])     // 0番目からすべて取得
    fmt.Printf("ex.2 type: %T, value: %v\n", s[:3], s[:3])     // 0番目から3番目まで取得
    fmt.Printf("ex.3 type: %T, value: %v\n\n", s[1:3], s[1:3]) // 1番目から3番目まで取得

    // 配列でもスライス
    a := [5]int{1, 2, 3, 4, 5}
    fmt.Printf("ex.4 type: %T, value: %v\n\n", a[1:3], a[1:3])

    // 文字列もスライス
    str := "aiueo"
    fmt.Printf("ex.5 type: %T, value: %v\n", str[3:], str[3:])

    // マップはできない
    // m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
    // fmt.Println(m[3:])
    // 以下のようにエラーが出る
    // # command-line-arguments
    // ./sliceslice.go:23:15: cannot slice m (type map[string]int)

}
実行結果
$ go run cutout01.go
[1 2 3 4 5]
ex.1 type: []int, value: [1 2 3 4 5]
ex.2 type: []int, value: [1 2 3]
ex.3 type: []int, value: [2 3]

ex.4 type: []int, value: [2 3]

ex.5 type: string, value: eo

数字から数値へ変換

数字と数値は加算など計算することできません。同じ型に変換する必要があります。

strconv01.go
package main

import (
    "fmt"
    "reflect"
    "strconv"
)

func main() {
    // 数字を数値に変換
    a1 := "1"
    a2, a3 := strconv.Atoi(a1)
    fmt.Println(a1, reflect.TypeOf(a1))
    fmt.Println(a2, reflect.TypeOf(a2), a3)

    // 2つめの変数(a5)にはエラー内容が入る
    // 成功した場合はnilが入る
    a4, a5 := strconv.Atoi("a")
    fmt.Println(a4, a5)

    // 数値を数字に変換
    b := 1
    b1 := strconv.Itoa(b) // 文字列に変換の場合は返却は1つ
    fmt.Println(b, reflect.TypeOf(b), b1, reflect.TypeOf(b1))

    // fmt.Println(a1 + b) // 型が違うのでエラーになる

}
実行結果
$ go run strconv01.go
1 string
1 int <nil>
0 strconv.Atoi: parsing "a": invalid syntax
1 int 1 string

文字列置換

replace01.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // bbbをXXXに置換
    s := "aaabbbcccaaabbbccc"
    s1 := strings.Replace(s, "bbb", "XXX", -1)
    fmt.Printf("%v\n%v\n", s, s1)
}
実行結果
$ go run replace01.go
aaabbbcccaaabbbccc
aaaXXXcccaaaXXXccc

文字列カウント

count01.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 文字列中の「a」の個数をカウント
    s := "aaabbbcccaaabbbccc"
    s1 := strings.Count(s, "a")
    fmt.Println(s1)
}
実行結果
$ go run count01.go
6

文字列の分割(スプリット)

split01.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 「,」区切りで分割
    s := "aaa,bbb,ccc,aaa,bbb,ccc"
    s1 := strings.Split(s, ",")
    fmt.Printf("%v\n%v\n", s, s1)
}
実行結果
$ go run split01.go
aaa,bbb,ccc,aaa,bbb,ccc
[aaa bbb ccc aaa bbb ccc]

ソート

sort01.go
package main

import (
    "fmt"
    "sort"
)

func main() {
    // intスライスを昇順にソート
    a1 := []int{1, 5, 3, 2, 4}
    sort.Ints(a1)
    fmt.Println(a1)

    // intのスライスを降順にソート(昇順にソートして反転)
    a2 := []int{1, 5, 3, 2, 4}
    sort.Sort(sort.Reverse(sort.IntSlice(a2)))
    fmt.Println(a2)

    //stringスライスを昇順にソート
    s1 := []string{"cc", "bb", "aa", "CC", "AA", "BB"}
    sort.Strings(s1)
    fmt.Println(s1)

    // stringスライスを降順にソート(昇順にソートして反転)
    s2 := []string{"cc", "bb", "aa", "CC", "AA", "BB"}
    sort.Sort(sort.Reverse(sort.StringSlice(s2)))
    fmt.Println(s2)

    // 配列のソート方法が見つからなかったので(sortに見当たらなかった)
    // スライスに変換してソートすれば良さそうです。(自信ない)
    a3 := [5]int{5, 8, 6, 9, 10}
    a4 := a3[:]
    sort.Ints(a4)
    fmt.Println(a4)
}
実行結果
$ go run sort01.go
[1 2 3 4 5]
[5 4 3 2 1]
[AA BB CC aa bb cc]
[cc bb aa CC BB AA]
[5 6 8 9 10]

スライスのコピーには注意する

スライスは配列と違い参照渡しです。別の変数にコピーしたり、関数に渡す場合に注意が必要です。
また、capという長さとは別に容量があり、appendした際にアドレスが変わる場合があるため注意が必要です。

slice02.go
package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3}
    b := a

    // メモリ上では同じ値(アドレス)を参照している
    // &で値のメモリ上でのアドレスが見れる
    fmt.Printf("a:%v, %v\n", a[0], &a[0])
    fmt.Printf("b:%v, %v\n", b[0], &b[0])

    // コピー元のaの値を変更するとbも変更される
    a[0] = 10
    fmt.Printf("a:%v, %v\n", a[0], &a[0])
    fmt.Printf("b:%v, %v\n", b[0], &b[0])
    fmt.Println()

    // スライスした場合も同じアドレスになる
    c := a[:]
    fmt.Printf("c:%v, %v\n", c[0], &c[0])
    a[0] = 20
    fmt.Printf("c:%v, %v\n", c[0], &c[0])
    fmt.Println()

    // 一部分をスライスしても同じ
    d := a[:2]
    fmt.Printf("d:%v, %v\n", d[0], &d[0])
    a[0] = 30
    fmt.Printf("d:%v, %v\n", d[0], &d[0])
    fmt.Println()

    // スライスを作成する際に第3引数でスライスの容量を指定できる。
    // 容量を超えるまでは同じアドレスを使用し、超えると新しく作り直される
    g := make([]int, 3, 3)
    fmt.Printf("g:%v, %v, %v\n", g, g[0], &g[0])
    g[0] = 1
    fmt.Printf("g:%v, %v, %v\n", g, g[0], &g[0])
    g1 := append(g, 4)
    fmt.Printf("g1:%v, %v, %v\n", g1, g1[0], &g1[0])
    fmt.Println()
    // 新しいアドレスが割り当てられたことで変更してもコピー元には影響しない
    g1[1] = 10
    fmt.Printf("g:%v, %v, %v\n", g, g[1], &g[0])
    fmt.Printf("g1:%v, %v, %v\n", g1, g1[1], &g1[0])
    fmt.Println()

    // capを多めにとった場合
    h := make([]int, 3, 5)
    fmt.Printf("h:%v, %v, %v\n", h, h[0], &h[0])
    h[0] = 1
    fmt.Printf("h:%v, %v, %v\n", h, h[0], &h[0])
    h = append(h, 4)
    fmt.Printf("h:%v, %v, %v\n", h, h[0], &h[0])
    fmt.Println()

    // 関数に渡す際もメモリ上のアドレスを渡している
    z := cp(a)
    fmt.Println("z:", &z[0])
    fmt.Println("z:", z[0])

}

func cp(s []int) []int {
    fmt.Println("s:", &s[0])
    return s
}
実行結果
$ go run slice02.go
a:1, 0xc00006c140
b:1, 0xc00006c140
a:10, 0xc00006c140
b:10, 0xc00006c140

c:10, 0xc00006c140
c:20, 0xc00006c140

d:20, 0xc00006c140
d:30, 0xc00006c140

g:[0 0 0], 0, 0xc00006c180
g:[1 0 0], 1, 0xc00006c180
g1:[1 0 0 4], 1, 0xc000098030

g:[1 0 0], 0, 0xc00006c180
g1:[1 10 0 4], 10, 0xc000098030

h:[0 0 0], 0, 0xc000098060
h:[1 0 0], 1, 0xc000098060
h:[1 0 0 4], 1, 0xc000098060

s: 0xc00006c140
z: 0xc00006c140
z: 30

パッケージ作成する場合の注意

パッケージを作成しインポートして使用する場合はgo envコマンドで表示される、
GOROOTまたはGOPATHの配下に配置していないとエラーになります。

こんなファイル構成の時
ptest
|-hoge.go
|-pp
  |-sample.go
hoge.go
package main

import (
    "fmt"
    "ptest/pp"
)

func main() {
    fmt.Println(pp.A)
    pp.Ptest()
    fmt.Println("go")
}
pp/sample.go
package pp

import "fmt"

const A = 10

func Ptest() {
    fmt.Println("Ptest", A)
}
GOROOTまたはGOPATHに配置していないとエラー例
$ go run hoge.go
hoge.go:5:2: cannot find package "ptest/pp" in any of:
        c/go/src/ptest/pp (from $GOROOT)
        c/home/vagrant/go/src/ptest/pp (from $GOPATH)

ちなみに、シンボリックリンクの作成でもエラーを回避できます。
1つのGitリポジトリで雑多なコードを管理している場合に便利です。Golang的に良い悪いはわかりませんが・・。

シンボリックリンク作成例(Linux)。work_spaceリポジトリのgolangディレクトリ配下にあるptestを実行できるようにする
$ cd $(go env GOPATH)/src
$ ln -s ~/work_space/golang/ptest/
$ cd ~/work_space/golang/ptest/
$ go run hoge.go
10
Ptest 10
vim-go

追記:Go Modulesを使うのが良さそう。
https://github.com/golang/go/wiki/Modules

無名関数

Golangには無名関数も存在します。
サンプルコードなどで出てくる場合があるので簡単にですが紹介します。

nameless01.go
package main

import (
    "fmt"
    "strings"
    "time"
)

func main() {

    // 即時実行
    func() {
        fmt.Print("nameless ")
    }()

    // 変数に入れて実行できる
    nameless1 := func() {
        fmt.Println("function")
    }
    nameless1()

    // 引数、返却値も通常の関数と同様に使用できる
    nameless2 := func(a, b int) (string, string) {
        aStr := strings.Repeat("GO", a)
        bStr := strings.Repeat("go", b)
        return aStr, bStr
    }
    aa, bb := nameless2(3, 5)
    fmt.Println(aa, bb)

    // 並行処理のサンプルでよく見かける気がする
    a := 5
    go func(a int) {
        for i := 0; i < 5; i++ {
            fmt.Println("hoge", i)
            time.Sleep(1 * time.Second)
        }
    }(a)
    time.Sleep(time.Duration(a) * time.Second)
}

実行結果
$ go run nameless01.go
nameless function
GOGOGO gogogogogo
hoge 0
hoge 1
hoge 2
hoge 3
hoge 4

次の一歩

A Tour of Go」というチュートリアルがGolangでは用意されています。
古めですが「はじめてのGo言語」もわかりやすくお勧めです。

Golang完全に理解したという方はソースコードを読んでみるのも良いかもしれません。
Golangをインストールした場所のsrcに保存されています。
自分の環境では以下に保存されていました。
・Windowsの場合C:\Go\src
・Linuxの場合/usr/local/go/src

おわり

触れられなかった内容や説明が足りていない部分も多々ありますが、今回は以上です。
次の一歩で紹介したチュートリアルやAtCoder、他のオンラインジャッジなどをやってコーディング力を伸ばしていきましょう。

参考

The Go programming Language
golang.jp
Go言語.com
The Go Blog 日本語訳
Go CodeReviewComments 日本語翻訳 #golang
プログラミング経験者がGo言語を本格的に勉強する前に読むための本
逆引きGolang
はじめてのGo言語
Go言語で幸せになれる10のテクニック
Go 言語(Golang) はまりどころと解決策
Go初心者が気を付けること
Effective Go

29
21
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
29
21