1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. LearnTheBasics
  5. データ型(Data Types) - Golang learning step 1-3

データ型(Data Types) - Golang learning step 1-3

  • 公開日
  • カテゴリ:LearnTheBasics
  • タグ:Golang,roadmap.sh,学習メモ
データ型(Data Types) - Golang learning step 1-3

roadmap.sh > Go > Learn the Basics > Data Types の学習を進めていきます。

※ 学習メモとしての記録ですが、反復学習の際の道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. Data Types(データ型)
  3. 基本型
  4. 数値型(int, ..., uint, ..., float32/64, complex64/128)
    1. 整数型(int)
    2. 符号なし整数型(uint)
    3. 浮動小数点数型(float32, float64)
    4. 複素数型(complex64, complex128)
  5. 文字型(string)
  6. ブール型(bool)
  7. バイト型(byte)
    1. 例1: 文字列の処理
    2. 例2: バイナリデータの操作
  8. ランタイム用ポインタ型(rune)
    1. rune 型の特徴
    2. rune 型が使われるシーン
    3. rune 型を使うシーン
    4. rune と byte の違い
  9. データ型まとめ

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

Data Types(データ型)

Go は静的型付けプログラミング言語のため、各変数は最初に型が定義され、その型の値のみを保持できます。Go の型には 2 つのカテゴリーの型があります。基本型と複合型です。

基本型は、単一の値を表現する最小単位のデータ型です。これらは直接メモリに格納され、単純な操作で扱えます。数値、文字列、真偽値などの基本的なデータを表現するのに使用されます。

複合型は、基本型や他の複合型を組み合わせて作られる、より複雑なデータ構造を表現する型です。これらは複数の値や異なる型の値を組み合わせて使用でき、より高度な抽象化や複雑なデータモデリングを可能にします。 この分類により、データの扱い方やメモリ管理の違い、操作の複雑さの違いを理解しやすくなり、適切なデータ型の選択や効率的なプログラミングに役立ちます。

このセクションで扱うデータ型は、基本型になります。複合型は別セクションで学習が用意されています。(Array, Slices, Maps, Structs)

基本型

基本型は、Go 言語における基本的なデータ型のことを指します。これらは最も単純なデータ型であり、他の複雑な型の構成要素となるものです。単一の値を表現する最小単位のデータ型であり、数値、文字列、真偽値などの基本的なデータを表現するのに使用されます。

数値型(int, ..., uint, ..., float32/64, complex64/128)

  • 整数: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
  • 浮動小数点数: float32, float64
  • 複素数: complex64, complex128

整数型(int)

整数型 int, int8, int16, int32, int64 の違いは、主に格納できる整数の範囲とメモリ使用量にあります。それぞれの型が保持できる数値の範囲や用途が異なります。

サイズ 範囲 用途
int 実装依存(32ビットまたは64ビット) 32ビット: -2,147,483,648 から 2,147,483,647
64ビット: -9,223,372,036,854,775,808 から 9,223,372,036,854,775,807
一般的な整数型。特定のビット幅を必要としない場合に使用。
int8 8ビット -128 から 127 小さな範囲の整数を扱う場合や、メモリ節約を重視する場合。
int16 16ビット -32,768 から 32,767 int8 より広い範囲が必要だが、int32 ほどメモリを使いたくない場合。
int32 32ビット -2,147,483,648 から 2,147,483,647 32ビットの固定サイズが必要な場合や、互換性を意識したい場合。
int64 64ビット -9,223,372,036,854,775,808 から 9,223,372,036,854,775,807 非常に大きな範囲の整数を扱いたい場合。

選択基準

  • 性能: int はそのプラットフォームで効率的なサイズ(32ビットまたは64ビット)で動作するため、特別な理由がない限り、一般的には int を使用することが推奨されます。
  • メモリ: 小さい範囲の整数を扱う場合、int8 や int16 を使うことでメモリ使用量を節約できます。
  • 互換性: 他のシステムやAPIとの互換性が必要な場合、明示的に int32 や int64 を使うことがあります。

符号なし整数型(uint)

