インストール
【Mac】
Golang Macへのインストール〜Homebrewを使って〜
【Windows】
はじめての Go 言語 (on Windows)
インストールとパス通しができたら
この記事のソースコードをダウンロードして、フォルダに移動後
go run 1_hello_world.go
で実行してみましょう!
基本的なデータ型
string "hello"
int 1234
float64 3.141592
bool false true
nil 不定
デフォルト値
var s string // ""
var a int // 0
var b float64 // 0
var f bool // false
こんにちは世界
プログラムのmainパッケージ内のmain関数が、まず最初(初期化処理後)に実行されるので、main()の中に処理を書いていきます。
入出力の標準パッケージ fmt
をimportして文字列を表示
package main
import (
"fmt"
)
func main() {
// 通常の代入
var msg = "hello world"
// 短縮代入
m := "hello world!!"
fmt.Println(msg)
fmt.Println(m)
}
hello world
hello world!!
goの特徴として「使っていない変数」や「パッケージ」があった場合
コンパイルエラーが発生します。
# command-line-arguments
./1_hello_world.go:24: msg declared and not used
と怒られるので注意 ʕ◔ϖ◔ʔ
変数と定数
package main
import (
"fmt"
)
// 基本的な変数
func main() {
a := 5 // int
b := 1.35 // float
c := "hoge" // 文字列
var d bool // bool
const e = "定数" // 定数
fmt.Printf("a:%d b:%f c:%s d:%t e:%s", a,b,c,d,e)
// 定数を列挙
// 識別子 iota を使用することで0から始まる連番にできる
// 識別子 iota+1 を使用すること1から始まる連番にできる
const (
sun = iota+1
mon
tue
)
fmt.Println(sun, mon, tue)
}
a:5 b:1.350000 c:hoge d:false e:定数
1 2 3
iotaは「アイオーティーエー」ではなく「イオタ」と読むらしい
ポインタ操作
package main
import (
"fmt"
)
// ポインタ操作
func main (){
a := 5
// int 型のアドレスを宣言
var pa * int
pa = &a
// 出力
fmt.Println(*pa)
fmt.Println(pa)
}
5
0x208178170
関数
関数の構文は func 関数名(引数,型)(戻り値の型){ 処理 }
// int型のaを受け取って、int型のaをリターン
func test(a int) (int) {
return a
}
Go言語は 複数戻り値
を設定できます
package main
import (
"fmt"
)
// 関数、関数リテラル
func main() {
// hello world
a := hello("wolrd")
fmt.Println(a)
// 複数戻り値
b , c := swap(111, 222)
fmt.Println(b, c)
// 無名関数を作成し、変数に代入(関数リテラル)
tmp := func(a, b int) (int, int) {
return b , a
}
fmt.Println( tmp(44, 33) )
// 即時関数
func (msg string) {
fmt.Println(msg)
}("関数を定義して実行")
}
/**
* 引数で受け取った文字列に連結させて返す(戻り値の変数を指定)
*/
//func hello(変数名 型) (戻り値 型)
func hello(msg string) (ret string) {
ret = "hello " + msg
return
}
/**
* 引数を逆にしてまとめて返す
*/
func swap(a, b int) (int, int) {
return b , a
}
hello wolrd
222 111
33 44
関数を定義して実行
Go言語の特徴の一つでもある 複数戻り値
がでてきました
swap関数に値を渡し、複数の戻り値がbとcに代入されていることがわかります。
次の行では関数を変数に代入しています
// 無名関数を作成し、変数に代入
tmp := func(a, b int) (int, int) {
return b , a
}
上記のような例を 関数リテラルといいます。
Tips①【リテラルとは?】
リテラルとは
ソースコード内に値となる、文字列、数字、式を直接表記したもの。
一例として、変数が箱とたとえられるのであれば、その変数の中に入る値をリテラルと呼ぶ。
例)
var string = "Hello World";
var num = 10;
ここでいう代入された値の「Hello World」や「10」のことをリテラルという。
関数リテラルとは
変数に関数を代入して記述することを関数リテラルと言う。
例)
var func01 = function(){処理};
関数リテラル、無名関数、匿名関数この三つは同義語。
配列
var 変数名 [要素数]型
で配列を宣言でき、0で初期化されます
また、要素数に ...
を指定することで要素数を省略することができます
package main
import (
"fmt"
)
func main() {
// 配列を宣言(0初期化)
var a [5]int
fmt.Println(a)
// 配列を宣言して代入
b := [3]int{1,3,5}
fmt.Println(b[2])
// [...]で要素の大きさを省略可能
c := [...]int{2,4,6}
fmt.Println(len(c)) // 要素数を取得
}
[0 0 0 0 0]
5
3
スライス①
Goの特徴のひとつ
スライスは配列の部分列を簡単に取り出すことができるデータ構造
Go の配列は固定長、スライスは可変長配列のようなもの
// 公式の説明
スライスはある配列内の連続した領域への参照であり、スライスの内容はその配列の要素の並びです。スライス型は、その要素型と同じ要素型を持つ配列のすべてのスライスの集合を表します。初期化されていないスライス型の値はnilです。
a := [3]int{1,2,3} // 配列
a := [...]int{1,2,3} // 配列
a := []int{1,2,3} // スライス
package main
import (
"fmt"
)
func main() {
// 要素数をしていせず、配列を作成(コンパイラが数える)
c := [...]int{1,3,5,7,9}
// 2番目から(4-1)番目をスライス
d := c[2:4]
// [5,7]
fmt.Println(d)
fmt.Println(c)
// スライスし生成した配列に値を代入 ※元の配列の参照なのでc[4]も12となる
d[1] = 12
fmt.Println(c)
// len() 配列の長さ
// cap() 配列の先頭からきりだせる最大数
fmt.Println(d, len(d), cap(d))
}
[5 7]
[1 3 5 7 9]
[1 3 5 12 9]
[5 12] 2 3
参考リンク
http://golang.jp/go_spec#Slice_types
http://jxck.hatenablog.com/entry/golang-slice-internals
http://www.slideshare.net/yasi_life/go-14075425
スライス操作
【make】
スライスのもう一つの宣言方法。第一引数に型を、第二引数に長さ(len)を指定
【append】
要素の追加
【copy】
スライスをコピーし、コピーした要素数を返す
package main
import (
"fmt"
)
func main() {
// eスライスを作成
e := []int{1,3,5}
// スライスした配列に要素を追加(スライスのみ可能)
e = append(e, 7, 9)
fmt.Println(e)
// eの要素数の配列を作成
f := make([]int, len(e))
// eスライスをコピーし、コピーした要素数をgに代入
g := copy(f,e)
fmt.Println(f)
fmt.Println(g)
}
eと同じ大きさの fスライスを作成し、eをfにコピーしています
gにはコピーした要素数がはいるので、5が出力されます
[1 3 5 7 9]
[1 3 5 7 9]
5
参考リンク
http://jxck.hatenablog.com/entry/golang-slice-internals
http://www.slideshare.net/yasi_life/go-14075425
MAP
PHPの連想配列のような感じ。
package main
import (
"fmt"
)
// MAP (連想配列のようなもの)
func main() {
// map[キーの型]値の型
m := make(map[string]string)
m["first_name"] = "yamada"
m["last_name"] = "taro"
fmt.Println(m);
// 宣言と代入
i := map[string]string{"first_name":"yamada", "last_name":"taro"}
fmt.Println(i);
// キーを指定し、要素を削除
delete(i, "first_name")
fmt.Println(i);
// 値が存在するか
value, flg := i["yamada"]
fmt.Println(value, flg);
}
map[first_name:yamada last_name:taro]
map[first_name:yamada last_name:taro]
map[last_name:taro]
false
分岐処理 ifとswitch
package main
import(
"fmt"
)
// ifとswitch
func main() {
// ifの中でしか生存しない変数の場合、この書き方ができる
if _score := 60; _score > 80 {
fmt.Println("Great")
} else if _score > 60 {
fmt.Println("Nice")
} else {
fmt.Println("Oh...")
}
// switch
signal := "red"
switch signal {
case "red":
fmt.Println("Stop")
case "yellow":
fmt.Println("Caution")
case "green", "blue":
fmt.Println("Go!!")
default:
fmt.Println("Accident")
}
// switchにifを使う場合
score := 80
switch {
case score > 80:
fmt.Println("Great")
case score > 60:
fmt.Println("Nice")
default:
fmt.Println("Oh...")
}
}
Oh...
Stop
Nice
変数の生存範囲が確定してるのは、わかりやすいですね!
ループ処理
goにはwhileがないので、ループは基本的にforで行う
package main
import(
"fmt"
)
func main() {
// 基本構文
for i := 0; i < 3; i++ {
if i == 0 {
continue
}
fmt.Println(i)
}
// whileっぽくする
i := 0
for i < 3 {
fmt.Println(i)
i++
}
// 無限ループ
n := 0
for {
fmt.Println(n)
if n == 5 {
break
}
n++
}
}
12
012
012345
range
予約語のrangeは
「Array」「Slice」「map」などをイテレートする
package main
import(
"fmt"
)
func main() {
// スライスを作成
slice := []int{2, 3, 8}
// sliceの要素文ループ i:インデックス v:値
for i , v := range slice {
// rangeは二つの値を返す
fmt.Println(i ,v)
}
// 値を破棄したければ、ブランク修飾子「 _ 」を使用
for _ , v := range slice {
fmt.Println(v)
}
// mapの場合は key value が返る
m := map[string]string{"first_name":"Tachi", "last_name":"Hiroshi"}
for k ,v := range m {
fmt.Println(k ,v)
}
}
0 2
1 3
2 8
2
3
8
first_name Tachi
※mapをrangeでイテレーションすると、実行ごとに異なるので注意が必要。
固定されたオーダーで実行していたことで、使用者がそれを期待してコードを書いてしまうことが問題になりました。なぜかというと、この"固定されたオーダー"そのものがターゲットのCPUアーキテクチャなどによって変化する場合があったからです。これではgoの良さである移植性が殺されてしまいます。
そういうわけで、mapのイテレーションの順序を期待したコードが書かれないように、変化するように実装が変えられました。
これまた強烈な仕様w
知らないでコード書いてると爆死します!
構造体
なぜクラスではなく構造体なのかというと
goにクラスは存在しないからです
package main
import(
"fmt"
)
// 構造体の宣言
type user struct {
first_name string
last_name string
score int
}
/**
* 構造体
*/
func main() {
u := new(user) // アドレスが返る
// 代入方法1
u.first_name = "Tachi"
// 代入方法2
(*u).last_name = "Hiroshi"
// 代入方法3 フィールド順
uu := user {"Tachi", "Hiroshi", 100}
// 代入方法4 キー指定っぽく
uuu := user {first_name:"Tachi", last_name:"Hiroshi", score:100}
fmt.Println(u)
fmt.Println(uu)
fmt.Println(uuu)
}
&{Tachi Hiroshi 0}
{Tachi Hiroshi 100}
{Tachi Hiroshi 100}
メソッド
構造体へのアクセサとして使用するのがほとんどのよう。
user構造体を定義しsocoreをカウントアップするclearメソッドと
表示するshowメソッドを作成します
package main
import (
"fmt"
)
// 構造体の宣言
type user struct {
first_name string
last_name string
score int
}
func main() {
u := user{first_name:"Tachi", last_name:"Hiroshi", score:79}
u.clear()
u.show()
fmt.Println(u)
}
// メソッド
func (u *user)clear(){
u.score++
}
func (u *user)show(){
fmt.Printf("Name:%s %s Score:%d", u.first_name, u.last_name, u.score)
}
Name:Tachi Hiroshi Score:80
{Tachi Hiroshi 80}
Tips② 関数とメソッドの違いって?
調べてみた結果
・オブジェクト自身を操作する場合に使う手続きをメソッド。
・それ以外を関数(非オブジェクト指向言語)
という結論に。
インターフェイス
Go の開発者が 「Interface を制すものは Go を制す」と言い切るくらい重要らしい。
「型アサーション」「型switch」で型を判定して別の文字を表示してみます
package main
import (
"fmt"
)
// インターフェース
type greeter interface {
greet()
}
// JPN構造体とメソッド
type jpn struct {}
func (j jpn) greet() {
fmt.Println("こんにちは!")
}
// USA構造体とメソッド
type usa struct {}
func (u usa) greet() {
fmt.Println("Hello!")
}
func main() {
// greeter型でスライスを作成
greeters := []greeter{ jpn{}, usa{} }
for _, v := range greeters {
v.greet()
// 型チェックし、文字列を出力
checkInterface(v)
}
}
/*
* 型チェック
* t interface{} は空のインターフェースであり、全ての型を受け取ることができる
*/
func checkInterface(t interface{} ) {
/**
* 型アサーション
* 値, フラグ := t.(type) // t が type を満たすかを調べる
*/
_, flg := t.(jpn)
if flg {
fmt.Printf("I am ")
} else {
fmt.Printf("I'm ")
}
// 型switch
switch t.(type) {
case jpn:
fmt.Println("japanese")
case usa:
fmt.Println("american")
default:
fmt.Println("human")
}
}
こんにちは!
I am japanese
Hello!
I'm american
インターフェイスを使えばダックタイピングができます。
ゴルーチン
Go最大の特徴であるゴルーチンです。
関数を並行処理します。
使い方は超簡単で go 関数名()
だけです。
main()で task1 → task2 の順で処理を実行させます。
package main
import (
"fmt"
"time"
)
func task1() {
// 2秒間停止
time.Sleep(time.Second * 2)
fmt.Println("task1 finish")
}
func task2() {
fmt.Println("task2 finish")
}
// ゴルーチン
func main(){
// 並行処理開始
go task1()
go task2()
// task1が終了する前にmain関数が終了するため3秒まつ
time.Sleep(time.Second * 3)
}
task2 finish
task1 finish
先にtask1が実行されますが、関数内で2秒停止しているため
task2の方が先に完了しています!
Tips③ 並行処理と並列処理の違い
【並行処理】
・時分割でスレッドを処理
・複数の動作が、順不同もしくは同時に起こりうる
・実行状態を複数保てる
【並列処理】
・マルチコアで処理
・複数の動作が、同時に起こること(非同期処理)
・複数の動作を同時に出来る
単純に「行」と「列」で考えるとわかりやすいかも
参考リンク
チャネル
Channel を用いたメッセージング
Channel は参照型なので make() でインスタンスを生成
送信が channel<-value
受信が <-channel
ゴルーチンの終了判定をしたり、ゴルーチン間で値を送受信できます
package main
import (
"fmt"
"time"
)
func task1(result chan string) {
// 2秒間停止
time.Sleep(time.Second * 2)
// チャネルに値を送信
result<- "task1 finish"
}
func task2() {
fmt.Println("task2 finish")
}
func main() {
// チャネルのインスタンスを生成
result := make(chan string)
go task1(result)
go task2()
/**
* <-result チャネルの値を取り出す
* resultに値が入るまで処理がブロックされる
*/
fmt.Println(<-result)
// 即時関数を使い、ローディングっぽい表示
complete := make(chan bool)
go func() {
fmt.Printf("Now Loading")
time.Sleep(time.Second * 1)
fmt.Printf(".")
time.Sleep(time.Second * 1)
fmt.Printf(".")
time.Sleep(time.Second * 1)
fmt.Printf(".")
complete<- true
}()
<-complete // 受信した値自体は必要ないため、捨てる
fmt.Println("end")
}
実行結果
実行結果をみると、 fmt.Println(<-result)
の行で
チャネルの受信待ちが発生しているため
task1終了後に Nowloading… が行われているのがわかります。
これはおもしろい!
Webサーバーをたてる
パッケージの net/http
をインポートしてwebサーバーをたて、URLの「/」以下を表示します
package main
import(
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
// [1:] ←1文字目から最後まで表示(0文字目は/がはいる)
fmt.Fprintf(w, "Hi %s!", r.URL.Path[1:])
}
実行結果
go run 17_web_server.go
で実行後、
ブラウザでローカル( http://localhost:8080/太刀ひろし
)にアクセス
(※yosemite環境で実行する場合はここを参照してくさだい)
超簡単にwebサーバーがたちました!Goすげぇ!
最後に
学生時代はCとC++しか書いたことがなかったんですが、言語っておもしろいですね!
コツコツと基礎を学ぶしかないかな〜っと考えてた今日この頃
以上、お疲れさまでした!
0 件のコメント :
コメントを投稿