ただの技術メモ

個人備忘録

string型の変数をfor文で回す

競プロで文字列を1文字ずつ扱う場面があって、改めてまとめてみる。

byte型とは

まずbyte型について。

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint

そもそもバイト=8ビットで、10進数だと0~255(28-1)までを表現できる。Goではbyte型は8ビットの符号なし整数のエイリアスになっており、整数として演算できる。

文字列をfor文で回す

文字列にインデックスでアクセスするとbyte型が取得でき、文字列をfor rangeで回すとrangeの第2返り値ではrune型が取得できる。

s := "string"
for i, r := range s {
    fmt.Println(s[i]) // byte
    fmt.Println(r)    // rune
    // 出力はどちらも 115 116 114 105 110 103

    fmt.Println(string(s[i])) // string
    fmt.Println(string(r))    // string
    // 出力はどちらも s t r i n g
}
s := "string"
for i := 0; i < len(s); i++ {
        fmt.Println(s[i]) // 115 116 114 105 110 103
        fmt.Println(string(s[i])) // s t r i n g
}

このように普段アルファベットをfor文で回す際は、特に何も注意することなく、1文字ずつ処理することができる。

Goでは符号化方式としてUTF-8を採用している。UTF-8ASCII互換の符号化方式で、ASCIIで定義されているアルファベットなどは1バイトで表現される。 そのため1バイト(Goのbyte型で0~255)で表現できるASCII文字は問題なく扱えるが、文字によっては1バイトで表現できないため、1バイトごとに出力しても意図する値を出力できない可能性がある。

例えば以下のように、日本語の「あ」をbyte型で出力すると、以下のように計3バイトで出力される。

ちなみに組み込み関数のlen()は対象文字列のバイトサイズを取得する。

s := "あ"
for i := 0; i < len(s); i++ {
    fmt.Println(s[i])         // 227 129 130
    fmt.Println(string(s[i])) // ã ? ?
}

よってGoでは1バイトで表現できない文字列でも1文字ずつ扱うために、コードポイント単位で文字を扱うruneが用意されている。

rune型は対象の文字列のコードポイント(16進数)を10進数に変換したものが割り当てられる。

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

コードポイントは最大で21ビットの数値(16進数で5~6桁)なので、rune型は32ビットの整数のエイリアスになっており、整数として演算できる。

// runeは文字をシングルクオートで囲って書く
// 実体はint32なので計算もできる
fmt.Println('あ')       // 12354
fmt.Println('あ' * 2)   // 24708
fmt.Println('い' - 'あ') // 2

rune型を利用することで、日本語のような1バイト以上で表される文字列でも1文字ずつ(コードポイント単位で)扱うことができる。

例えば「あ」をrune型で出力する場合、「あ」のコードポイントは「U+3042」なので、これを10進数に変換した「12354」が出力される。16進数で出力したい時はPrintf%xを使う。

s := "あ"
for _, r := range s {
    fmt.Println(r) // 12354
    fmt.Printf("%x", r) // 3042
}

rune型をstring型に直すにはstring()を使う。

s := "あ"
for _, r := range s {
    fmt.Println(string(r)) // あ
}

スライスを使うとこんな感じ。

s := "あ"
// コードポイントのスライス
fmt.Println([]rune(s)) // [12354]
// UTF-8で符号化したバイトスライス
fmt.Println([]byte(s)) // [227 129 130]

ちなみにslicingするとbyteでは返ってこないので注意。

s := "ABCD"
fmt.Println(s[2:3]) // C
fmt.Println(s[2]) // 67