符号なし整数型として、uint, uint8, uint16, uint32, uint64, および特殊な型である uintptr が存在します。

これらはすべて、正の整数のみを扱う型で、符号付きの int 型とは異なり、負の値を表すことができません。それぞれの型の違いは、格納できる数値の範囲と使用されるメモリのサイズにあります。

サイズ 範囲 用途
uint 実装依存(32ビットまたは64ビット) 32ビット: 0 から 4,294,967,295
64ビット: 0 から 18,446,744,073,709,551,615
符号なし整数型。特定のビット幅を気にせず使いたい場合に使用。
uint8 8ビット 0 から 255 小さい数値やバイトデータを扱う際に使用。`byte` と同じ。
uint16 16ビット 0 から 65,535 小さな数値を扱いたい場合や、ID、カウンタ管理に使用。
uint32 32ビット 0 から 4,294,967,295 32ビットの範囲内で大きな数値を扱いたい場合に使用。
uint64 64ビット 0 から 18,446,744,073,709,551,615 非常に大きな数値や、精度が必要な計算に使用。
uintptr 実装依存(32ビットまたは64ビット) 環境に依存 ポインタ演算やメモリアドレスを扱うために使用。

uintptr は、低レベルなシステムプログラミングや、ポインタやメモリアドレスの操作が必要な場合に使用されますが、一般的なアプリケーション開発では基本的に使用しません。uintptr は、ポインタのアドレスを整数として扱うため、使用を誤るとメモリの不正アクセスやプログラムのクラッシュにつながる可能性があります。そのため、慎重に使う必要があります。また、uintptr に変換されたポインタは、Go のガベージコレクタから追跡されなくなります。メモリ管理においてこの点を理解していないと、メモリリークやデータの破損が発生する恐れがあります。

浮動小数点数型(float32, float64)

浮動小数点数型 float32 と float64 の違いは、主に精度とメモリ使用量に関係しています。それぞれの型が扱える数値の範囲や精度に違いがあります。

サイズ 精度 範囲 用途
float32 32ビット(4バイト) 約7桁の有効数字 最小: 約 1.4e-45
最大: 約 3.4e+38
精度をそこまで必要としない場合や、メモリ節約が重要な場合。
float64 64ビット(8バイト) 約15桁の有効数字 最小: 約 5.0e-324
最大: 約 1.7e+308
高い精度や大きな範囲の数値が必要な場合。

浮動小数点数型には float が存在しない

整数型には int がありますが、浮動小数点数型には float が存在していません。

整数型については、プラットフォーム依存(32bit or 64bit)の最適なサイズを提供するために int が存在します。

一方で浮動小数点数型については、プログラマーは常に使用する浮動小数点数の精度を意識する必要があり、これにより誤って低精度を使用してしまうリスクを減らすために float は設けられていません。

float32 と float64 のどちらかを明示的に選択することで、プログラマーが計算の精度やメモリ使用量について意識的に選択できるようにしています。例えば、低精度で十分な場面では float32 を使い、精度が必要な場合には float64 を使う、という形です。

なお、型宣言をせずに浮動小数点数型を変数に代入した場合の推論は、float64 になります。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 型宣言をせずに浮動小数点数を代入
	x := 3.14

	// 変数 x の型を確認
	fmt.Printf("xの値: %v\n", x)
	fmt.Printf("xの型: %v\n", reflect.TypeOf(x))

	// 明示的に float32 を指定した場合との比較
	y := float32(3.14)
	fmt.Printf("yの値: %v\n", y)
	fmt.Printf("yの型: %v\n", reflect.TypeOf(y))
}

実行結果:

xの値: 3.14
xの型: float64
yの値: 3.14
yの型: float32

浮動小数点数型の精度

ここで出てきている「精度」とは、数値全体で保持できる有効数字の桁数を指しています。Go における float32 では約 7 桁、float64 では約 15 桁の有効数字を扱えるという意味です。

つまり、計算が正しい、計算を誤るといった意味での「正確さ」とは異なる意図ですので、混同しないように注意が必要です。

