演習2-1

文字列型のメンバー変数helloをもつ構造体HelloWorldを定義せよ.また,メンバー変数helloの値をセットするメソッドSetHelloと,メンバー変数helloの値をディスプレーに表示するメソッドSeyHelloを定義せよ.

解答例(1)

  1. package main
  2. import "fmt"
  3. type HelloWorld struct {
  4. hello string
  5. }
  6. func (h *HelloWorld) SayHello() {
  7. fmt.Printf(h.hello+"\n")
  8. }
  9. func (h *HelloWorld) SetHello(hello string) {
  10. h.hello = hello
  11. }
  12. func main() {
  13. h := new(HelloWorld) // newでHelloWorld型のオブジェクトを生成
  14. h.SayHello() // h.helloは""に初期化されるので何も表示されない
  15. h.SetHello("Hello, World!") // 文字列"Hello, World!"のセット
  16. h.SayHello() // セットした文字列"Hello, World!"が表示される
  17. }

解説

型と型の宣言4行目

  • 型には事前宣言済みの型とコンポジット型があります.事前宣言済みの型:論理値型、数値型、文字列型コンポジット型:配列、構造体、ポインタ、関数、インタフェース、スライス、マップ、チャネル型
  • 新たな型を宣言するには,typeを用います.type
  • 4行目では,構造体型をHelloWorldという名前の新たな型として宣言しています.
  • 型についての詳細は言語仕様 型の宣言を参照してください.

構造体4行目~6行目

  • 構造体は,名前と型をもつフィールドと呼ばれる要素の集まりです.
  • 新たな構造体型を宣言するにはsructを用います.struct { フィールド名, フィールド名, ... 型 フィールド名, フィールド名, ... 型 ...}
  • 5行目では,helloというフィールド名をもつ文字列型のフィールドを定義しています.
  • 構造体についての詳細は,言語仕様の構造体型複合リテラルを参照してください.

ポインタ型

  • ポインタ型は所定の型(ベース型という)の変数へのすべてのポインタの集合を表します.ベース型の前に'*'をつけて表します.
  • たとえば,8行目の*HelloWorldはHelloWorldというベース型の変数へのポインタ型です.
  • C言語と同様に,型Tの変数xのアドレスはアドレス演算子'&'を用いて,&xで表すことができ,&xの型は*Tとなります.また,*T型であるポインタpの間接参照(ポインタpがさすもの)は,アドレス演算子'*'を用いて,*pで表すことができ,*pの型はTとなります.
  • ポインタについての詳細は,言語仕様のポインタ型アドレス演算子を参照してください.

メソッド8行目,12行目

  • 型は,その型と関連付けられたメソッド群を持ちます.
  • 新たなメソッドを宣言するには,関数の宣言と同様にfuncを用います.メソッドと関数の違いは,レシーバの有無です.
  • func レシーバ メソッド名(引数のリスト) (戻り値のリスト) { メソッド本体}
  • このように宣言されたメソッドは,レシーバに記述された型のメソッドとなります.
  • C++やJavaのようなクラスという概念はなく,クラスの宣言とともにメソッドを宣言することもありません.T型のレシーバを持ったすべてのメソッドが,T型のメソッド群となります.
  • 解答例(1)の例では,ポインタ型 *HelloWorld のメソッド群は8行目のSetHelloと12行目のSeyHelloの二つです.
  • C++のように暗黙的なthisを持たないので,メソッドから構造体のフィールとメンバにアクセスするためにはレシーバ変数を使用します.
  • 解答例(1)のメソッドSayHello()とSetHello()では,構造体HelloWorldのhelloというフィールドにアクセスするために,hというレシーバ変数を用いています.
  • メソッドに関する詳細は,言語仕様のメソッド群メソッドの宣言を参照してください.

