Go言語入門:言語仕様 -Vol.20-

スポンサーリンク
Go言語入門:言語仕様 -Vol.20- 用語解説
Go言語入門:言語仕様 -Vol.20-
この記事は約15分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

Numeric types(数値型)

整数型、浮動小数点型、または複素数型は、それぞれ整数、浮動小数点、または複素数の値の集合を表します。これらは総称して数値型と呼ばれます。事前宣言されたアーキテクチャ非依存の数値型は次のとおりです。

uint8       すべての符号なし8ビット整数の集合(0〜255)
uint16      すべての符号なし16ビット整数の集合(0〜65535)
uint32      すべての符号なし32ビット整数の集合(0〜4294967295)
uint64      すべての符号なし64ビット整数の集合(0〜18446744073709551615)

int8        すべての符号付き8ビット整数の集合(-128〜127)
int16       すべての符号付き16ビット整数の集合(-32768〜32767)
int32       すべての符号付き32ビット整数の集合(-2147483648〜2147483647)
int64       すべての符号付き64ビット整数の集合(-9223372036854775808〜9223372036854775807)

float32     すべてのIEEE 754 32ビット浮動小数点数の集合
float64     すべてのIEEE 754 64ビット浮動小数点数の集合

complex64   float32の実数部と虚数部を持つすべての複素数の集合
complex128  float64の実数部と虚数部を持つすべての複素数の集合

byte        uint8のエイリアス
rune        int32のエイリアス

nビット整数の値はnビット幅で、2の補数演算を使用して表現されます。

また、実装固有のサイズを持つ事前宣言された整数型のセットもあります。

uint     32ビットまたは64ビット
int      uintと同じサイズ
uintptr  ポインタ値の未解釈ビットを格納するのに十分な大きさの符号なし整数

移植性の問題を回避するため、すべての数値型は定義された型であり、したがってbyte(uint8のエイリアス)とrune(int32のエイリアス)を除いて区別されます。式または代入で異なる数値型が混在する場合は、明示的な変換が必要です。たとえば、int32intは、特定のアーキテクチャで同じサイズであっても、同じ型ではありません。


解説

数値型とは何か?

数値型は、数値(整数、小数、複素数)を扱うための型です。Goには用途に応じて様々なサイズと特性の数値型が用意されています。

たとえ話: 数値型は「数を入れる容器」で、容器の大きさ(ビット数)によって入れられる数の範囲が決まります。小さな容器(8ビット)には-128〜127しか入りませんが、大きな容器(64ビット)には非常に大きな数まで入ります。


1. 整数型(Integer types)

整数型は小数点を持たない数を表します。

符号付き整数(Signed integers)

正の数も負の数も表せる整数型です。

package main

import "fmt"

func main() {
    var i8 int8 = -128      // 最小値
    var i16 int16 = -32768
    var i32 int32 = -2147483648
    var i64 int64 = -9223372036854775808
    
    fmt.Println(i8, i16, i32, i64)
    
    // 正の数も扱える
    var positive int32 = 12345
    fmt.Println(positive)
}

各型の範囲

ビット数最小値最大値
int88-128127
int1616-32,76832,767
int3232-2,147,483,6482,147,483,647
int6464-9,223,372,036,854,775,8089,223,372,036,854,775,807

符号なし整数(Unsigned integers)

0以上の数だけを表せる整数型です。負の数は扱えません。

package main

import "fmt"

func main() {
    var u8 uint8 = 255
    var u16 uint16 = 65535
    var u32 uint32 = 4294967295
    var u64 uint64 = 18446744073709551615
    
    fmt.Println(u8, u16, u32, u64)
    
    // 負の数は扱えない
    // var invalid uint8 = -1  // エラー!
}

各型の範囲

ビット数最小値最大値
uint880255
uint1616065,535
uint323204,294,967,295
uint6464018,446,744,073,709,551,615

アーキテクチャ依存の整数型

システムに応じてサイズが変わる型です。

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var i int
    var u uint
    var ptr uintptr
    
    // サイズを確認(32ビットシステムでは4バイト、64ビットでは8バイト)
    fmt.Printf("intのサイズ: %d バイト\n", unsafe.Sizeof(i))
    fmt.Printf("uintのサイズ: %d バイト\n", unsafe.Sizeof(u))
    fmt.Printf("uintptrのサイズ: %d バイト\n", unsafe.Sizeof(ptr))
}