ただし例えば、float32 では精度が低いため、非常に細かい数値を扱うと丸め誤差が発生しやすく、これが結果的に正確さに影響を与えることはあります。

例として、以下のプログラムを実行してみます。総桁数 9 桁の数字をそれぞれ float32 と float64 に代入し、出力しています。

package main

import "fmt"

func main() {
	var value32 float32
	var value64 float64

	value32 = 1234.56789
	value64 = 1234.56789

	fmt.Printf("float32: %f\n", value32)
	fmt.Printf("float64: %f\n", value64)
}

出力結果は以下になります。

float32: 1234.567871
float64: 1234.567890

float32 の方が、1234.56789 という数字を保持できていないことがわかります。これが精度の違いです。浮動小数点数の形式では、すべての数を正確に表現できるわけではなく、特に float32 のようにビット数が少ない型では、丸め誤差が発生します。

float32 型は約 7 桁の有効数字を扱えるため、1234.56789 の場合、小数点以下がすべて正確に格納されるわけではありません。このため、出力が 1234.567871 のようにわずかに異なった値になります。

一方で float64 は 64 ビットで表現され、有効数字が約 15 桁と float32 よりも多くの桁を正確に保持できます。そのため、float64 ではより精密に数値を表現でき、1234.56789 もほぼ正確に格納されます(出力結果が 1234.567890 )。

選択基準

  • float32: メモリ節約が重要であり、少しの誤差が許容される場合に適しています。
  • float64: 精度が重要であり、誤差を最小限に抑えたい場合に適しています。

複素数型(complex64, complex128)

複素数型 complex64 と complex128 は、複素数を扱うためのデータ型です。それぞれ、64 ビットと 128 ビットの複素数を表しています。複素数は、実数部と虚数部の 2 つの成分を持つ数で、これらの型では両方の成分を浮動小数点数で表現します。

サイズ 構成 用途
complex64 64ビット(8バイト) 実数部と虚数部がそれぞれ float32 で表現 メモリを節約したい場合や、低精度の計算が許容される場合に使用
complex128 128ビット(16バイト) 実数部と虚数部がそれぞれ float64 で表現 高精度が必要な計算や、大きな数値を扱う場合に使用

Go では、複素数を扱うために複素数リテラルや組み込み関数を使用できます。

  • 複素数リテラル: a + bi の形式で表現します(虚数部には i を使います)。
  • complex() 関数: 実数部と虚数部から複素数を作るために使用します。
package main

import (
    "fmt"
)

func main() {
    // complex64 の複素数
    var c1 complex64 = complex(1.2, 3.4) // 実数部1.2, 虚数部3.4
    fmt.Printf("c1: %v (complex64)\n", c1)

    // complex128 の複素数
    var c2 complex128 = complex(1.2, 3.4) // 実数部1.2, 虚数部3.4
    fmt.Printf("c2: %v (complex128)\n", c2)

    // 実数部と虚数部を分離して取得
    fmt.Printf("実数部: %f, 虚数部: %f\n", real(c2), imag(c2))
}

出力結果:

c1: (1.2+3.4i) (complex64)
c2: (1.2+3.4i) (complex128)
実数部: 1.200000, 虚数部: 3.400000

complex128 は complex64 よりも高精度です。complex128 は約 15-17 桁の精度、complex64 は約 6-9 桁の精度になります。(complex128 は complex64 の 2 倍のメモリを使用)

package main

import (
	"fmt"
	"math/cmplx"
)

func main() {
	// complex64 の使用例
	var c64 complex64 = 3 + 4i
	fmt.Printf("complex64: %v\n", c64)
	fmt.Printf("実部: %f, 虚部: %f\n", real(c64), imag(c64))

	// complex128 の使用例
	var c128 complex128 = 3 + 4i
	fmt.Printf("complex128: %v\n", c128)
	fmt.Printf("実部: %f, 虚部: %f\n", real(c128), imag(c128))

	// 複素数の演算
	fmt.Printf("絶対値: %f\n", cmplx.Abs(c128))
	fmt.Printf("指数関数: %v\n", cmplx.Exp(c128))

	// 精度の違いを示す例
	c64precise := complex64(0.1 + 0.2i)
	c128precise := complex128(0.1 + 0.2i)
	fmt.Printf("complex64精度: %v\n", c64precise*c64precise)
	fmt.Printf("complex128精度: %v\n", c128precise*c128precise)
}

