概要
A Tour of Goの順番に沿ってGoの基本で個人的に学習したことをまとめています。
No | 記事 |
---|---|
1 | 【Go】基本文法①(基礎) |
2 | 【Go】基本文法②(フロー制御文) |
3 | 【Go】基本文法③(ポインタ・構造体) |
4 | 【Go】基本文法④(配列・スライス) |
5 | 【Go】基本文法⑤(Maps・ Range) |
6 | 【Go】基本文法⑥(インターフェース) |
7 | 【Go】基本文法⑦(並行処理) |
8 | 〜〜【Go】基本文法総まとめ「今ココ」〜〜 |
パッケージ
パッケージ(package)
ポイント
- Goのプログラムは
パッケージ(package)
によって成り立っている。 - importを使用する事で必要な
パッケージ(package)
をインポートする事が可能。 - Goでは最初の文字が大文字で始まるものは、外部パッケージから参照が可能(
Exported names
)。
(例えば以下のPi
はmathパッケージからエクスポートされたもの。)
参考コード
package main
import (
"fmt"
"math"
)
//import "fmt"
//import "math" の様に個別でもimportが可能
func main() {
fmt.Println(math.Pi) //=> 3.141592653589793
}
Functions(関数)
Goでは関数は以下の様に定義される。
参考コード
func <関数名>([引数]) [戻り値の型] {
[関数の本体]
}
Functions(関数)
のポイント
- 引数の戻り値の型を書く必要がある。(ex1)
- 関数が2つ以上の同種類の引数を伴う場合、以下の様に型の省略する事が可能。(ex2)
- 関数は複数の値を返すことができる。(ex3)
参考コード
func main() {
fmt.Println(hello("Hello World")) //=> Hello World
fmt.Println(hello2("Hello", "World")) //=> Hello World
fmt.Println(multipleArgs("Hello", "World")) //=> World Hello
}
//ex1:引数の戻り値の型を書く必要がある
func hello(arg string) string{
return arg
}
//ex2:関数が2つ以上の同種類の引数を伴う際、型の省略する事が可能
func hello2(arg1, arg2 string) string {
return arg1 + " " + arg2
}
//ex3:関数は複数の値を返すことができる
func multipleArgs(arg1, arg2 string)(string, string){
return arg2, arg1
}
Variables(変数)
Variables(変数)
ポイント
-
var
によって変数を宣言し、型の明示が必要である。(ex1) - 初期値を渡した状態で変数を宣言すると型の明示を省略が可能。(ex2)
- 関数内では
:=
を利用することでより短いコードで変数の宣言を行うことが可能。(ex3)
参考コード
//ex1:`var`によって変数を宣言し、型の明示が必要である。
var lang string
//※変数に初期値を与えないとゼロ値(Zero values)が設定されます。(数値型には0、bool型にはfalse、string型には""(空の文字列)が与えられる。
//ex2:初期値を渡した状態で変数を宣言すると型の明示を省略が可能。
var lang2 = "Golang"//初期値を渡す。
func main() {
//ex3:関数内では`:=`を利用することでより短いコードで変数の宣言を行うことが可能
lang3 := "JS"
fmt.Println(lang, lang2, lang3) //=> Golang JS
}
Constants(定数)
Constants(定数)
ポイント
- 定数は、
const
キーワードを使用して宣言する。 - 定数は、
文字(character)
、文字列(string)
、boolean
、数値(numeric)
のみで使用可能。 - 定数は
:=
を使用して宣言することはできない。
参考コード
const Num = 2
func main(){
const Greetings = "Hello World"
fmt.Println(Greetings) // => Hello World
fmt.Println(Num) // => 2
}
Forループ
Forループ
ポイント
- 使用時に条件式等を囲む
()
は必要ありませんが処理を囲む{}
は必要。
参考コード
func main(){
sum := 0
for i := 0; i < 5; i++ {
sum += i
}
fmt.Println(sum) //=> 10
}
If(条件分岐)
if
ステートメントは、for
のように、条件の前に、評価するための簡単なステートメントを書くことができる。(ここで宣言された変数は、if
のスコープ内だけで有効。)
参考コード
package main
import "fmt"
func condition(arg string)string{
if v := "GO"; arg == v {
return "This is Golang"
}else{
return "This is not Golang"
}
}
func main(){
fmt.Println(condition("Swift")) //=> This is not Golang
}
Switch(条件分岐)
Switch(条件分岐)
ポイント
- 選択された
case
だけを実行してそれに続く全てのcase
やdefault
は実行されない。 -
switch
の前に何も条件を書かない場合はswitch true
と書くのと同じ。
参考コード
func main(){
fmt.Println(condition("Go")) //This is Go
}
func condition(arg string) string{
switch arg {
case "Ruby":
return "This is Ruby"
case "Go": //これ以降のcaseやdefaultは実行されない。
return "This is Go"
case "JS":
return "This is JS"
default:
return "I don't know what this is"
}
}
Defer(遅延実行)
defer
へ渡した関数の実行を呼び出し元の関数の終わり(returnする)まで遅延させる。
Defer(遅延実行)
ポイント
- deferへ渡した関数が複数ある場合、その呼び出しはスタックされ、新しいデータ→古いデータの順番で実行される。(=最初にdeferされた行が一番最後に実行される。)
参考コード
package main
import "fmt"
func main(){
defer fmt.Println("Golang") //defer1
defer fmt.Println("Ruby") //defer2
fmt.Println("JS")
//=> JS
//=> Ruby
//=> Golang
}
ポインタ
ポインタ(pointer)
ポイント
- ポインタはメモリのアドレス情報のこと。
- Goでは
&
を用いることで変数のアドレスの取得が可能。(ex1) -
*
を使用する事でポインタを値にとったポインタ変数の宣言が可能。(ex2) - ポインタ型変数名の前に
*
をつけることで変数の中身へのアクセスが可能。(ex3)
参考コード
func main(){
var lang string
lang = "Go"
//ex1:`&`を用いることで変数のアドレスの取得が可能
fmt.Println(&lang) //=> 0x1040c128
//ex2:`*`を使用する事でポインタを値にとったポインタ変数の宣言が可能
var langP *string
langP = &lang
fmt.Println(langP)//=> 0x1040c128
//ex3:ポインタ型変数名の前に`*`をつけることで変数の中身へのアクセスが可能
fmt.Println(*langP) //=> Go
}
Structs(構造体)
Structs(構造体)
ポイント
-
class
に似た役割を提供する。(関連する変数をひとまとめにする。) -
type
とstruct
を使用して定義する。(参考コード① ex1) - 複数の初期化方法が存在する。(参考コード① ex2)
- 構造体内にメソッド
method
を定義できる。(参考コード② ex3) - 継承に似た機能として構造体の埋め込みが可能(参考コード③ ex4)
参考コード①
// ex1: typeとstructを使用して定義する。
type Person struct {
name string
age int
}
func main(){
//ex2:複数の初期化方法が存在する
//初期化方法①:変数定義後にフィールドを設定する
var mike Person
mike.name = "Mike"
mike.age = 23
//初期化方法②: {} で順番にフィールドの値を渡す
var bob = Person{"Bob", 35}
//初期化方法③:フィールド名を : で指定する方法
var sam = Person{age:89, name: "Sam"}
fmt.Println(mike, bob, sam) //=> {Mike 23} {Bob 35} {Sam 89}
}
参考コード②
//ex3:構造体内にメソッドを定義できる
//普通の関数と違うのはレシーバ引数(下記の「(ele Person)」)の部分だけ。
func(ele Person)intro(arg string) string {
return arg + " I am" + " " + ele.name
}
func main(){
bob := Person{"Bob", 85}
fmt.Println(bob.intro("Hello!")) //=>Hello! I am Bob
}
参考コード③
type Person struct {
name string
age int
}
func(ele Person)intro(arg string) string { //Personのメソッド
return arg + " I am" + " " + ele.name
}
//ex4:継承に似た機能として構造体の埋め込みが可能
//構造体UserにPersonを組み込む。
type User struct {
Person
}
func(ele User)intro(arg string) string { //Userのメソッド
return "User No." + arg + " " + ele.name
}
func main(){
bob := Person{"Bob", 85}
var user1 User
//組み込みによりnameの定義が可能
user1.name = "Bob"
fmt.Println(bob.intro("Hello!")) //=> Hello! I am Bob
fmt.Println(user1.intro("1")) //=> User No.1 Bob
}
Arrays(配列)
Arrays(配列)
ポイント
- 配列とは、同じ型を持つ値(要素)を並べたもの。(ex1)
- 複数の宣言方法がある。(ex2)
- 最初に宣言した配列のサイズを変えることはできない。(ex3)
配列の基本的な宣言方法
① var 変数名 [長さ]型
② var 変数名 [長さ]型 = [大きさ]型{初期値1, 初期値n}
③ 変数名 := [...]型{初期値1, 初期値n}
参考コード
//ex1:配列とは、同じ型を持つ値(要素)を並べたもの
//ex2:複数の宣言方法がある
//宣言方法(1)
var arr1 [2]string
//宣言方法(2)
var arr2 [2]string = [2]string{"Golang", "Ruby"}
//宣言方法(3)
var arr3 = [...]string{"Golang", "Ruby"}
func main(){
arr1[0] = "Golang"
arr1[1] = "Ruby"
fmt.Println(arr1, arr2, arr3) //=> [Golang Ruby] [Golang Ruby] [Golang Ruby]
}
Slices(スライス)
Slices(スライス)
ポイント
- 配列とは異なり長さ指定の必要なし。(参考コード① ex1)
- 別の配列から要素を取り出し参照する形での宣言や
make()
を利用した宣言が可能。(参考コード① ex2) - 配列とは異なり要素の追加が可能。(参考コード① ex3)
-
長さ(length)
と容量(capacity)
の両方を持っている。(参考コード② ex4) - 型が一致している場合、他のスライスに代入することが可能。(参考コード② ex5)
- スライスのゼロ値は
nil
。(参考コード② ex6)
参考コード①
func main(){
//参照用配列
var arr[2]string = [2]string{"Ruby","Golang"}
//ex1:配列とは異なり長さ指定の必要なし
var slice1 []string //スライス(1)
var slice2 []string = []string{"Ruby", "Golang"} //スライス(2)
//ex2:配列から要素を取り出し参照する形での宣言が可能
var slice3 = arr[0:2] //スライス(3)
//ex2:make()を利用した宣言が可能
var slice4 = make([]string,2,2) //スライス(4)
//ex3:配列とは異なり要素の追加が可能
//append は新しいスライスを返すことに注意
slice5 := [] string{"JavaScript"}
newSlice := append(slice5, "Ruby") //sliceに"Ruby"を追加
fmt.Println(slice1,slice2,slice3,slice4, slice5, newSlice) //=>[] [Ruby Golang] [Ruby Golang] [ ] [JavaScript] [JavaScript Ruby]
}
参考コード②
func main(){
slice := []string{"Golang", "Ruby"}
//ex4:長さ(length)と容量(capacity)の両方を持っている。
//長さ(length) は、それに含まれる要素の数
//容量(capacity) は、スライスの最初の要素から数えて、元となる配列の要素数
fmt.Println(len(slice)) //=> 2
fmt.Println(cap(slice)) //=> 2
//ex5:型が一致している場合、他のスライスに代入することが可能。
var slice2[]string //sliceと同型slice2を作成
slice2 = slice //sliceをslice2に代入
fmt.Println(slice2) //=>[Golang Ruby]
//ex6:スライスのゼロ値は nil
var slice3 []int
fmt.Println(slice3, len(slice), cap(slice)) //=> [] 0 0
if slice3 == nil {
fmt.Println("nil!") //=> nil!
//sliceの値がnilの場合にnil!を表示する。
}
}
Maps(連想配列)
Maps(連想配列)
ポイント
-
Maps(連想配列)
はキー (key)
というデータを使って要素を指定するデータ構造である。 - 複数の宣言方法が存在する。(参考コード① ex1)
- 初期値を指定しない場合、変数は
nil(nil マップ)
に初期化される。(参考コード① ex2) - 要素の挿入や削除が行える。(参考コード② ex3)
参考コード①
func main(){
//ex1複数の宣言方法が存在する
//①組み込み関数make()を利用して宣言
//make(map[キーの型]値の型, キャパシティの初期値)
//make(map[キーの型]値の型)
map1 := make(map[string]string)
map1["Name"] = "Mike"
map1["Gender"] = "Male"
//②初期値を指定して宣言
//var 変数名 map[key]value = map[key]value{key1: value1, key2: value2, ..., keyN: valueN}
map2 := map[string]int{"Age":25,"UserId":2}
//ex2初期値を指定しない場合、変数はnil(nil マップ)に初期化される
var map3 map[string]string
fmt.Println(map1, map2, map3) //=>map[Gender:Male Name:Mike] map[Age:25 UserId:2] map[]
}
参考コード②
func main(){
//ex3:要素の挿入や削除が行える
//連想配列の作成
var mapEx = map[int]string{1:"Go",2:"Ruby"}
fmt.Println(mapEx) //=> map[2:Ruby 1:Go]
//要素の挿入や更新
mapEx[3] = "Javascript"
fmt.Println(mapEx) //=> map[1:Go 2:Ruby 3:Javascript]
//要素の取得
fmt.Println(mapEx[2]) //=> Ruby
//要素の削除
delete(mapEx,3)
fmt.Println(mapEx) //=> map[2:Ruby 1:Go]
}
Range(for
のrange
節)
Range
ポイント
- スライスやマップに使用すると反復毎に2つの変数を返す。(ex1)
- スライスの場合、1つ目の変数は
インデックス(index)
で、2つ目は要素(value)
である。(ex2) - マップの場合、1つ目の変数は
キー(key)
で、2つ目の変数はバリュー(value)
である。(ex3) - インデックスや値は、
_
へ代入することで省略することが可能。(ex4)
参考コード
//スライスとマップの作成
var slice1 = []string{"Golang", "Ruby"}
var map1 = map[string]string{"Lang1":"Golang", "Lang2":"Ruby"}
func main(){
//ex1:スライスやマップに使用すると反復毎に2つの変数を返す。
//ex2:スライスの場合、1つ目の変数は `インデックス(index)`で、`2つ目は要素(value)`である。
for index, value := range slice1{
fmt.Println(index,value)
//=> 0 Golang
//=> 1 Ruby
}
//ex3:マップの場合、1つ目の変数は`キー(key)`で、2つ目の変数は`バリュー(value)`である。
for key, value := range map1{
fmt.Println(key, value)
//=> Lang1 Golang
//=> Lang2 Ruby
}
//ex4:インデックスや値は、 _ へ代入することで省略することが可能。
for _,value := range map1{
fmt.Println(value)
//=> Golang
//=> Ruby
}
}
Interface(インターフェース)
Interface(インターフェース)
ポイント
- 任意の型が__「どのようなメソッドを実装するべきか」__を規定するためのもの。
- インターフェースの定義の内容は、単なるメソッドリストである。
- オブジェクト指向言語でいうところのポリモーフィズムと同様の機能を実現可能。
__参考コード①__ではPerson
とPerson2
という構造体があり、各構造体にintro()
メソッドが定義されており、各構造体がそれぞれintro()
を実行するためのIntroForPerson()
とIntroForPerson2()
と呼ばれるメソッドを持っている。
参考コード①
type Person struct {} //Person構造体
type Person2 struct {} //Person2構造体
//Person構造体のメソッドintro()
func (p Person) intro() string{
return "Hello World"
}
//Person2構造体のメソッドintro()
func (p Person2) intro() string{
return "Hello World"
}
//Person構造体のメソッドintro()を実行するメソッド
func IntroForPerson(arg *Person){
fmt.Println(arg.intro())
}
//Person2構造体のメソッドintro()を実行するメソッド
func IntroForPerson2(arg *Person2){
fmt.Println(arg.intro())
}
func main(){
bob := new(Person)
mike := new(Person2)
IntroForPerson(bob) //=> Hello World
IntroForPerson2(mike) //=> Hello World
}
現状ではIntroForPerson
とIntroForPerson2
の同じ動きをするメソッドが存在しており冗長である。
そこで、__参考コード②__の様に重複するメソッドをInterface(インターフェース)
に括り出す事ができる。
参考コード②
type Person struct {} //Person構造体
type Person2 struct {} //Person2構造体
type People interface{
intro()
}
func IntroForPerson(arg People) {
arg.intro();
}
//Person構造体のメソッドintro()
func (p *Person) intro() {
fmt.Println("Hello World")
}
//Person2構造体のメソッドintro()
func (p *Person2) intro() {
fmt.Println("Hello World")
}
func main(){
bob := new(Person)
mike := new(Person2)
IntroForPerson(bob) //=> Hello World
IntroForPerson(mike) //=> Hello World
}
goroutine(ゴルーチン)
goroutine(ゴルーチン)
ポイント
- Go言語のプログラムで並行に実行されるもののこと。
- 関数(またはメソッド)の呼び出しの前に
go
を付けると、異なるgoroutine
で関数を実行することが可能。(ex1) -
runtime.NumGoroutine()
を使用する事で現在起動しているgoroutine(ゴルーチン)
の数を知ることが可能。(ex2)
参考コード
package main
import(
"fmt"
"log"
"runtime"
)
func main(){
fmt.Println("Hello World")
//ex1:関数(またはメソッド)の呼び出しの前にgoを付けると、異なるgoroutineで関数を実行することが可能。
go hello()
go goodMorning()
//ex2:runtime.NumGoroutine()を使用する事で現在起動しているgoroutine(ゴルーチン)の数を知ることが可能。
log.Println(runtime.NumGoroutine())
//=> Hello World
//=> 2009/11/10 23:00:00 3
//3がgoroutineの数。
//ここではmain, hello, goodMorningの3つを指す。
}
func hello(){
fmt.Println("Hello")
}
func goodMorning(){
fmt.Println("Good Morning")
}
channel(チャネル)
channel(チャネル)
ポイント
- 組み込み関数
make()
を使用する事で生成可能。(参考コード① ex1) - チャネルオペレータの
<-
を用いる事で値の送受信が可能。(参考コード① ex2) - 送信が
channel<-value
で受信が<-channel
(参考コード① ex2+) -
channel(チャネル)
は値の交換および同期という通信機能を兼ね備えており、ゴルーチンが予期しない状態とならないことを保証する。(参考コード② ex3)
参考コード①
func main(){
//ex1:組み込み関数`make()`を使用する事で生成可能。
ch := make(chan string)
//ex2:<-を用いる事で値の送受信が可能。
//ex2+:作成したチャネルchに値を送信。
go func(){ ch <- "str"}()
//ex2+:チャネルchから値を受信
msg := <- ch
fmt.Println(msg) //=>str
}
参考コード②
func main(){
//ex3:ゴルーチンが予期しない状態とならないことを保証する。
ch := make(chan bool) //bool型のチャネルchを作成
// ゴルーチンとして以下の関数を起動。
//完了時にchannelの型であるboolの値を送信する事で、チャネルへ通知。
go func(){
fmt.Println("Hello")
ch <- true // 通知を送信。値は何でも良い(boolの型であれば)
}()
<- ch //=>Hello
// channelの型であるboolの値を受け取るまでの完了待ち。送られてきた値は破棄
}