
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
Pointer types(ポインタ型)
ポインタ型は、指定された型の変数へのすべてのポインタの集合を示します。この型はポインタの基底型と呼ばれます。初期化されていないポインタの値はnilです。
PointerType = "*" BaseType .
BaseType = Type .
*Point
*[4]int
解説
ポインタとは何か?
ポインタ(pointer) は、変数の**メモリアドレス(住所)**を保持する型です。値そのものではなく、「値がどこにあるか」を指し示します。
たとえ話: ポインタは「住所」のようなものです。家(変数)そのものではなく、「〇〇市△△町1-2-3」という住所を持っています。住所を知っていれば、その家を訪ねて中身を見たり、変更したりできます。
package main
import "fmt"
func main() {
// 通常の変数
x := 42
// ポインタ: xのアドレスを取得
ptr := &x
fmt.Println("xの値:", x) // 42
fmt.Println("xのアドレス:", ptr) // 0xc0000... (メモリアドレス)
fmt.Println("ポインタ経由の値:", *ptr) // 42
// ポインタ経由で値を変更
*ptr = 100
fmt.Println("変更後のx:", x) // 100
}
1. ポインタの基本
ポインタの宣言
package main
import "fmt"
func main() {
// int型のポインタ
var p1 *int
fmt.Println(p1) // <nil> (初期化されていない)
// string型のポインタ
var p2 *string
fmt.Println(p2) // <nil>
// 構造体のポインタ
type Person struct {
Name string
Age int
}
var p3 *Person
fmt.Println(p3) // <nil>
}
ゼロ値はnil
初期化されていないポインタの値はnilです。
package main
import "fmt"
func main() {
var ptr *int
// nilチェック
if ptr == nil {
fmt.Println("ポインタはnilです")
}
// nilポインタを参照外しするとパニック
// fmt.Println(*ptr) // panic: runtime error
}
2. アドレス演算子(&)と参照外し(*)
&演算子: アドレスを取得
package main
import "fmt"
func main() {
x := 42
// &演算子でアドレスを取得
ptr := &x
fmt.Printf("xの値: %d\n", x)
fmt.Printf("xのアドレス: %p\n", ptr)
fmt.Printf("xのアドレス(別の表記): %p\n", &x)
}
*演算子: 参照外し(dereference)
package main
import "fmt"
func main() {
x := 42
ptr := &x
// *演算子でポインタの指す値にアクセス
fmt.Println("ポインタ経由の値:", *ptr) // 42
// ポインタ経由で値を変更
*ptr = 100
fmt.Println("変更後のx:", x) // 100
fmt.Println("変更後の*ptr:", *ptr) // 100
}
3. 様々な型のポインタ
基本型のポインタ
package main
import "fmt"
func main() {
// int型
var i int = 42
var pi *int = &i
// string型
var s string = "Hello"
var ps *string = &s
// bool型
var b bool = true
var pb *bool = &b
fmt.Println(*pi, *ps, *pb)
}
配列のポインタ
package main
import "fmt"
func main() {
arr := [4]int{1, 2, 3, 4}
// 配列のポインタ
ptr := &arr
// ポインタ経由で要素にアクセス
fmt.Println((*ptr)[0]) // 1
// Goでは自動的に参照外ししてくれる
fmt.Println(ptr[0]) // 1
// 要素の変更
ptr[0] = 10
fmt.Println(arr) // [10 2 3 4]
}
構造体のポインタ
package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
p := Point{X: 10, Y: 20}
// 構造体のポインタ
ptr := &p
// Goでは (*ptr).X と書かなくても良い
fmt.Println(ptr.X) // 10 (自動的に参照外し)
// フィールドの変更
ptr.X = 30
fmt.Println(p.X) // 30
}
4. newによるポインタの作成
new()関数は、指定した型のゼロ値を持つ変数を作成し、そのポインタを返します。
package main
import "fmt"
func main() {
// newでポインタを作成
ptr := new(int)
fmt.Println(ptr) // アドレス
fmt.Println(*ptr) // 0 (ゼロ値)
// 値を設定
*ptr = 42
fmt.Println(*ptr) // 42
}
newと&の比較
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 方法1: new
p1 := new(Person)
p1.Name = "太郎"
p1.Age = 25
// 方法2: &と複合リテラル
p2 := &Person{
Name: "花子",
Age: 30,
}
fmt.Printf("%+v\n", p1)
fmt.Printf("%+v\n", p2)
}
5. ポインタと関数
値渡し vs ポインタ渡し
package main
import "fmt"
// 値渡し(コピーが渡される)
func incrementByValue(x int) {
x = x + 1
fmt.Println("関数内:", x)
}
// ポインタ渡し(アドレスが渡される)
func incrementByPointer(x *int) {
*x = *x + 1
fmt.Println("関数内:", *x)
}
func main() {
a := 10
incrementByValue(a)
fmt.Println("値渡し後:", a) // 10 (変わらない)
incrementByPointer(&a)
fmt.Println("ポインタ渡し後:", a) // 11 (変わる)
}
構造体とポインタ
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 値レシーバ(コピー)
func (p Person) HaveBirthdayValue() {
p.Age++
fmt.Println("関数内:", p.Age)
}
// ポインタレシーバ(元の値を変更)
func (p *Person) HaveBirthdayPointer() {
p.Age++
fmt.Println("関数内:", p.Age)
}
func main() {
person := Person{Name: "太郎", Age: 25}
person.HaveBirthdayValue()
fmt.Println("値レシーバ後:", person.Age) // 25 (変わらない)
person.HaveBirthdayPointer()
fmt.Println("ポインタレシーバ後:", person.Age) // 26 (変わる)
}
6. ポインタのポインタ
ポインタのアドレスを保持するポインタも作れます。
package main
import "fmt"
func main() {
x := 42
// xのポインタ
p := &x
// ポインタのポインタ
pp := &p
fmt.Println("x:", x) // 42
fmt.Println("*p:", *p) // 42
fmt.Println("**pp:", **pp) // 42
// ポインタのポインタ経由で値を変更
**pp = 100
fmt.Println("x:", x) // 100
}
7. nilポインタ
nilチェック
package main
import "fmt"
func processValue(ptr *int) {
if ptr == nil {
fmt.Println("nilポインタです")
return
}
fmt.Println("値:", *ptr)
}
func main() {
var p1 *int
processValue(p1) // nilポインタです
x := 42
p2 := &x
processValue(p2) // 値: 42
}
nilポインタのメソッド呼び出し
package main
import "fmt"
type Person struct {
Name string
}
func (p *Person) Greet() {
if p == nil {
fmt.Println("nilです")
return
}
fmt.Println("こんにちは、", p.Name)
}
func main() {
var p *Person
p.Greet() // nilです (nilレシーバでも呼び出せる)
p2 := &Person{Name: "太郎"}
p2.Greet() // こんにちは、 太郎
}
8. 実用例
例1: オプショナルな値
package main
import "fmt"
type Config struct {
Host string
Port *int // nilならデフォルト値を使う
Timeout *int
}
func main() {
port := 8080
config := Config{
Host: "localhost",
Port: &port,
// Timeoutは指定しない(nil)
}
// ポート
if config.Port != nil {
fmt.Println("ポート:", *config.Port)
} else {
fmt.Println("ポート: デフォルト")
}
// タイムアウト
if config.Timeout != nil {
fmt.Println("タイムアウト:", *config.Timeout)
} else {
fmt.Println("タイムアウト: デフォルト")
}
}
例2: 大きな構造体の効率的な処理
package main
import "fmt"
type LargeStruct struct {
Data [1000000]int
}
// 値渡しだとコピーが発生(非効率)
func processByValue(s LargeStruct) {
// ...
}
// ポインタ渡しはコピー不要(効率的)
func processByPointer(s *LargeStruct) {
s.Data[0] = 100
}
func main() {
large := LargeStruct{}
// processByValue(large) // 遅い
processByPointer(&large) // 速い
fmt.Println(large.Data[0])
}
例3: リンクリスト
package main
import "fmt"
type Node struct {
Value int
Next *Node // 次のノードへのポインタ
}
func main() {
// リンクリストの作成
node1 := &Node{Value: 1}
node2 := &Node{Value: 2}
node3 := &Node{Value: 3}
node1.Next = node2
node2.Next = node3
// リストの走査
current := node1
for current != nil {
fmt.Print(current.Value, " -> ")
current = current.Next
}
fmt.Println("nil")
// 出力: 1 -> 2 -> 3 -> nil
}
例4: 二分木
package main
import "fmt"
type TreeNode struct {
Value int
Left *TreeNode
Right *TreeNode
}
func (n *TreeNode) Insert(value int) {
if value < n.Value {
if n.Left == nil {
n.Left = &TreeNode{Value: value}
} else {
n.Left.Insert(value)
}
} else {
if n.Right == nil {
n.Right = &TreeNode{Value: value}
} else {
n.Right.Insert(value)
}
}
}
func (n *TreeNode) InOrder() {
if n == nil {
return
}
n.Left.InOrder()
fmt.Print(n.Value, " ")
n.Right.InOrder()
}
func main() {
root := &TreeNode{Value: 5}
root.Insert(3)
root.Insert(7)
root.Insert(1)
root.Insert(9)
root.InOrder()
// 出力: 1 3 5 7 9
}
9. ポインタの比較
ポインタ同士の比較
package main
import "fmt"
func main() {
x := 42
y := 42
p1 := &x
p2 := &x // 同じ変数を指す
p3 := &y // 別の変数を指す
fmt.Println(p1 == p2) // true (同じアドレス)
fmt.Println(p1 == p3) // false (異なるアドレス)
// 値が同じでもアドレスが違えば異なる
fmt.Println(*p1 == *p3) // true (値は同じ)
}
nilとの比較
package main
import "fmt"
func main() {
var p1 *int
x := 42
p2 := &x
fmt.Println(p1 == nil) // true
fmt.Println(p2 == nil) // false
// nilポインタ同士
var p3 *int
fmt.Println(p1 == p3) // true
}
10. よくある間違いと注意点
間違い1: nilポインタの参照外し
package main
func main() {
var ptr *int
// これはパニック!
// fmt.Println(*ptr) // panic: runtime error
// 正しい書き方
if ptr != nil {
fmt.Println(*ptr)
}
}
間違い2: ローカル変数のアドレスを返す
package main
func createPointer() *int {
x := 42
// xは関数終了後も有効(Goは自動的にヒープに確保)
return &x // OK (ガベージコレクションがある)
}
func main() {
ptr := createPointer()
fmt.Println(*ptr) // 42 (正しく動作する)
}
注意: Goはガベージコレクションがあるので、C言語のようなダングリングポインタの問題は起きません。
間違い3: スライスとマップの混同
package main
func main() {
// スライスは参照型(内部的にポインタを持つ)
slice := []int{1, 2, 3}
modifySlice(slice)
fmt.Println(slice) // [100 2 3] (変更される)
// 配列は値型
arr := [3]int{1, 2, 3}
modifyArray(arr)
fmt.Println(arr) // [1 2 3] (変更されない)
}
func modifySlice(s []int) {
s[0] = 100
}
func modifyArray(a [3]int) {
a[0] = 100
}
まとめ: ポインタ型で覚えておくべきこと
ポインタの基本
- アドレスを保持: 変数の「住所」を持つ
- ゼロ値はnil: 初期化されていないポインタは
nil - &演算子: アドレスを取得
- *演算子: 参照外し(値にアクセス)
宣言と使用
// 宣言
var ptr *int
// アドレス取得
x := 42
ptr = &x
// 参照外し
value := *ptr
// 値の変更
*ptr = 100
関数との使用
// 値渡し(コピー)
func byValue(x int) {
x = 100 // 元の変数は変わらない
}
// ポインタ渡し(参照)
func byPointer(x *int) {
*x = 100 // 元の変数が変わる
}
newとの比較
// new
ptr1 := new(int)
*ptr1 = 42
// &
x := 42
ptr2 := &x
実用的なアドバイス
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 1. nilチェックを忘れずに
var ptr *int
if ptr != nil {
fmt.Println(*ptr)
}
// 2. 大きな構造体はポインタで渡す
p := &Person{Name: "太郎", Age: 25}
updatePerson(p)
// 3. スライス・マップは既に参照型
slice := []int{1, 2, 3}
modifySlice(slice) // ポインタ不要
// 4. メソッドはポインタレシーバを使うことが多い
p.HaveBirthday()
fmt.Println(p.Age)
}
func updatePerson(p *Person) {
p.Age++
}
func modifySlice(s []int) {
s[0] = 100
}
func (p *Person) HaveBirthday() {
p.Age++
}
ポインタを使うべき場合
- 関数で元の値を変更したい
- 大きな構造体を効率的に渡したい
- オプショナルな値を表現したい(
nilが使える) - データ構造(リンクリスト、木構造など)
ポインタは、効率的で柔軟なプログラミングを可能にする重要な機能です。適切に使うことで、メモリ効率が良く、保守性の高いコードが書けます!
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント