🌟

Go言語 構造体のポインタ 関数

2023/06/20に公開

はじめに

はじめまして。新卒エンジニアです。
前回Go言語の関数内でのポインタの在り方について記事を書かせていただきましたが、学習進めるうちに、関数で構造体ポインタを使用するのが複雑に感じたため、メモとして記事を書かせていただきます。

実行結果

package main

import (
	"fmt"
)

// UserIDとRoleを持つ構造体USERを定義する
type USER struct {
	UserID int
	Role   string
}

func User(user *USER) {
	//パターン1
	// userのアドレスを表示
	fmt.Println(user)
	// 実行結果 &{123 admin}

	//パターン2
	// userの型を表示
	fmt.Printf("userの型 : %T", user)
	// 実行結果 userの型 : *main.USER

	//パターン3
	// &を用いない
	fmt.Println(user.UserID)
	fmt.Println(user.Role)
	// 実行結果 123 admin

	//パターン4
	// &を用いる
	fmt.Println(&user.UserID)
	fmt.Println(&user.Role)
	// 実行結果 0xc000010030 0xc000010038

	user.UserID = 456
	user.Role = "user"

	// 下の二つはポインタなので、値の変更ができない
	// &user.UserID = 456
	// &user.Role = "user"
}

func main() {
	// 構造体USER型の変数userを定義
	user := USER{UserID: 123, Role: "admin"}
	// 引数で変数userのアドレスを渡す
	User(&user)

	// userの値を表示
	fmt.Println(user)
	// 実行結果 {456 user}
}

解説

まずGO言語の構造体について

構造体とは

構造体とは、異なるデータ型の変数を1つにまとめたものになります。
つまり、構造体には、整数型や文字列型などの異なるデータ型の変数を、1つにまとめることができます。
構造体を作ることを「構造体を定義する」といいます。

https://kino-code.com/course-go13-structure/


構造体にもポインタの指定をすることができ、その際関数での動きが少し特殊になります。
その細かい解説をコードをもとに行っていきます。

まずmain関数内で、構造体USER型の変数userを定義し、USER関数に先ほど定義した変数userのポインタを渡しており、その時のUSER関数内での4つのパターンで見ていきます。

パターン1 引数userの中身
User(&user)という呼び出しで、user変数のアドレスをUSER関数に渡しています。そのため、USER関数内のuserパラメータは*USER型(USER型へのポインタ)であり、アドレスを受け取ることができます。
fmt.Println(user)は、user変数がポインタ型であるため、そのアドレスを表示します。&{123, "admin"}は、user変数のアドレスとして表されることを意味します。

パターン2 引数userの型
fmt.Printf("userの型 : %T", user)の実行結果が⋆main.USERとなるのは、userがUSER型のポインタであるためです。
%Tフォーマット指定子は、渡された値の型を表示します。
⋆main.USERという結果は、userがUSER型のポインタを指していることを示しています。⋆はポインタ型を示し、main.USERはUSER型がmainパッケージに定義されていることを示します。
つまり、実行結果⋆main.USERは、userがUSER型のポインタを指していることを表しています。

パターン3 &を用いない呼び出し
基本型での参照渡しで実体にアクセスするには、* が必要でしたが構造体では不要です.
明確には省略することができます。
そのため、userが*User型(User型へのポインタ)である場合、user.UserIDは実際にはポインタではなく、User型の変数のフィールドであるUserIDの値を取得します。userがポインタであっても、user.UserID(user.Role)は値そのものを表します。
すなわち、fmt.Println(user.UserID)およびfmt.Println(user.Role)は、user変数のフィールドへのアクセスを行っています。
したがって、fmt.Println(user.UserID)およびfmt.Println(user.Role)は、user変数のフィールドの値である123および"admin"を表示します。

パターン4 &を用いた呼び出し
&user.UserIDはuser.UserIDのアドレスを表し、&user.Roleはuser.Roleのアドレスを表します。
このように、&user.UserIDや&user.Roleのように&を付けると、それぞれuser.UserIDとuser.Roleのアドレスを取得してしまいます。

補足
USER(&user)で、ポインタを与えているため、&をつけていない形である、user.UserIDとuser.RoleにUser関数内で上書きした値が、main関数内にもしっかりと適応されていました。
そして、&user.UserIDのような形はポインタなので、直接値の変更はできませんでした。

終わりに

構造体のポインタの働きを理解しておかないと、データベースの接続をするタイミング等で、躓いてしまう部分があったため、今回メモとしてまとめさせていただきました。
今後は、今回の学習を用いて実際の活用方法等に繋げながら学習していきます。

参考

https://golangstart.com/go_struct_pointer/
ChatGPTを活用しました。

株式会社アクティブコア

Discussion