使い分け:

  • int: 最も一般的。通常はこれを使う
  • uint: 負の数が絶対に必要ない場合
  • uintptr: ポインタのアドレスを整数として扱う場合(低レベルプログラミング)

エイリアス型

package main

import "fmt"

func main() {
    // byte は uint8 のエイリアス
    var b byte = 255
    var u8 uint8 = 255
    fmt.Println(b == u8)  // true
    
    // rune は int32 のエイリアス(Unicode文字用)
    var r rune = 'A'      // 65
    var i32 int32 = 65
    fmt.Println(r == i32)  // true
}

2. 浮動小数点型(Floating-point types)

小数点を持つ数を表します。IEEE 754規格に準拠しています。

float32 と float64

package main

import "fmt"

func main() {
    var f32 float32 = 3.14159
    var f64 float64 = 3.141592653589793
    
    fmt.Println(f32)  // 3.14159
    fmt.Println(f64)  // 3.141592653589793
    
    // 精度の違い
    fmt.Printf("float32: %.20f\n", f32)
    fmt.Printf("float64: %.20f\n", f64)
}

特徴と使い分け

ビット数精度用途
float3232約7桁メモリ節約が必要な場合
float6464約15-16桁一般的な用途(推奨)

特殊な値

package main

import (
    "fmt"
    "math"
)

func main() {
    // 無限大
    inf := math.Inf(1)   // 正の無限大
    negInf := math.Inf(-1)  // 負の無限大
    
    fmt.Println(inf)     // +Inf
    fmt.Println(negInf)  // -Inf
    
    // NaN(Not a Number)
    nan := math.NaN()
    fmt.Println(nan)     // NaN
    
    // チェック
    fmt.Println(math.IsInf(inf, 1))   // true
    fmt.Println(math.IsNaN(nan))      // true
}

3. 複素数型(Complex types)

実数部と虚数部を持つ数を表します。

complex64 と complex128

package main

import (
    "fmt"
    "math/cmplx"
)

func main() {
    // complex64: float32の実数部と虚数部
    var c64 complex64 = 1 + 2i
    
    // complex128: float64の実数部と虚数部
    var c128 complex128 = 3 + 4i
    
    fmt.Println(c64)   // (1+2i)
    fmt.Println(c128)  // (3+4i)
    
    // 実数部と虚数部を取得
    fmt.Println("実数部:", real(c128))  // 3
    fmt.Println("虚数部:", imag(c128))  // 4
    
    // 絶対値
    abs := cmplx.Abs(c128)
    fmt.Printf("絶対値: %.2f\n", abs)  // 5.00
}

4. 2の補数表現

符号付き整数は2の補数で表現されます。これは負の数をビットで表現する標準的な方法です。

package main

import "fmt"

func main() {
    var i int8 = -1
    
    // -1 は 2の補数で 11111111 (8ビット)
    fmt.Printf("10進数: %d\n", i)          // -1
    fmt.Printf("2進数: %08b\n", uint8(i))  // 11111111
    
    var i2 int8 = -128
    fmt.Printf("10進数: %d\n", i2)         // -128
    fmt.Printf("2進数: %08b\n", uint8(i2)) // 10000000
}

5. 型の変換

異なる数値型を混ぜて使うには、明示的な変換が必要です。

整数型同士の変換

package main

import "fmt"

func main() {
    var i32 int32 = 100
    var i64 int64
    
    // 明示的な変換が必要
    i64 = int64(i32)
    fmt.Println(i64)
    
    // これはエラー
    // i64 = i32  // cannot use i32 (type int32) as type int64
    
    // intとint32も別の型
    var i int = 42
    var i32_2 int32
    i32_2 = int32(i)  // 変換が必要
    
    fmt.Println(i32_2)
}

整数と浮動小数点の変換

package main

import "fmt"