出力結果:

complex64: (3+4i)
実部: 3.000000, 虚部: 4.000000
complex128: (3+4i)
実部: 3.000000, 虚部: 4.000000
絶対値: 5.000000
指数関数: (-13.128783081462158-15.200784463067954i)
complex64精度: (-0.030000001+0.040000003i)
complex128精度: (-0.030000000000000006+0.04000000000000001i)

高精度が必要な場合は complex128 を使用し、メモリ効率や処理速度が重要な場合は complex64 を検討する。デフォルトでは complex128 を使用し、必要に応じて complex64 に変更することが一般的とされています。

複素数とは?

複素数は、実部と虚部から構成される数です。一般的に a + bi の形式で表されます。

  • a は実部(実数部分)
  • b は虚部(虚数部分)
  • i は虚数単位(i² = -1)

複素数は数学や工学のさまざまな分野で使われる強力なツールで、実数では解決できない問題を扱う際に役立ちます。特に、波動現象や振動、電気回路、信号処理、量子物理学など、物理や工学の分野で幅広く応用されています。複素数は、実数ではうまく扱えない負の数の平方根を扱うために導入されましたが、それだけでなく、現実世界のさまざまな問題に対して役立つ特性を持っています。

使用シーン:

  • 信号処理
    • 音声信号や電気信号の解析
  • 電気工学
    • 交流回路の解析
  • 量子力学
    • 波動関数の表現
  • コンピュータグラフィックス
    • フラクタル図形の生成
  • 制御理論
    • システムの周波数応答の解析
  • 数値解析
    • 複素関数の積分や微分

文字型(string)

  • 文字列(string): UTF-8 エンコーディングされた文字列データ。

ブール型(bool)

  • bool: 真偽値を表す型で、値は true か false。

バイト型(byte)

byte 型は、Go において バイトデータ を扱うための特別な型で、uint8 の別名として定義されています。具体的には、byte 型は 8ビットの符号なし整数(0 から 255)を表しますが、その主な用途は 文字列やバイナリデータ を扱うときに便利です。

byte 型は主に次のような場面で使用されます:

  • 文字列をバイト単位で処理したいとき。
  • バイナリファイルの読み書きを行うとき。
  • ネットワーク通信でバイト列をやりとりするとき。
  • 暗号処理やハッシュ計算においてデータをバイト列で扱うとき。

byte 型は基本的に、データを低レベルで操作する際に重要な役割を果たします。

例1: 文字列の処理

Go では文字列(string)は UTF-8 でエンコードされたバイトのスライス([]byte)として内部的に扱われます。文字列をバイト単位で操作したいときに、byte 型が使われます。

package main

import "fmt"

func main() {
    str := "Hello"
    bytes := []byte(str) // 文字列をバイトスライスに変換
    fmt.Println(bytes)   // [72 101 108 108 111]
}

この例では、"Hello" という文字列を []byte に変換しています。各文字がそのASCII値(バイト値)に変換され、結果として [72 101 108 108 111] というスライスになります。

例2: バイナリデータの操作

ファイルやネットワーク通信でやりとりするデータは、一般的に バイナリ形式 です。バイナリ形式とは、テキストとは異なり、人間が直接読むことのできないデータ形式を指します。このようなバイナリデータを操作する際に、byte 型がよく使われます。たとえば、画像や音声ファイルなどの バイナリ形式のファイル を読み書きするとき、データは []byte として処理されます。

package main

import (
    "fmt"
    "os"
)

func main() {
    // ファイルからバイナリデータを読み込む
    data, err := os.ReadFile("/path/to/sample.png")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    // バイトのスライスとして出力
    fmt.Println(data) // [137 80 78 71 13 10 26 10 0 0 0 13 73 ... 68 174 66 96 130]
}