呼び出し

  • 関数の呼び出しでは,引数は単一値となる式であり,この式は関数の呼び出し前に評価されます.
  • C言語と同様,引数は値渡しされます.すなわち,実引数として変数を渡してもその値のみが渡され,また,関数内でその変数を変更しても,その変更は,呼び出し元にはされません.
  • したがって,渡した引数への操作を呼び出し元に反映させるには,ポインタを渡します.
  • メソッドのレシーバも引数とみなすことができますから,レシーバへの変更をメソッドの呼び出し元に反映させるには,レシーバをポインタ型とします.
  • 例えば,8行目のように記述することで,hへの文字列の代入が,メソッドの呼び出し元へも反映されます.

セレクタ

  • x.f と書くことで,xのフィールドまたはメソッドfを表します.この識別子fはセレクタと呼ばれます.
  • セレクタは自動的に構造体へのポインタの間接参照を行います.xが構造体へのポインタ型のとき,(*.x).yを簡略化してx.yと書くことができます.
  • 解答例(1)のhはHelloWorld型の変数へのポインタなので,9行目と13行目のh.hello,18行目と20行目のh.SayHello(),19行目のh.SetHello()は,それぞれ,(*h).hello,(*h).SayHello(),(h*).SetHello()の簡略形を使って書いています.CやC++のような->の記法はありません.
  • セレクタに関する詳細は,言語仕様を参照してください.

メモリの割り当て

  • メモリ割り当てには組み込み関数newを使います.
  • newはパラメータに型Tを取り,型*Tの値を返します.
  • このときメモリの内容はゼロ値(下記)に初期化されます.
  • 17行目のh := new(HelloWorld)では,構造体HelloWorldのメモリ領域が確保され,それへのポインタがhに代入されます.
  • また,この文は,newの代わりに構造体リテラルを使って,h := &HelloWorld{""}と書くことができます.単純な構造体であれば,複合リテラルのアドレスを返すほうが簡単です。
  • メモリの割り当てに関する詳細は,言語仕様を参照してください.

ゼロ値

  • 値を格納するため宣言や,newによるメモリ割り当てでは,明示的に初期化をしなければ,それぞれその型のゼロ値がセットされます
  • 論理値型のゼロ値はfalse,整数型は0,浮動小数点型は0.0,文字列型は""です.
  • ポインタ,関数,インタフェース,スライス,チャネル,マップ型のゼロ値はnilです.
  • 初期化は再帰的に行われ,型が構造体が配列のようなコンポジット型のときは,初期値が指定されなければ各要素のフィールドがゼロ値となります.
  • ゼロ値に関する詳細は言語仕様を参照してください.

解答例(2)

  1. package main
  2. import "fmt"
  3. type HelloWorld string
  4. func (h *HelloWorld) SayHello() {
  5. fmt.Printf(string(*h)+"\n")
  6. }
  7. func (h *HelloWorld) SetHello(hello string) {
  8. *h = HelloWorld(hello)
  9. }
  10. func main() {
  11. h1 := new(HelloWorld) // newでHelloWorld型のオブジェクトを生成
  12. h1.SayHello() // h1は""に初期化されるので何も表示されない
  13. h1.SetHello("Hello, World!") // 文字列のセット
  14. h1.SayHello() // セットした文字列が表示される
  15. h2 := HelloWorld("こんにちは、世界。") // 文字列をHelloWorld型にキャスト
  16. h2.SayHello() // その文字列を表示
  17. var h3 HelloWorld
  18. h3.SayHello()
  19. h3.SetHello("Bonan tagon")
  20. h3.SayHello()
  21. }

解説(2)

メソッド4行目,6行目,10行目

  • メソッドは構造体にだけ定義できるわけではありません.
  • 整数や文字列,配列などの組み込み型から新たな型を定義し,その型にメソッドを追加することができます.(ただし,ある型のメソッドの定義は,その型が定義されたパッケージ内でなければなりません)
  • 解答例(2)では,string型からHelloWorldという型を定義して,その型にメソッドを定義しています.

変換

  • 7行目のstring(*h)はHelloWorld型の*hを文字列型に変換しています.
  • また,11行目のHelloWorld(hello)は文字列型のhelloをHelloWorld型に変換しています.
  • Go言語では,自動型変換は行われません.明示的な変換が必要です.
  • 変換についての詳細は,言語仕様書を参照してください.