目次
1.Go言語とは?
- Googleが2009年に公開した静的型付け言語
- 文法がシンプル
- 並行処理が得意
- 実行速度が早い
公式サービス
Go Playground
ブラウザ上でGo言語のソースコードを実行する環境を提供している
また、ソースコードのシェアやフォーマットが可能。
A Tour of Go
チュートリアル形式でGo言語の一通りの文法を学ぶことができる
コンパイル・実行方法
# コンパイル
go build hello.go
# exeファイルが作成されるので、あとはそれを叩けばよい
# ビルドと実行を同時に実施する
go run hello.go
2.基本文法
基本ルール
- 文末にセミコロンを付ける必要がない(付けても良い)
- セミコロンで文を区切れば1行に複数の処理を記載できる
- ダブルクォートで括ると通常の文字列、バッククォートで括る とraw 文字列
- 処理のブロックは波括弧({})で括る
- クラスのメンバアクセスはドットで行う
- 大文字と小文字は区別される
- 識別子に使えるのは文字、数値、アンダースコアで、数値以外から始める(2バイト文字も使用可)
- 関数定義は「func 関数名(引数...)」、戻り値の指定は「return 式」
- プログラムはmainパッケージのmain関数から開始される
### 変数
```golang
//「var 変数名 型名」で変数宣言できる
var a int //初期値の指定なし
var b int = 1 //初期値を指定
var c = 0 //型を省略
//複数の変数宣言1
var(
d int
e int = 1
f = 0
)
//複数の変数宣言2
var a1,a2 int
var b1,b2 int = 0,1
var c1,c2 =0,1
変数(スコープによる違い)
- ローカルスコープ...関数内で宣言
- グローバルスコープ...ソースのトップレベルで宣言
//ローカルスコープの変数に限り、varを省略して
//変数名 := 式の形式で宣言できる
//この際、変数の型は指定できない
func main(){
a := 1
b,c := 2,3
var d int = 4 //varも使用可能
}
定数
- constで宣言することが可能
func main(){
const c1 string = "hoge"
const (
c2 = 1
c3 = c1
)
fmt.Println(c1,c2,c3) //hoge 1 hoge
//iotaを使う定数
//iotaを使うと連続したものを生成することができる
const (
c4 = iota
c5 = iota
c6
)
const (
c7 = iota
c8 = 0
c9 = iota
c10
)
fmt.Println(c4, c5, c6) //0 1 2
fmt.Println(c7, c8, c9, c10) //0 0 2 3
}
コメント
//単行コメント
//コメント
//複数行コメント
/*
コメント1
コメント2
コメント3
*/
パッケージ
####パッケージのルール
- プログラムのグループ分けする機能で、
このパッケージのグループを「名前空間」と呼ぶ - パッケージは一つ以上のソースファイルで構成される
- 同じパッケージに属するソースファイルは全て同一のディレクトリに格納 する
- 一つのディレクトリに複数のパッケージを配置することはでき ない
- mainパッケージを除き、パッケージ名と格納ディレクトリ名 は同じにする(これは必須ではなく慣例)
パッケージのインポート
//他のパッケージのインポート
import "p1"
//複数のインポート
import (
"p2"
"p3"
"p4"
)
パッケージのエクスポート
関数の最初の文字を大文字にするか、小文字にするかで挙動が変わる
小文字の場合、異なるパッケージに属するソースファイルからは参照できない
大文字の場合、異なるパッケージ外からも使用できる
package p1
//この関数はp1パッケージ以外からは使用できない
func fnc1(){
//略
}
//この関数はp1パッケージ以外から使用可能
func Fnc2(){
//略
}
3.演算子とデータ型
演算子
- 演算子は大きく、「演算」「比較」「論理」「単項」に分類される
- 三項演算子(条件演算子)はない
算術演算子
演算子 | 説明 |
---|---|
+ | 加算 |
- | 減算 |
* | 乗算 |
/ | 除算 |
% | 余剰 |
& | ビットAND |
| | ビットOR |
^ | ビットXOR |
&^ | ビットAND NOT |
<< | 左シフト |
>> | 右シフト |
比較演算子
演算子 | 説明 |
---|---|
== | 等しい |
!= | 等しくない |
< | 未満 |
<= | 以下 |
> | より大きい |
>= | 以上 |
論理演算子
演算子 | 説明 |
---|---|
&& | AND |
|| | OR |
単項演算子
演算子 | 説明 |
---|---|
++ | インクリメント |
-- | デクリメント |
- | マイナス |
^ | ビットNOT |
! | 論理NOT |
データ型
- 「基本形」と「複合型」に分けられる
基本型
単体で意味を成すデータ型
- 数値型
- 文字列型
- 真偽値型
複合型
他の型と組み合わせて使用するデータ型
型宣言
型に別名をつけて新しい型を作成すること
「type 新しい型名 元の型」で宣言できる
//int型をベースにした型宣言
type MyInt int
//複数の宣言も可能
type (
MyInt 2 int
MyInt 3 int
)
//宣言した型の利用
var n1 MyInt = 1
var n2 MyInt = 2
var n int = 10
func main() {
fmt.Println(n1 + n2) // 3
//fmt.Println(n1 + n) 型が異なるため演算できない
}
数値型
以下の3種類がある。
- 整数型
- 浮動小数点型
- 複素数型
整数型
型 | バイト数 | 説明 |
---|---|---|
int | 4 or 8※ | 符号付きの整数 |
int8 | 1 | 符号付きの整数(8ビット) |
int16 | 2 | 符号付きの整数(16ビット) |
int32 | 4 | 符号付きの整数(32ビット) |
int64 | 8 | 符号付きの整数(64ビット) |
uint | 4 or 8※ | 符号なしの整数 |
uint8 | 1 | 符号なしの整数(8ビット) |
uint16 | 2 | 符号なしの整数(16ビット) |
uint32 | 4 | 符号なしの整数(32ビット) |
uint64 | 8 | 符号なしの整数(64ビット) |
byte | 1 | uint8と同じ |
rune | 4 | int32と同じ |
uintptr | ※ | ポインタの値を符号なしの整数で格納する |
※CPUアーキテクチャに依存
ゼロ値は「0」
浮動小数点型
型 | バイト数 | 説明 |
---|---|---|
float32 | 4 | 32ビットの浮動小数点 |
float64 | 8 | 64ビットの浮動小数点 |
ゼロ値は「0.0」
複素数型
型 | バイト数 | 説明 |
---|---|---|
complex64 | 8 | float32型の実数+虚数 |
complex128 | 16 | float64型の実数+虚数 |
ゼロ値は「0.0」
文字列型
- string型
- UTF-8のバイト列を使用する
- ゼロ値は空文字("")
真偽値型
- bool型
- 値はtrue, falseのどちらか
- ゼロ値はfalse
構造体型
- 複数のデータをひとまとめに扱うために使用する
- structを使って宣言する
//構造体型の宣言
type MyStruct struct {
a string
b,c int
}
func main() {
var st MyStruct
st.a = "hoge"
st.b = 1
st.c = 2
fmt.Println(st.a, st.b + st.c) //hoge 3
}
//構造体の埋め込み
type MyStruct2 struct {
MyStruct
d int
}
func main() {
var st2 MyStruct2
st.a = "hoge" //埋め込んだ構造体のメンバアクセス
st.d = 10
fmt.Println(st.a, st.d)
}
//構造体メンバのタグ付け
//reflectパッケージの機能を使って参照できる
import (
"fmt"
"reflect"
)
type MyStruct struct {
a string `tag1:"value1" tag2:"value2"`
b int `tag3:"value3"`
}
func main() {
var st MyStruct
field1 := reflect.TypeOf(st).Field(0)
field2 := reflect.TypeOf(st).Field(1)
fmt.Println(field1.Tag.Get("tag1")) //value1
fmt.Println(field2.Tag.Get("tag3")) //value3
}
ポインタ型
- ポインタを扱うための型
- 変数宣言の際に型名の前に「*」をつける
- 変数に「&」をつけると変数のアドレスを参照することができる
- アドレスから変数の値を参照するには「*」を変数につける
func main(){
//int型のポインタ変数
var p *int
//int型の変数
n := 10
//変数nのアドレスを取得
p = &n
fmt.Println(p) //0x116d006c
fmt.Println(*p) //10
}
また、new関数を使って、動的にメモリを確保することが可能。
func main(){
//型を指定してメモリを割り当てる
var p *int = new(int)
fmt.Println(p) //0x116da0bc
fmt.Println(*p) //0
}
配列型
- 「変数名 [データ長]型名」で定義する
- 初期値を指定する場合は、配列リテラルを使用する(配列リテラルは{}の中に、,で区切った値を記述する)
func main() {
//配列の定義
var a [3]int
b := [2]string{"hoge", "fuga"}
//初期値を指定すると「...」表記で長さを省略できる
c := [...]int{1,2}
//インデックスを指定して値を割り当てることも可能
d := [3]int{ 1:1, 2:10 }
fmt.Println(a[0]) //0
fmt.Println(b[1]) //fuga
fmt.Println(d[0], d[1], d[2]) //0 1 10
//データ長はlen関数で取得可能
fmt.Println(len(a)) //3
fmt.Println(len(c)) //2
}
スライス型
- 可変長な配列
func main(){
//スライスの定義
var a []int //nilが設定される
b := []string{"hoge", "fuga"}
c := []int {1, 2}
fmt.Println(a) //[]
fmt.Println(b[0]) //hoge
fmt.Println(c[1]) //2
}
マップ型
- 連想配列やハッシュ、辞書に相当する型
- 「map[キーの型名]要素の型名」で定義する
//マップの定義
m := map[string]int{"a":1, "b":2}
//要素への代入
m["b"] = 10
//要素の参照
fmt.Println(m["a"], m["b"]) //1 10
そのほかの型
- チャネル型
- 関数型
- インタフェース型
が存在。(後述記載)
リテラル
以下のリテラルが使用可能
リテラル名 | 記述例 | 備考 |
---|---|---|
整数リテラル | 1111,01234,0xabcd | 10進数、8進数、16進数 |
浮動小数点リテラル | 1.1,1.2e+3 | 符指数表記可 |
虚数リテラル | 1.1i | |
ルーンリテラル | 'あ' | Unicodeコードポイントの数値を表す |
文字列リテラル | "あいうえお" | エスケープシーケンス利用可 |
row文字列リテラル | かきくけこさしすせそ |
複数行の文字列を表記可 |
構造体リテラル | struct{a int}{a: 0} | |
配列リテラル | [2]int{0,1,2} | |
スライスリテラル | []int{0,1,2} | |
マップリテラル | map[string]int{"a":0} | |
関数リテラル | func(a int) int {return 1} |
nil
指し示す先が無いことを表すための特別な値
型キャスト
- 「変換後の型(式)」の形式で行う
var a int = 1
var b int16 = 2
var c int = a + int(b)
4.制御文
条件分岐
if文
if a > 1 {
b = "hoge"
} else if a == 1 {
b = "fuga"
} else {
b = "boo"
}
//if文のスコープとなる変数が宣言可能
if s := "abc"; a > 0 {
fmt.Println(s) //abc
}
switch文
- 他言語と違って、break文が必要ない
- 他のケースに移らせるためには「fallthrough」を使う
switch i := 0; a {
case 1:
fmt.Println(i + 1) //1
fallthrough
case 2:
fmt.Println(i + 1) //2
case 3, 4:
fmt.Println(i + 1) //実行されない
default:
fmt.Println(i) //実行されない
}
このような書き方もできる
switch {
case a == 0:
fmt.Println(0)
case a == 1:
fmt.Println(1)
case a == 1:
fmt.Println(2)
default:
fmt.Println(999)
}
繰り返し文
for文
//基本的なfor文
for i:=0;i<3;i++ {
fmt.Println(i) //0,1,2
}
//いわゆるforeach文
for i, t := range []int{5,6,7}{
fmt.Printf("i=%d,t=%d¥n",i,t) //i=0,t=5 i=1,t=6 i=2,t=7
}
//いわゆるwhile文
w := 0
for w < 2 {
fmt.Println(w)
w++
}
//無限ループ
for {
//略
}
//ループを抜けるには、break
//スキップするには、continue
5.関数
Go言語の関数の特徴
- 複数の戻り値を返却できる
- 戻り値に名前をつけて扱える
//ノーマルな関数
func f1(a int, b int) int {
return a + b
}
//可変長引数
func f2(s string, params ...string){
fmt.Printf("%s:", s)
for _, p:= range params {
fmt.Printf("%s", p)
}
}
//複数の戻り値
func f3() (int, string) {
return 100, "hoge"
}
//戻り値に名前を付けて扱う
func f4() (i int, t int){
i = 5
t = 2
return
}
関数型
関数を変数に代入するためのデータ型
//関数型の変数を定義
var fnc func(int, int) int
//関数の代入
fnc = add
fmt.Println(fnc(2,1)) //3
func add(a int, int) int {
return a + b
}
クロージャ
ローカル変数を参照している関数内関数
var fnc = f()
fmt.Println(fnc()) //1
fmt.Println(fnc()) //2
fmt.Println(fnc()) //3
//ローカル変数を返却する関数
func f() func()int{
l := 0
//ローカル変数を参照する関数を返却する
return func() int {
l++
return l
}
}
defer文
- それを呼び出した関数が終了する際に実行すべき処理を記述できる
- リソースの解放など、確実に行いたい処理を記述するのに適している
func main(){
f() //processing...と出力された後にfinish!と出力される
}
func f(){
defer fmt.Println("finish!")
fmt.Println("processing...")
}
パニック
- エラー処理の仕組み
- 処理を停止して関数の呼び出し元に戻る仕組み
- 発生すると順番に関数の読み出し元に戻り、main関数まで戻るとスタックトレースを出力してプログラムを終了させる
- panic(エラー値)でパニックを起こすことができる
func main(){
fmt.Println("start")
sub()
fmt.Println("end") //この行は実行されない
}
func sub(){
fmt.Println("sub start")
panic("パニック!")
fmt.Println("sub end") //この行は実行されない
}
6.メソッドとインタフェース
メソッド
特定の型に関連付けられた関数
(メソッドと関連づいている型のことを「レシーバ」と呼ぶ)
//型宣言
type Calc struct{
a,b int
}
type MyInt int
//メソッド(Calc構造体に紐づく関数)
func (p Calc) Add() int {
return p.a + p.b
}
//メソッド(MyInt型に紐づく関数)
func (m MyInt) Add(n int) MyInt {
return m + MyInt(n)
}
func main() {
p := Calc{3,2}
var m MyInt = 1
//メソッドの呼び出し
fmt.Println(p.Add()) //5
fmt.Println(m.Add(2)) //3
}
インタフェース
- メソッドの集まりを定義するためのもの
- 「type インタフェース名 interface {...}」の形式で宣言する
//インタフェースの宣言
type Human interface {
hello()
walk()
}
//構造体とメソッドの宣言
type Masaru struct {
name string
age int
}
func(m Masaru) hello(){
fmt.Printf("%s%d歳です\n", m.name, m.age)
}
func(m Masaru) walk(){
fmt.Println("トコトコ...")
}
func(m Masaru) shout(){
fmt.Println("ワーーー!!")
}
func main(){
//構造体の値をインタフェースに設定する
m := Masaru{"マサル", 20}
var h Human = m
//インタフェースから構造体のメソッドを呼び出す
h.hello() //マサル20歳です
h.walk() //トコトコ...
h.shout() //アクセスできない
}
エラーインタフェース
- エラー処理のための仕組み
- Errorメソッドでエラー内容を確認することができる
type error interface {
Error() string
}
import(
"fmt"
"os"
)
func main(){
//os.Open関数はファイルを開くための関数
//存在しないファイルを開こうとしてエラーを発生させる
_, e := os.Open("./hoge.txt")
if(e != nil){
fmt.Println(e.Error())
}
//略
}
自作関数のエラーハンドリング
import (
"errors"
"fmt"
)
func main() {
e := test()
fmt.Println(e.Error()) //errors.Newでエラーを生成する
}
func test() error {
return errors.New("errors.Newでエラーを生成する")
}
7.並行処理
ゴルーチン
- ゴルーチンとは処理の単位のこと
- 複数のゴルーチンを並行で動作させることで並行処理が実現できる
- go文で関数を呼ぶことで生成できる
- main関数は他のゴルーチンの終了を待たないため、自身(main関数のゴルーチン)が終了した時点でプログラムは終了する
//ゴルーチンの生成
import (
"fmt"
"time"
)
func main() {
fmt.Println("main実行")
fmt.Println(&n)
//ゴルーチンの生成
go sub()
//3ミリ秒スリープする
time.Sleep(time.Millisecond * 3)
}
func sub() {
fmt.Println("sub実行")
fmt.Println(&n)
//10ミリ秒スリープする
time.Sleep(time.Millisecond * 10)
fmt.Println("sub実行2") //先にmainゴルーチンが終了するためこの行は実行されない
}
チャネル
- Go言語で値を送受信するための通信経路
- ゴルーチン間でなんらかの値をやりとりするために使用する
- チャネルはmake関数で生成できる
- 用途に応じて次の3パターンの定義を行う
- chan 要素の型(送受信用のチャネル)
- chan <- 要素の型(送信用のチャネル)
- <-chan 要素の型(受信専用のチャネル)
func main() {
//チャネルの作成
ch := make(chan int)
//ゴルーチンの生成
go send(ch)
//チャネルによる値の受信
c := <-ch
fmt.Printf("[値を受信:%d]¥n", c)
}
func send(ch chan int) {
fmt.Println("[値を送信:1]")
//チャネルによる値の送信
ch <- 1
}