前回の記事でGo言語の変数宣言やメソッド宣言などの部分を紹介しましたが、 今回はその後半で...としようと思ったのですが、以外と量が多かったので今回は下記二つに絞ってまとめていきます。
- 構造体
- インターフェース
前回の記事はこちらです。
勉強に使用した本
文法の勉強に使用した本は前回と一緒のこちらです。
前回も書きましたが、これ一冊でバランスよくGoの文法がまとめられているので 一回通しでよんだ後に、簡単なコマンドベースのコードを書きつつ 不明なところを再度読み直せば結構かけるようになる気がします。
文法のまとめ
ということで早速、構造体、インターフェースなど前回さわれなかったところをまとめていきます。
構造体
構造体はCやVBAを触った時にちょっと触ったので親しみがありますが、はるか昔すぎてなんのことやらわかりません。 rubyのハッシュのようなものかなと思っているのですが、構造体とはフィールドの集合でそれぞれに型があるものなのだそうです。
まぁ型のあるハッシュとざっくり理解しています。どうやら構造体にもスコープの概念が適用されフィールドの先頭1文字目を大文字にすると フィールドが外部に公開され外部パッケージからでもアクセスできるようになっているようです。
構造体の書式は
struct {
// フィールド宣言
}
で、構造体に名前をつける場合は、
type MyData struct {
a string
b bye
}
のようにtypeキーワードを用いて名前をつけていきます。
特殊なフィールド
また、構造体にはいくつか特殊なフィールドを宣言することができて、そのひとつにブランクフィールドがあります。 メモリ領域には確保が必要だけどフィールドにアクセスすることはないということを明示的に示すために使用するそうです。 通常構造体のフィールドは一意の名前をつける必要がありますが、ブランクフィールドの場合は一つの構造体に複数定義することが 可能です。
struct {
b1 byte
_ byte // ブランクフィールド
b2 [5]byte
_ byte // ブランクフィールド
}
ブランクフィールドはちょっと使いどころがわからないです。本の後ろの方に逆引き的なサンプルがのっているので もしかしたらそこで確認できるかもしれません。
構造体には、他にも匿名フィールドと呼ばれるフィールドも宣言可能でフィールド名を指定せずに型名だけでフィールドを宣言 できます。
struct {
int // フィールド名はint
uinit64 // フィールド名はuint64
*byte // フィールド名はbyte
}
構造体の初期化
構造体の初期化の方法は3通りあり
- 構造体宣言後に書くフィールドに個別に値を代入して初期化
- 構造体リテラルを使うのと同時にフィールド名と値をペアで与えて初期化
- 構造体リテラルを使うのと同時フィールドの宣言順に値を記述して初期化
1.は説明する必要ないと思いますが、1の方法以外に2,3の初期化方法があり、
2.のフィールド名と値をペアで与えて初期化するには次のようにします。
type Person struct {
name string
age int
}
func main() {
// フィールド名と値のペア
p2 := Person{age: 31, name: "Tom"}
// 宣言順に値を渡す
p3 := Person{"Jane", 42}
}
type Person struct {
name string
age int
}
func main() {
// フィールド名と値のペア
p2 := Person{age: 31, name: "Tom"}
// 宣言順に値を渡す
p3 := Person{"Jane", 42}
}
ここの文を読み進めていて構造体初期化が複数行にわたった時の記法ちょっと気持ち悪いなと思いました。 構造体の初期化で複数行にわたる場合は最後の } をフィールドと同じ行にかかないといけないようです。
// こうやって書かないとコンパイルエラーになる。。気持ち悪い
Person{
age: 31,
name: "Tom"}
ただ、さらに本を読み進めると最後にカンマをつければコンパイルエラーにならないそうでスッキリしました。
// これならスッキリ
Person{
age: 31,
name: "Tom",
}
インターフェース
インターフェースはわかるかたも多いかもしれませんが、「振る舞い」を規定する関数みたいなものです。中身のロジックを持たずに外面だけを定義します。 インターフェースではロジックを書かないので関数の名前とパラメータ(引数)・戻り値だけを定義します。
interface {
関数名(パラメータリスト) 戻り値の型
関数名(パラメータリスト) 戻り値の型
}
// type宣言を使って名前をつけて定義する場合
type インターフェース型名 interface {
関数名(パラメータリスト) 戻り値の型
関数名(パラメータリスト) 戻り値の型
}
予備知識として関数を一つしかもたないインターフェースの場合インターフェース名は「関数名 + er」とするのが 習わしらしいです。例をそえておきます。
type Reader interface {
Read(p []byte) (n int, err error)
}
自動的に実装されるインターフェース
Goのインターフェースは自動的に実装されるそうで、「インターフェース」と「それを実装した型」との関係性を 明示的に書く必要がないそうです。
Javaとかだと
public class Sample implements [インターフェース] {
}
というように型に対してどのインターフェースを実装するかを明示的に書く必要があるのですが、 Goではこれが必要なく既存の方に後付けでインターフェースを実装することができるようです。
空インターフェース
interface {}
空インターフェースとは、「インターフェースに実装すべき関数がない」 => 「すべての型が空インターフェースを実装している」 ということになるので空インターフェースにはどんな値も代入可能です。なんでもできます。
型変換について
Go言語では型アサーションという機能がありレシーバーの後ろに「.」と丸括弧で変換先を記述することで型変換を行ったり、 型変換可能かどうかを確認することができます。
// 空インターフェースの変数の値をstring型に変換
var i interface{} = "test"
var s string = i.(string)
型アサーションが可能かどうかを調べたいだけの時は、型アサーションから返ってくる二番目の論理値を使います。 型アサーションから二番目の値を取得する場合は、型変換できなくても失敗しないので型変換可能かどうかをチェックできます。
var s string = "test"
s2, ok := s.(interface {
dummy()
})
fmt.Println(s2, ok)
型switch文
switch 値.(type) {
case 型1:
// type が型1の時ここが実行される。
case 型2:
// type が型2の時ここが実行される。
default:
// type が上以外の型の場合
}
型switch文です!ついに来ました!個人的にこれだいぶ便利そうな感じするのですが実際どうなのでしょうか?
型switch文は単純に型ごとに処理を分けられるのですが、その結果を変数に代入することもできるので 変数の型を気にしない実装ができそうな気がしています。多用して簡単に分岐作っちゃうのもよくないとは思うのですが どうしてもという時に役たちそうです。
コードも書いていない状態でフライング気味ですが、ぜひ使ってみたい機能の一つです。
まとめ
当初記事二本でまとめ切ろうと思いましたが、無理でした。もう一、二本で完結させたいです。
文法をまとめている間にどんどん時はすぎていくので早くコード書けという話なのですが、まだ書けていないので とりあえず記事にしておきます。文法をなんとなくさらったらコマンドベースで動くものでも作ってみると良さそうですね。 最終的にはGoのWebフレームワーク使ってAPI開発などできればというところですね。
ペースはゆったりですがじわじわ進めていければ。