func main() {
    // 整数 → 浮動小数点
    var i int = 42
    var f float64 = float64(i)
    fmt.Println(f)  // 42.0
    
    // 浮動小数点 → 整数(小数部分は切り捨て)
    var f2 float64 = 3.99
    var i2 int = int(f2)
    fmt.Println(i2)  // 3(小数部分が切り捨てられる)
    
    // 負の数
    var f3 float64 = -3.99
    var i3 int = int(f3)
    fmt.Println(i3)  // -3(ゼロ方向に切り捨て)
}

オーバーフロー

範囲を超えた値を変換すると、予期しない結果になります。

package main

import "fmt"

func main() {
    // int32の最大値を超える
    var i64 int64 = 2147483648  // int32の最大値+1
    var i32 int32 = int32(i64)  // オーバーフロー
    
    fmt.Println(i32)  // -2147483648(負の数になる!)
    
    // uint8の範囲を超える
    var i int = 256
    var u8 uint8 = uint8(i)  // オーバーフロー
    
    fmt.Println(u8)  // 0(256 % 256 = 0)
}

6. 実用例

例1: 適切な型の選択

package main

import "fmt"

func main() {
    // 年齢: 0〜150程度 → uint8で十分
    var age uint8 = 25
    
    // 人口: 大きな数 → int64
    var population int64 = 1_400_000_000
    
    // 金額: 小数が必要 → float64
    var price float64 = 1980.50
    
    // カウンター: 通常はint
    var count int = 0
    
    fmt.Println(age, population, price, count)
}

例2: 計算での型変換

package main

import "fmt"

func main() {
    // 整数の除算
    var a int = 10
    var b int = 3
    
    // 整数同士の除算は整数になる
    result1 := a / b
    fmt.Println(result1)  // 3(小数部分は切り捨て)
    
    // 浮動小数点にしてから除算
    result2 := float64(a) / float64(b)
    fmt.Printf("%.2f\n", result2)  // 3.33
}

例3: 範囲チェック

package main

import (
    "fmt"
    "math"
)

func SafeConvertToInt32(i int64) (int32, error) {
    if i < math.MinInt32 || i > math.MaxInt32 {
        return 0, fmt.Errorf("値が範囲外: %d", i)
    }
    return int32(i), nil
}

func main() {
    var big int64 = 10000000000
    
    if result, err := SafeConvertToInt32(big); err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Println("変換成功:", result)
    }
}

例4: 科学計算

package main

import (
    "fmt"
    "math"
)

func main() {
    // 円の面積
    radius := 5.0
    area := math.Pi * radius * radius
    fmt.Printf("半径%.1fの円の面積: %.2f\n", radius, area)
    
    // 平方根
    x := 16.0
    sqrt := math.Sqrt(x)
    fmt.Printf("%.0fの平方根: %.0f\n", x, sqrt)
    
    // 指数関数
    e := math.Exp(1)
    fmt.Printf("e: %.10f\n", e)
}

まとめ: 数値型で覚えておくべきこと

整数型の選び方

// 通常はintを使う
var count int = 100

// 負の数が不要ならuint
var age uint = 25

// 小さい範囲ならuint8/int8
var percentage uint8 = 85

// 大きな数が必要ならint64
var bigNumber int64 = 999_999_999_999

浮動小数点型の選び方

// 通常はfloat64(推奨)
var price float64 = 1980.50

// メモリ節約が重要ならfloat32
var coordinate float32 = 35.6895

型変換の原則

// 1. 明示的な変換が必要
var i32 int32 = 100
var i64 int64 = int64(i32)

// 2. 精度の損失に注意
var f float64 = 3.99
var i int = int(f)  // 3(小数部分が失われる)

// 3. オーバーフローに注意
var big int64 = 1000000000000
var small int32 = int32(big)  // オーバーフロー!

実用的なアドバイス

package main

import "fmt"

func main() {
    // 1. デフォルトはint, float64を使う
    count := 100        // int
    price := 1980.50    // float64
    
    // 2. 型を明示する場合
    var age uint8 = 25
    var pi float64 = 3.14159
    
    // 3. 定数は型なしで
    const MaxRetries = 3
    
    // 4. 計算では型を揃える
    a := 10
    b := 3
    result := float64(a) / float64(b)
    
    fmt.Println(count, price, age, pi, result)
}

数値型は、プログラミングの基礎です。適切な型を選ぶことで、メモリ効率が良く、バグの少ないコードが書けます!

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました