Golang 入門

Go 基礎データ型

データ型は、変数がどのようなデータを格納できるか、そしてそのデータに対してどのような操作を実行できるかを定義します。

本章では、Go 言語の基礎的なデータ型について、網羅的かつ平易に解説します。内部的な特性、物理的な制限、そして日常的な開発におけるベストプラクティスを探索していきましょう。

1. 整数 (Integers)

整数は、小数部分を持たない完全な数字(正数、負数、およびゼロ)です。Go 言語は非常に豊富な整数型を提供しており、主な違いはメモリ占有サイズと表現できる数値の範囲にあります。実際のニーズに合わせて適切な整数型を選択することで、メモリを節約し、データオーバーフローを防止できます。

1.1 有符号整数 (Signed Integers)

有符号整数(符号付き整数)は、正数、負数、およびゼロを表現できます。Go では以下の種類が提供されています。

  • int8: 8ビット符号付き整数(範囲:-128 ~ 127)
  • int16: 16ビット符号付き整数(範囲:-32768 ~ 32767)
  • int32: 32ビット符号付き整数(範囲:約 -21億 ~ 21億)
  • int64: 64ビット符号付き整数(範囲:極めて広大)
  • int: 最も一般的に使用されるデフォルトの整数型。サイズは実行されるコンピュータのアーキテクチャに依存します(32ビットシステムでは32ビット、64ビットシステムでは64ビット)。
package main

import "fmt"

func main() {
	var age int8 = 30                 // 年齢の格納に適した8ビット整数
	var salary int32 = 50000          // 一般的な給与に適した32ビット整数
	var population int64 = 7000000000 // 世界人口などは64ビット整数が必須
	var score int = 100               // プラットフォーム依存のデフォルトint型
	
	fmt.Println("年齢:", age)
	fmt.Println("給与:", salary)
	fmt.Println("人口:", population)
	fmt.Println("スコア:", score)
}

1.2 无符号整数 (Unsigned Integers)

无符号整数(符号なし整数)は、非負数(ゼロと正数)のみを表現できます。符号を格納するためのビットを必要としないため、正の数の上限は同ビット数の符号付き整数の2倍になります。

  • uint8: 8ビット符号なし整数(0 ~ 255)
  • uint16: 16ビット符号なし整数(0 ~ 65535)
  • uint32: 32ビット符号なし整数(0 ~ 約42億)
  • uint64: 64ビット符号なし整数(極めて広大)
  • uint: プラットフォーム依存の符号なし整数型(32ビットまたは64ビット)。
package main

import "fmt"

func main() {
	var positiveAge uint8 = 30        // 年齢は負になり得ないため、uint8は合理的
	var fileSize uint32 = 2000000     // ファイルサイズ (バイト)
	var processID uint = 12345        // プロセスID
	
	fmt.Println("正の年齢:", positiveAge)
	fmt.Println("ファイルサイズ:", fileSize)
	fmt.Println("プロセスID:", processID)
}

1.3 rune と byte:2つの特殊なエイリアス

Go 言語は、文字とロウデータ(生のデータ)を処理するために、2つの非常に重要な型エイリアスを提供しています。

  • rune: int32 のエイリアスです。Go において rune は Unicode コードポイント (Code Point) を表現するために専門的に使用されます。中国語、日本語、絵文字などの非 ASCII 文字を含むテキストを処理する場合、rune がメインとなります。
  • byte: uint8 のエイリアスです。通常、生のバイナリデータ(シングルバイト)を表現するために使用されます。
package main

import "fmt"

func main() {
	var unicodeValue rune = '中' // 注意:文字はシングルクォートで囲む
	var dataByte byte = 255
	
	fmt.Println("文字 '中' の Unicode コードポイント数値:", unicodeValue) // 出力: 20013
	fmt.Println("データバイト:", dataByte) 
}

1.4 整数オーバーフローとアンダーフロー

極めて重要なポイント:整数の値が収容限界を超えた場合、Go 言語は通常、エラーやクラッシュ(Panic)を発生させず、ラップアラウンド (Wrap around) が発生します。

  • オーバーフロー (Overflow): 例えば int8 の最大値は 127 です。127 に 1 を加えると、128 になるのではなく、最小値に「回り込んで」 -128 になります。
  • アンダーフロー (Underflow): 同様に、-128 から 1 を引くと 127 になります。
package main

import "fmt"

func main() {
	var maxInt8 int8 = 127
	var minInt8 int8 = -128
	
	overflow := maxInt8 + 1
	underflow := minInt8 - 1
	
	fmt.Println("オーバーフロー結果:", overflow)   // 出力: -128
	fmt.Println("アンダーフロー結果:", underflow)  // 出力: 127
}

       ヒント: 実際の開発で数値の範囲が不確かな場合は、デフォルトの int を使用するのが最も安全で一般的です。

2. 浮動小数点数

浮動小数点数は、小数部分を持つ数字、または極端に巨大・微小な数字を表現するために使用されます。Go は2つの精度の浮動小数点型を提供しています。

  • float32: 32ビット浮動小数点数(単精度)。メモリ消費は小さいですが、精度は低めです(小数点以下約7桁まで正確)。
  • float64: 64ビット浮動小数点数(倍精度)。精度が高く(小数点以下約15桁まで正確)、Go 言語における浮動小数点数のデフォルトの選択肢です。
package main

import "fmt"

func main() {
	var pi32 float32 = 3.14159
	var pi64 float64 = 3.14159265359
	
	fmt.Println("Float32 Pi:", pi32)
	fmt.Println("Float64 Pi:", pi64)
}

2.1 致命的な浮動小数点精度の罠

すべてのプログラミング言語の浮動小数点は、低レイヤーで IEEE 754 標準を採用しています。これにより、一部の小数はコンピュータ内で正確に表現できません。これが、古典的な「精度損失」問題を引き起こします。

