演習2-1
文字列型のメンバー変数helloをもつ構造体HelloWorldを定義せよ.また,メンバー変数helloの値をセットするメソッドSetHelloと,メンバー変数helloの値をディスプレーに表示するメソッドSeyHelloを定義せよ.
解答例(1)
解答例(1)
- package main
- import "fmt"
- type HelloWorld struct {
- hello string
- }
- func (h *HelloWorld) SayHello() {
- fmt.Printf(h.hello+"\n")
- }
- func (h *HelloWorld) SetHello(hello string) {
- h.hello = hello
- }
- func main() {
- h := new(HelloWorld) // newでHelloWorld型のオブジェクトを生成
- h.SayHello() // h.helloは""に初期化されるので何も表示されない
- h.SetHello("Hello, World!") // 文字列"Hello, World!"のセット
- h.SayHello() // セットした文字列"Hello, World!"が表示される
- }
解説
解説
型と型の宣言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)
解答例(2)
- package main
- import "fmt"
- type HelloWorld string
- func (h *HelloWorld) SayHello() {
- fmt.Printf(string(*h)+"\n")
- }
- func (h *HelloWorld) SetHello(hello string) {
- *h = HelloWorld(hello)
- }
- func main() {
- h1 := new(HelloWorld) // newでHelloWorld型のオブジェクトを生成
- h1.SayHello() // h1は""に初期化されるので何も表示されない
- h1.SetHello("Hello, World!") // 文字列のセット
- h1.SayHello() // セットした文字列が表示される
- h2 := HelloWorld("こんにちは、世界。") // 文字列をHelloWorld型にキャスト
- h2.SayHello() // その文字列を表示
- var h3 HelloWorld
- h3.SayHello()
- h3.SetHello("Bonan tagon")
- h3.SayHello()
- }
解説(2)
解説(2)
メソッド4行目,6行目,10行目
- メソッドは構造体にだけ定義できるわけではありません.
- 整数や文字列,配列などの組み込み型から新たな型を定義し,その型にメソッドを追加することができます.(ただし,ある型のメソッドの定義は,その型が定義されたパッケージ内でなければなりません)
- 解答例(2)では,string型からHelloWorldという型を定義して,その型にメソッドを定義しています.
変換
- 7行目のstring(*h)はHelloWorld型の*hを文字列型に変換しています.
- また,11行目のHelloWorld(hello)は文字列型のhelloをHelloWorld型に変換しています.
- Go言語では,自動型変換は行われません.明示的な変換が必要です.
- 変換についての詳細は,言語仕様書を参照してください.