初めに
Goの構造体を勉強したため、アウトプットのため記事を投稿する。
■ 実行環境
The Go Playground
■ GOのバージョン
1.18
概要
構造体とは・・・異なるデータ型を1つにしたもの
1つ1つのデータをフィールド呼ぶ。
構造体の宣言
基本形
type [構造体名] struct {
[フィールド名] [型]
}
例
type Go struct {
X int
Y int
S string
}
フィールドの型が同じ場合は、カンマを使用して同じ列に記載可能である。
type Go struct {
X, Y int
S, T string
}
※構造体名の頭文字は小文字でも宣言できるが、その場合は外部のパッケージから参照することができないので、使用用途に合わせて変更する必要がある。
余談だが、Go言語の予約後に「go」という単語があるので、上記例にて頭文字を小文字で使用するとエラーになる。
構造体の初期化
構造体の初期化は、値を指定しない方法と値をあらかじめ指定する方法がある。
値を指定しない方法
値を指定せずに初期する場合は、3つの初期化方法がある。
いずれの場合も同じように初期化されている。
また、初期化したフィールドの変数にはゼロ値が格納されている。
type Go struct {
X int
Y int
S string
}
func main() {
// 1つ目
one := Go{}
// 2つ目
var two Go = Go{}
// 3つ目
var three Go
fmt.Println(one)
fmt.Println(two)
fmt.Println(three)
}
// 出力結果
{0 0 }
{0 0 }
{0 0 }
値を指定する方法
値を指定する場合は、全てのフィールドの変数に値を代入する方法と、一部の変数に値を代入する方法がある。
全てのフィールドの変数に値を代入して初期化
type Go struct {
X int
Y int
S string
}
func main() {
all := Go{1, 2, "s"}
fmt.Println(all)
}
// 出力結果
{1 2 s}
全ての値をセットしていないとエラーになる。
type Go struct {
X int
Y int
S string
}
func main() {
// 変数Sの値をセットしていない
all := Go{1, 2}
fmt.Println(all)
}
// 出力結果
too few values in struct literal
一部のフィールドの変数に値を代入して初期化
一部のフィールドの変数に値を代入して初期化したい場合は、フィールドの変数名を指定して初期化する。
その場合、フィールドを指定していない変数の値はゼロ値となる。
type Go struct {
X int
Y int
S string
}
func main() {
part := Go{Y: 3}
fmt.Println(part)
}
// 出力結果
{0 3 }
ポインタを使用して初期化
初期化する際にポインタを使用することができる。
&演算子
&演算子を初期化時に使用することができる。
下記コードだと、GOという構造体へのポインタであることが分かる。
例
type Go struct {
X int
Y int
}
func main() {
p := &Go{}
fmt.Printf("%T\n", p)
}
// 出力結果
*main.Go
new
newを使用しても、上記の&演算子
を使用した時と同じように初期化することができる。
type Go struct {
X int
Y int
}
func main() {
p := new(Go)
fmt.Printf("%T\n", p)
}
// 出力結果
*main.Go
構造体を初期化しないで使用を試みる
初期化を行わないで構造体を使用しようとするとエラーになる。
そのため、構造体は必ず初期化をして使用する。
func main() {
type Go struct {
X int
Y int
S string
}
fmt.Println(Go)
}
// 出力結果
Go (type) is not an expression
構造体へのアクセス
構造体へアクセスするには.
を使用する。
type Go struct {
X int
Y int
S string
}
func main() {
st := Go{1, 2, "initial"}
fmt.Println(st)
st.X = 3
st.S = "second"
fmt.Println(st)
}
// 出力結果
{1 2 initial}
{3 2 second}
.
を使用して代入したフィールドXとSの値のみ変更されていることが分かる。
値渡しと参照渡し
値渡し
type Go struct {
X int
Y int
S string
}
func passValue(pVal Go) {
// pValを使用して構造体の値を変更
pVal.X = 2
pVal.S = "値渡し"
fmt.Println("仮引数 =", pVal)
}
func main() {
// 構造体Goの初期化
p := Go{}
// 構造体Goを代入した変数pの型を出力
fmt.Printf("実引数の型 = %T", p)
// 変数pを出力
fmt.Println("\n実引数の型 =", p)
// 変数pを関数に値渡し
passValue(p)
// 値渡し後の変数pを出力
fmt.Println("値渡し後の変数p =", p)
}
// 出力結果
実引数の型 = main.Go
実引数の型 = {0 0 }
仮引数 = {2 0 値渡し}
値渡し後の変数p = {0 0 }
値渡しのため、値渡し後の変数pの値が変化していないことが分かる。
参照渡し
type Go struct {
X int
Y int
S string
}
// 構造体Goへのポインタである変数pを仮引数pRefとして受け取る
func passRef(pRef *Go) {
fmt.Printf("仮引数のポインタ = %p", pRef)
// pRefを使用して構造体の値を変更
pRef.X = 2
pRef.S = "参照渡し"
}
func main() {
// 構造体Goの初期化
p := &Go{}
// 構造体Goへのポインタである変数pのメモリアドレスを出力
fmt.Printf("実引数のポインタのメモリアドレス = %p", p)
// ポインタpの型を出力
fmt.Printf("\n実引数の型 = %T ", p)
// ポインタpを出力
fmt.Println("\n実引数 =", p)
// 構造体Goへのポインタを関数に参照渡し
passRef(p)
// 参照渡し後のポインタpを出力
fmt.Println("\n参照渡し後の実引数 =", p)
}
// 出力結果
実引数のポインタのメモリアドレス = 0xc00005c020
実引数の型 = *main.Go
実引数 = &{0 0 }
仮引数のポインタ = 0xc00005c020
参照渡し後の実引数 = &{2 0 参照渡し}
実引数と仮引数のメモリアドレスが同様であり、構造体を参照渡しできることが分かる。
ネスト
構造体はネストして使用できる。
GOはclassが存在しないが、構造体をネストして使用することで、クラスの継承のような機能を享受できる。
今回は、ネストする構造体を「親の構造体」、ネストされる構造体を「子の構造体」という名称で表記する。
通常のネスト
ネストする場合は、親の構造体のフィールドに子の構造体の情報を記載する。
func main() {
type child struct {
s string
}
type parent struct {
s string
chind child
}
// 親の構造体であるparentと子の構造体であるchildを一緒に初期化する
st := parent{"parent structのfield ", child{"child structのfield"}}
// 初期化した親の構造体を出力
fmt.Println(st)
// 子の構造体であるchildのフィールドの値を、親の構造体であるparent経由でアクセスして変更する
st.chind.s = "child structの値を変更します"
fmt.Println(st)
}
// 出力結果
{parent structのfield {child structのfield}}
{parent structのfield {child structの値を変更します}}
子の構造体である、childのsというフィールドの値が変更されていることが分かる。
子の構造体を初期化して変数に代入し、その変数を親の構造体の初期化時に使用することもできる。
func main() {
type child struct {
s string
}
type parent struct {
s string
chind child
}
// 子の構造体であるchildを初期化する
c := child{"childを初期化します"}
st := parent{"parent structのfield ", c}
fmt.Println(st)
}
// 出力結果
{parent structのfield {childを初期化します}}
スライス
スライスの要素に構造体を格納することもできる。
例
func main() {
var Go = []struct {
X int
S string
}{
{
X: 0,
S: "要素0",
},
{
X: 1,
S: "要素1",
},
}
fmt.Println(Go)
Go[0].S = "change"
fmt.Println(Go)
}
// 出力結果
[{0 要素0} {1 要素1}]
[{0 change} {1 要素1}]
構造体のフィールドにスライスを格納することもできる。
例
func main() {
type Go struct {
X []int
}
// 初期化
st := Go{}
fmt.Println(st)
// フィールドXにスライスを代入
st.X = []int{1, 2, 3}
fmt.Println(st)
}
// 出力結果
{[]}
{[1 2 3]}
構造体のフィールドに構造体を使用する
構造体のフィールドに構造体を使用することもできる。
type A struct {
S string
}
type B struct {
S string
A A
}
func main() {
var b B
fmt.Printf("(%v, %T)\n", b.A, b.A) // ({}, main.A)
b.A = A{"S"}
fmt.Printf("(%v, %T)\n", b.A, b.A) // ({S}, main.A)
fmt.Printf("(%v, %T)\n", b.A.S, b.A.S) // (S, string)
}