package main

import "fmt"

func main() {
	var a float64 = 0.1
	var b float64 = 0.2
	var c float64 = a + b
	
	fmt.Println("0.1 + 0.2 の和:", c) // 出力警告: 0.30000000000000004
	
	if c == 0.3 {
		fmt.Println("等しい")
	} else {
		fmt.Println("等しくない!") // プログラムはここを通ります!浮動小数点を == で直接比較してはいけません
	}
}

浮動小数点数を正しく比較する方法:

2つの浮動小数点数が等しいかどうかを判断するために、直接 == を使ってはいけません。それらの差の絶対値が、極めて小さい閾値(イプシロン)未満であるかどうかを比較する必要があります。

package main

import (
	"fmt"
	"math"
)

func main() {
	var c float64 = 0.1 + 0.2
	var epsilon float64 = 1e-9 // 非常に小さな許容閾値を定義 (0.000000001)
	
    // math.Abs は絶対値を計算します:|c - 0.3| < epsilon
	if math.Abs(c-0.3) < epsilon {
		fmt.Println("これらは近似的に等しく、同一とみなせます") 
	}
}

2.2 特殊な浮動小数点数値

浮動小数点数は、組み込みの math パッケージを通じて取得できるいくつかの特殊な数学的概念も表現できます。

  • NaN (Not a Number): 「数字ではない」ことを示します(例:0 ÷ 0 の結果)。注意:NaN は自分自身とも一致しません。
  • Infinity (無限大): 正の無限大 (+Inf) と負の無限大 (-Inf) があります。

3. 布爾値 (Booleans)

布爾値(ブール値)は論理的な「真」または「偽」を表し、Go では bool 型で表現されます。true(真)と false(偽)の2つの有効な値のみを持ちます。

package main

import "fmt"

func main() {
	var isGoFun bool = true
	var isFishTasty bool = false
	
	fmt.Println("Goは楽しいですか?", isGoFun)
}

ブール値は、プログラムの実行フロー(if 条件判断やループなど)を制御する絶対的な中核です。

package main

import "fmt"

func main() {
	age := 20
	isStudent := true
	
    // && は論理「積」(AND)、! は論理「否定」(NOT) を表します
	if age >= 18 && !isStudent {
		fmt.Println("フルタイム勤務の条件を満たしています")
	} else {
		fmt.Println("条件を満たしていません") // isStudentがtrueなので否定するとfalseになり、全体が不成立
	}
}

4. 文字列 (Strings)

文字列は文字の集合です。Go 言語において、文字列に関する極めて重要な核心概念があります:文字列はイミュータブル (Immutable) です。これは、一度作成された文字列は、メモリ上のデータを絶対に修正できないことを意味します。

package main

import "fmt"

func main() {
	var message string = "Hello, Go!"
	var emptyString string = "" // 空の文字列
	fmt.Println(message)
}

4.1 一般的な文字列操作

  • 結合 (Concatenation): + 演算子を使用します。
  • 長さの取得: 組み込みの len() 関数を使用します。(注意:これはバイト数を返します。純粋な英数字の文字列なら問題ありませんが、中国語や日本語を含む場合は注意が必要です)。
  • 部分文字列の抽出 (Slicing): [開始インデックス:終了インデックス] の構文を使用して文字列の一部を抽出します。
package main

import "fmt"

func main() {
	message := "Hello, Go!"
	
	// 結合
	newMessage := message + " ようこそ!"
	
	// 長さの取得
	length := len(message) // 長さは 10
	
	// スライシングで部分文字列を取得 (インデックス 0 から 4、つまり最初の5文字)
	substring := message[0:5]
	fmt.Println("抽出された部分文字列:", substring) // 出力: Hello
}

4.2 文字列のイミュータブル性の証明

文字列内の特定の文字を直接修正しようとすると、Go コンパイラは容赦なくエラーを出します。

package main

import "fmt"

func main() {
	message := "Hello"
	// message[0] = 'J' // 致命的エラー!文字列はイミュータブルなのでコンパイラが阻止します
	
	// 正しい方法は、スライシングと結合を利用して全く新しい文字列を生成することです
	newMessage := "J" + message[1:] 
	fmt.Println(newMessage) // 出力: Jello
}

4.3 マルチバイト文字列の反復処理 (Rune の威力)

日本語を含む文字列を通常のループとインデックスで反復処理すると、文字化けしたバイトが得られます。多言語テキストを正しく反復処理するには、必ず for...range ループを使用する必要があります。これにより、文字列が一つ一つの rune(Unicode コードポイント)に自動的にデコードされます。

package main

import "fmt"

func main() {
	message := "こんにちは、世界!" 
	
    // index はバイトインデックス、runeValue はデコード後の Unicode 文字
	for index, runeValue := range message {
        // %c は文字そのものをプリント、%U は標準の Unicode フォーマットコードをプリント
		fmt.Printf("インデックス位置: %d, 文字: %c, Unicode: %U\n", index, runeValue, runeValue)
	}
}

4.4 原生文字列リテラル (Raw Strings)

複数行のテキスト形式をそのまま保持したい場合や、文字列内に大量のエスケープ記号(バックスラッシュ \、ダブルクォート " など)が含まれる場合は、バッククォート ` で文字列を囲みます。これは「原生文字列(ロー文字列)」と呼ばれ、あらゆるエスケープ文字を無視します。

package main

import "fmt"

func main() {
	rawString := `これは原生文字列です。
自由に複数行にわたることができます。
\n や \t などのエスケープ記号は無視され、
入力した通りにプリントされます。
{"json": "JSONなどを直接書く際もダブルクォートのエスケープが不要です"}` 
	
	fmt.Println(rawString)
}