この例では、画像ファイル example.png をバイナリデータとして読み込み、そのデータを []byte として取得しています。[137 80 78 71] は PNG ファイル特有のヘッダで、ファイルが PNG 形式であることを示しています。

ランタイム用ポインタ型(rune)

rune 型は、Go における Unicode コードポイントを表現するための型です。Go では、rune は int32 のエイリアスであり、Unicode 文字 1 つを扱うために使用されます。これにより、Go では国際化された文字(日本語や絵文字を含む)を簡単に扱うことができます。

rune 型の特徴

  • rune は int32 の別名
    • rune は Unicode 文字を表すために使われる型で、32ビット(4バイト)の符号付き整数型です。Unicode コードポイントを直接表現するため、ASCII 文字から多バイトの文字(例えば、日本語の漢字や絵文字)まで表現できます。
  • 1 文字を表す
    • Go の文字列は UTF-8 エンコーディングで扱われ、複数バイトから成る文字を含むことができます。rune はその文字列の中の**1文字(1つのUnicodeコードポイント)**を表現します。

rune 型が使われるシーン

  • 文字列から 1 文字ずつ処理したいとき
  • Unicode 文字を操作したいとき
  • 複数バイトで構成される文字(非ASCII文字)を扱うとき

特に、日本語、中国語、韓国語などの多バイト文字や、絵文字など非 ASCII 文字を正しく扱うためには、rune を使用することが適しています。

package main

import (
    "fmt"
)

func main() {
    str := "こんにちは"
    
    // 文字列を1文字ずつ(rune単位)で処理
    for i, r := range str {
        fmt.Printf("%d: %c (Unicode: %U)\n", i, r, r)
    }
}

出力結果:

0: こ (Unicode: U+3053)
3: ん (Unicode: U+3093)
6: に (Unicode: U+306B)
9: ち (Unicode: U+3061)
12: は (Unicode: U+306F)

rune 型を使うシーン

  1. 文字列の1文字ずつを操作したい場合
    • Go の文字列はバイトのシーケンスであり、特に UTF-8 では 1 文字が 1 バイトでない場合があります。そのため、文字列をバイト単位で扱うと、文字化けや予期しない挙動が発生する可能性があります。rune を使うと、Unicodeのコードポイントを意識しながら文字単位で処理できるため、多バイト文字も正しく扱うことができます。
  2. 文字コードの変換や文字に対する操作
    • Unicode コードポイント(rune)に対して、特定の操作を行いたい場合に使います。例えば、文字を数値として扱い、何らかの計算や変換を行うといった操作に適しています。
  3. 日本語や絵文字などの非 ASCII 文字を含む文字列を操作する場合
    • 日本語のように 1 文字が複数バイトで構成される文字を含む場合、rune を使うことで 1 文字単位で正しく処理できます。絵文字や記号なども同様です。

rune と byte の違い

  • rune は、1 つの Unicode 文字(コードポイント)を表すために使用され、32ビット(4バイト)で表現されます。1 文字が 1 つのruneとして表される。
  • byte は、1 バイト(8ビット)のデータを表すために使われ、文字列全体のバイト配列として扱われる。UTF-8 エンコードされた文字列の場合、1 文字が複数の byte に分割されることがあります。

データ型まとめ

Goは静的型付け言語であり、変数は定義時に型が決定されます。

Goの基本データ型は以下のとおりです。

  1. 数値型
    • 整数: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
    • 浮動小数点数: float32, float64
    • 複素数: complex64, complex128
  2. 文字列型: string
  3. ブール型: bool
  4. 特殊な型
    • byte: uint8 のエイリアス
    • rune: int32 のエイリアス、Unicodeコードポイントを表す

主な選択基準:

  • 整数は通常 int を使用
  • 浮動小数点数は精度に応じて float32 またはfloat64を選択
  • 文字処理には rune を使用(特に多バイト文字の場合)

注意点:

  • float 型は存在しない(float32またはfloat64を明示的に使用)
  • uintptr は低レベルプログラミング用途に限定

[next] Step 1-4: Arrays, Slices, Maps, Structs, make()

[Prev] Step 1-2: 変数と宣言(Variables and declaration)

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級