Go言語のメリット・デメリット
メリット
- 文法がシンプルでわかりやすい
- ライブラリが豊富なため簡潔に書くことができる
- 処理速度が速い
- 直接機械語に変換するためコンパイルが早い
- 並列処理を簡潔に書くことができる。
goroutine
やchannel
を利用することで大量データの並列処理が可能
- シングルバイナリ、クロスコンパイルにより環境依存を減らすことができる
デメリット
- コードの継承がない(enableにより似たような機能はある)
→詳細は以下記事を参照
Go言語で「embedded で継承ができる」と思わないほうがいいのはなぜか? - Genericsがない
→使えるようになったらしい
Goに導入されるGenericsについてまとめてみた - 例外がない(エラーは値として処理)
javaの場合以下のようにエラーを飛ばしますが
throw new Exception(...)
goでは以下のような記載になります。
// 何かの処理の戻り値にエラーが存在する場合
if err != nil {
// ログ出力で処理終了
log.Fatal(err)
}
Javaとの違い
クラスではなく構造体で処理を紐付ける
クラスで行いたい処理としては以下のことがメインとなります。
- 処理毎によるメソッドの切り分け
- 処理に必要な関数、変数、定数の定義
Go言語ではstruct
を用いて行います。
package main
import (
"fmt"
)
type Human struct {
name string
age int
}
func (h Human) SayName() string {
return h.name + "です。"
}
func main() {
h := Human{"Mike", 10}
fmt.Println(h, h.name, h.age)
fmt.Println(h.SayName())
}
{Mike 10} Mike 10
Mikeです。
上記ではHuman
というstruct
を利用してクラス変数を定義しています。
Human
にたいしてあたいを入れることにより、その変数、クラス関数を利用することができます。
継承が存在しない(似たような機能Embedded
は存在する)
goにおいて継承は存在しません。その代わりにEmbeddedは存在します。
厳密にはクラスと違う部分もあるのですが、気になる場合は下記記事を参考にしてください。
Go言語で「embedded で継承ができる」と思わないほうがいいのはなぜか?
今回はHuman
を親クラス、Japanese
を子クラスとして記述します。
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
}
type Japanese struct {
Human
weight int
}
func (h Human) SayName() string {
return h.name + "です。"
}
func (h Human) SayAge() string {
return strconv.Itoa(h.age) + "です。"
}
func (j Japanese) SayName() string {
return "初めまして" + j.name + "です。"
}
func New(name string, age int, weight int) *Japanese {
return &Japanese{Human{name, age}, weight}
}
func main() {
// 初期化
j := New("Mike", 20, 55)
// 親の関数を呼び出せていること
fmt.Println(j.SayAge())
// オーバーライドされていること
fmt.Println(j.SayName())
}
20です。
初めましてMikeです。
Human
,Japanese
にSayName関数を実装してオーバーライドされるか、親クラスの関数の呼び出しが可能か確認しました。
実行結果の通り親クラスの関数の呼び出し、オーバーライドが行えていることがわかります。
Embedded
により、オブジェクト指向の特徴である。継承、ポリモーフィズムといった機能が実装できることがわかります。
基本文法
パッケージ
Goではプログラムをpackageごとに分割しています。
importすることで他パッケージの利用もできます。
Goでは大文字で始まるものがPublic、この字で始まるものがPrivateとされるため、大文字始まりの定数や関数を呼び出すことができます。
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Println("Hello!");
}
function(ファンクション):関数
Goではファンクションに処理を書きます。Javaでいうメソッドです。
大文字であればPublic、小文字であればPrivateで利用できます。
以下のように定義されます。
func <関数名>([引数]) [戻り値の型] {
[関数の本体]
}
func (j Japanese) SayName() string {
return "初めまして" + j.name + "です。"
}
func New(name string, age int, weight int) *Japanese {
return &Japanese{Human{name, age}, weight}
}
先ほど継承の例で挙げた例を参考にすると、
SayNameメソッドはJapanese構造体に付随されており、引数はなし、戻り値はstringとされています。
Newメソッドは引数はname、age、weightとされ、Japanese構造体のポインタを返却しています。
※構造体とポインタはのちにて説明します
変数・定数
変数はvar、定数はconstにより定義します。
これに関してもPublic、Privateは大文字小文字で決まります。
宣言時は変数名、型の順で記述します。直接文字列、数値を入れる場合は型定義の必要がありません。
変数に値を代入したい場合は:=
を利用することで宣言記述を省略できます。
定数の場合は:=
を利用できません
定数で宣言できるのはstring``bool``character``numeric
のみです。
var name1 string
var name2 = "Mike"
const age1 int
const age2 = 1
func main() {
name3 := "Jessica"
}
for/if/Switch
Switch/if条件/forループは()
で囲む必要がなく記載ができる。{}
の括りは必要。
Switch文はJavaではbreakでswitch文を抜けますがGo言語ではbreakは不要です。
Switch文はfallthrough
を利用することでcase文で
func main() {
sum := 0
for i := 0;i < 10;i++ {
sum += 1;
if sum<5 {
fmt.Println("合計値が5以上")
}
}
result := judge()
}
func judge() string {
switch sum
case sum == 5:
return "合計値が正しくないよ"
case sum == 10:
return "合計値が正しいよ"
default:
return "想定値以外"
}
struct(ストラクト):構造体
Goでは構造体にそのパッケージで利用する変数を定義します。
Javaでいうクラスに当たります。継承の記述説明を行った時同様に、処理を持たせることもできます。
type 構造体名 struct {}
で宣言することができます。フィールド変数を呼び出すときは構造体名.変数名
にて利用することができます。
ストラクトの初期化には「直接変数に値を入れる」「ストラクトの記載順で値を入れる」「フィールド名を指定して値を入れる」の3パターンがあります。
type Human struct {
name string
age int
}
func (h Human) SayName() string {
return h.name + "です。"
}
func New(name string, age int) *Human {
// ストラクトの順に値を入れる
return &Human{name, age}
}
func main() {
// 初期化
h := New("Mike", 20)
// Humanストラクトの変数呼び出し
fmt.Println(h.name)
// Humanストラクトの処理呼び出し
fmt.Println(h.SayAge())
// フィールド名指定で初期化する
Alex := Human{name: "Alex", age: 10};
// ストラクトに直接値を入れる
Alex.age = 20
}
point(ポインタ):メモリアドレス
ポインタはメモリのアドレス情報のことです。
GO言語では&
を変数、構造体の前につけることでアドレスの取得(ポインタの取得)ができます。
また、*
を変数名の前に記述することでポインタ型で変数宣言が可能です。
ポインタの存在意義が分からない場合はこちらの記事を参考にしてください
Goで学ぶポインタとアドレス
type Human struct {
name string
age int
}
func main() {
// Human型で宣言
h1 := Human{"Mike", 20}
fmt.Println(h1)
// Human型の別ストラクトの宣言
h2 := h1
h2.name = "Alex"
fmt.Println(h2)
fmt.Println(h1)
// ポインタ型で宣言
h3 := &h1
h3.name = "Alex"
fmt.Println(h3)
fmt.Println(h1)
}
{Mike 20}
{Alex 20}
{Mike 20}
&{Alex 20}
{Alex 20}
Human型で宣言した場合はh2には代入できているが、h1に反映できていない。これを値渡しという。
ポインタ型で宣言した場合は大元のHuman型で宣言した値に対して代入されていることがわかる。これを参照渡しという。
配列
配列とは、同じ型を持つ値(要素)を並べたものです。
複数の宣言方法があります。
最初に宣言した配列のサイズを変えることはできません。
var 変数名 [長さ]型
var 変数名 [長さ]型 = [大きさ]型{初期値1, 初期値n}
変数名 := [...]型{初期値1, 初期値n}
// 宣言方法1
var Names1 [2]string
// 宣言方法2
var Names2 [2]string = [2]string{"Mike", "Alex"}
// 宣言方法3
var Names3 = [...]string{"Mike", "Alex"}
func main() {
// 値の取得
fmt.Println(Names2[0])
fmt.Println(Names2[1])
fmt.Println(Names1, Names2, Names3)
}
Mike
Alex
[ ] [Mike Alex] [Mike Alex]
slice(スライス):要素指定が必要ない配列
配列とは異なり長さ指定の必要がない。
別の配列から要素を取り出し参照する形での宣言やmake()を利用した宣言が可能。宣言時に値を入れることはできない。make(型,長さ,容量)
で宣言が可能です。容量は省略が可能で、省略した場合長さと同じ値とされます。
配列とは異なり要素の追加が可能です(append)。
数値の意味合いは以下のようになります。
操作 | 意味 |
---|---|
Slice[start:end] | start から end - 1 まで |
Slice[start:] | start から最後尾まで |
Slice[:end] | 先頭から end - 1 まで |
Slice[:] | 先頭から最後尾まで |
長さ(length)と容量(capacity)の両方を持っています。
型が一致している場合、他のスライスに代入することが可能です。
スライスのゼロ値はnil。
var 変数名 []型
var 変数名 = make([]型,2,2)
var arr [2]string = [2]string{"Mike", "Alex"}
var slice1 []string
var slice2 []string = []string{"Mike", "Alex", "jessica"}
// 配列からの代入
var slice3 []string = arr[0:2]
// makeを使用した宣言
// make(型,長さ,容量)スライスオブジェクトの確保、初期化を行う。容量は省略可能
var slice4 = make([]string, 2, 4)
// appendによる追加
var slice5 []string = []string{"Mike", "Alex"}
slice5 = append(slice5, "jessica")
fmt.Println(slice1, slice2, slice3, slice4, slice5)
fmt.Println(len(slice2), cap(slice2))
[] [Mike Alex jessica] [Mike Alex] [ ] [Mike Alex jessica]
3 3
次の記事
GO言語基礎②(defer/map/range/Interface/goroutine/channel)
参考記事
go言語 メリットとデメリット
JavaプログラマーのためのGo言語入門
Go言語でハマったことメモ(クラス・継承)
【Go】基本文法総まとめ