はじめに
A Tour of Goで軽く文法の勉強をしたので、ここに記事として少しまとめてみようと思います。
構文
型定義
JavaやC#等の静的型付け言語は
型 <変数> = ...
というように 型名から書き始めると思います。
しかし、Goでは
<変数> 型
と逆になっています。
例えば、int型の変数xを宣言する場合
- C#
int x;
- Go
x int
後ろに;
がつきません、気をつけてください
変数定義
変数を宣言する場合は、先ほどの型宣言に加えてvar
をつける必要があります。
var x int
もし具体的な値を設定しなければ、この場合xは0になります。
bool型の場合は、falseになります。
定数定義
javascriptと同じようにconst
を定義できる。
const x int = 1
関数宣言
関数は以下のように宣言します。
func add(x int, y int) int {
return x + y
}
他の関数の違いとしては、型の指定の仕方がJavaとかと違い後ろにあること、関数名の後ろ({の前)に返す値の型を指定していることが、他の関数と違うことだと感じました。
またこの関数は
func add(x ,y int) int {
return x + y
}
とも書けるし
返す値に対応する変数名をあらかじめ指定することもできます。
func add(x, y int) (add int) {
add = x + y
return
}
上のような記述をnakedreturnsと言います。
型変換
下のようにすると型を変換することができます。
var i int = 1
f := float64(i)//float64型に変換される
暗黙的な型変換
静的型付けなので、基本的には変数の方を設定しなければ、変数の宣言は出来ませんが、:=
を使えば型を推測して設定してくれます。
i:=1//int型になる
for文
for文は以下のように記述します。
for i := 1; i < 3 ; i++ {
sum += i
}
while文
Goには、while文はなく、for文で再現します。
i := 1
for ; i < 100 ; {
i+=i
}
fmt.Println(i)
下のようにもかけます
i := 1
for i<100 {
i += 1
}
fmt.Println(i)
個人的にはこっち派というか多分こっちの方がよくみる?と思います。
無限ループは下のように条件式を書きません。
for{
}
if文
ifは下のように記述します。
i := 1
if i == 1 {
fmt.Println(1)
}
if文の中に条件式を書くこともできます。
a := 1
b := 2
if v := a+b ; v == 3{
fmt.Println("yes")
} else {
}
switch文
switch文は以下のように書きます。
switch i {
case 0:
fmt.Println("0")
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
}
}
defer文
defer文で宣言すると、関数の終わりに実行するようにできます。
func main{
fmt.Println("hello")
defer fmt.Println("hi")
fmt.Println("world")
}
実行結果は下のようになります。
hello
world
hi
deferによって"hi"は関数の最後に実行されています。
deferで渡した内容はスタックされるので、最後にdeferと宣言した関数が先に呼ばれます。
このことをlast in first out
(lifo)というらしいです。
ポインタ
C言語をやってない自分はかなり理解に苦しんでいるポインタです。
下のように書きます。
i = 10
p := &i
fmt.Println(*p)//10
fmt.Println(p)//メモリアドレスが表示される
*p = 12
fmt.Println(i)//12
構造体
あらゆる型の変数を入れることができるデータ構造の一種です。
type Person struct{
name string
age int
}
構造体のフィールドへのアクセスは以下のように行います。
p := Person{"m", 10}
fmt.Println(p.name)//name
構造体を変数に入れるときに、{}ではなく()にしないようにしましょう。
構造体の変数を変更するときは、ポインタを使わなければなりません。
type Person struct{
name string
age int
}
func main() {
p := Person{"m",10}
pp := &p
fmt.Println(*pp)//{m 10}
fmt.Println(pp.name)//m
}
配列
決まった数の列である配列を宣言するには以下のように記述します。
var a [2]int
a[0] = 1
fmt.Println(a)
//[1 0]
nums := [4]int{1,2,3,4}
スライス
可変長の配列のことです。配列の部分列であります。以下のように記述します。
nums := [4]int{1,2,3,4}//配列
nums2 := nums[1:4]//スライスに
fmt.Println(nums2)
//[2 3 4]
nums:=[]int{1,2,3,4}//配列とスライスを同時に作成
nums[0:4]
nums[0:]
nums[:4]
nums[:]
スライスの要素を変更すると元の配列の要素も変更されます
スライスの長さはlenで調べることができます。
len(s)
capで元の配列の先頭からスライスの最後の要素までの数を調べることができます。
cap(s)
以下のように以下のようにfor文で色々調べることができます。
for i, v := range nums //要素番号、要素
for i := range nums //インデックス
for i, _ := range pow//_で捨てる
make関数
make()でスライスを作ることができます。
a := make([]int,5)
map関数
キーと値を対応させたデータ構造を作成します。いわゆる辞書型ですね。
m := map[string]int{}
m["a"] = 1
fmt.Println(m["a"])//1
配列やスライスとは違うデータ構造であることに注意してください
make関数で初期容量を決めることができます。要素を追加することもできます。
m := make(map[string]int,1)
m["a"] = 1
m["b"] = 2//初期容量設定しても動く
構造体をリテラル(値)にすることもできます。
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
単純な型名ならリテラルから推測できます。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
以下のようなコードでキーと値が存在するか確認することができます。
elem, ok = m[key]
クロージャー
関数の中の関数を宣言した際に、親関数で宣言した変数等を参照できる子関数のことをクロージャーと言います。
親と子という言い方は少し正しくないかもしれません、上手い言い方が思いつきませんでした。
func increment(base int)func() int{
i := base//下のfuncはこの変数を参照できる
return func() int {
i = i+1
return i
}
}
インターフェース
ここは正直自分もまだ理解できていない内容です。
複数の型に共通するメソッドの集合を定義することができます。(Javaとかのポリモーフィズムにあたります)
type Get interface{
GetName() string
}
type Person struct{
name string
age int
}
func (p Person) GetName() string{
return p.name
}
func main(){
p := Person{"t",1}
fmt.Println(p.name)//t
var g Get = p
fmt.Println(g.GetName())//t
}
構造体のフィールドは参照できません
型アサーション
インターフェースに対して指定した型の値を取り出すことができます。
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)//hello
s, ok := i.(string)
fmt.Println(s, ok)//hello true
f, ok := i.(float64)
fmt.Println(f, ok)// 0 false
f = i.(float64) // panic
fmt.Println(f)
型スイッチ
肩によって処理を変えることができます。
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
Goroutin
Goのランタイムに管理される軽量なスレッドで並行実行できます
go f(x, y, z)
channels(チャネルズ)
チャネルオペレータの <-
を用いて値の送受信ができる通り道を作成できます。
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
チャネルが閉じているか下で確認できます。
v, ok := <-ch
select文
チャネルによって、処理を変えることができます。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
sync.Mutex
排他処理を行う時使うインターフェースです。
ゴールーチンでの処理のダブルブッキングを制限できます。
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()//他の処理が行われないようにします
c.v[key]++
c.mu.Unlock()//解除
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
最後に
書いていて説明が難しいなと感じるところがいくつかあったので、これから開発をしていきながらGoとはどういう言語なのかはっきりさせていこうと思います。