Go言語の入門書には必ずstring・byte・runeが出てきます。
サラッと書かれていることが多く、そんなものかと分かった気になって軽く流してしまいがちなのですが、いざプログラムを組み始めると、それぞれを相互変換する場面が頻発して混乱してしまいます。
そんな時は、何となく型変換してうまく動いたからOKとしがちなのですが、もう少し意味を分かった上で型変換できるようにしたいので、ここにstring・byte・runeの使い方をまとめようと思います。
byte とは
byte
とはuint8
の別名です。
プログラムではバイナリーデータを扱う場面が多いのですが、その際、バイナリーデータはuint8
の配列に格納されます。
しかし、unit8
と書いてしまうとuint16
・uint32
...など、他の型と並列に見えてしまうので、バイナリーデータだと分かりやすくするために、バイナリーデータを扱う際はbyte
を使います。
string とは
stringとは、byte
の配列の文字列領域と、その領域配列の長さを持つ言語組み込みの特殊な構造体です。
type StringHeader struct { Data uintptr Len int }
特に注目すべきは、一度作られたstringの内容は一生涯固定で後から変更できない点で、それは言語で保証されています。
例えば途中の1文字を変更するといったようなことはできません。下記のような操作はエラーになります。
a := "ABC" // NG a[0] = 'a'
stringの演算は必ず新しいstringを生成します。下記例ではa
の領域は再利用されず、123
・ABC
・123ABC
の3つの文字列がメモリに生成されます。
a := "123" b := "ABC" a = a + b
スライスのようにapend()
で後に追加するようなことはできません。下記のような操作はエラーになります。
a := "123" b := "ABC" // NG a = append(a, b)
このことから分かるように、stringの演算はあまり効率よくありません。
rune とは
stringに格納される文字列の文字コードはUTF-8です。
UTF-8は文字毎に長さが違う可変長コードです。
stringの長さはbyte
配列の長さなので、実際の文字の数と必ずしも一致しません。
a := "ABCあいう" fmt.Println(len(a)) // 12
一方、全ての文字を4byteの固定長で表す、UTF-32という文字コードがあります。
プログラム中において、stringから文字数を求めたり、特定の位置の文字を取り出すといった、文字列から個々の文字を参照する需要はよくあります。
そういった需要に応じるため、Go言語では、stringで個々の文字を参照する時は、一旦UTF-8の文字列をUTF-32の文字列に変換して、UTF-32文字で扱います。
そして、そのUTF-32文字をGo言語ではrune
と呼びます。
例えば、stringの文字数は下記の方法で数えることができます。
a := "ABCあいう" r : = []rune(a) fmt.Println(len(r)) // 6
UTF-8の文字列をUTF-32の文字列に変換するという作業は、stringをrune
のスライスに型変換することによりGo言語が行ってくれます。
変換した結果はrune
のスライスに格納されます。
また、range
でstringを呼び出した時も変換が行われ、rune
のスライスが裏で自動で生成されます。
a := "ABCあいう" for _, r := range a { fmt.Printf("%c", r) // ABCあいう }
上記の例では、range
により、stringからrune
のスライスが生成され、そこから1文字づつrune
を取り出して表示するということを行っています。
また、rune
はstringではありません。rune
を文字として表示するには%c
を使います。
相互型変換
通常、ある型のスライスから別の型のスライスへの型変換は行なえないのですが、stringからbyte・runeのスライスと、byte・runeのスライスからstringへの型変換は言語で特別に定義されています。
stringからbyte
stringから[]byte
への型変換は、stringのデータがそのまま[]byte
にコピーされます。
データはコピーなので、stringから[]byte
に型変換した場合、[]byte
の値を変更しても、元のstringの値はそのままです。
s := "ABC" b := []byte(s) b[0] = 'a' fmt.Printf("%s\n", b) // aBC fmt.Printf("%s\n", s) // ABC
byteからstring
[]byte
からstringへの型変換は、[]byte
のデータを元に、新たなstringが生成されます。
ここでも、stringのデータは[]byte
からコピーしたものなので、変換後に[]byte
の値を変更しても、生成されたstringの値はそのままです。
b := []byte{'A', 'B', 'C'} s := string(b) b[0] = 'a' fmt.Printf("%s\n", b) // aBC fmt.Printf("%s\n", s) // ABC
[]byte
に格納されている文字列を表示するには、一旦stringに型変換するか、%s
で[]byte
を文字列として表示します。
b := []byte{'A', 'B', 'C'} s := string(b) fmt.Printf("%s\n", s) // ABC fmt.Printf("%s\n", b) // ABC
stringからrune
混乱しやすいのですが、stringは文字列、rune
は1文字を表します。
なので、stringに対応する概念はrune
ではなく[]rune
になります。
前述の通り、stringを[]rune
に型変換すると、stringをUTF-32に変換したrune
のスライスが新たに生成されます。
// rune slice rs := []rune("ABC") for _, r := range rs { fmt.Printf("%c", r) // ABC }
1文字のrune
への変換は、1文字のUTF-8文字の型変換で行います。
UTF-8の1文字がUTF-32の1文字に変換されてrune
になります。
ダブルクォーテーションでくくられた文字列はstringなので1文字のrune
への変換は行なえません。
また、シングルクォーテーションでくくられた文字でも、2文字以上だとstringになるので、1文字のrune
への変換は行なえません。
// OK r := rune('A') // NG r = rune("A") // NG r = rune('AB')
runeからstring
[]rune
を型変換でstringに変換できます。
UTF-32の文字列がUTF-8の文字列に変換され、そのデータで新たにstringが生成されます。
r := []rune{'A', 'B', 'C'} s := string(r) fmt.Println(s) // ABC
1文字のrune
をstringに型変換することもできます。その場合、1文字のstringが新たに生成されます。
r := rune('A') s := string(r) fmt.Println(s) // A
便利だけとコストがかかる
stringからの型変換および、stringへの型変換は、必ず新しい配列の生成とその配列への値のコピーが行われるのでコストがかかります。
また、stringの演算は、必ず新しいstringが生成されるので、ここでも新しい配列の生成とその配列への値のコピーが行われるのでコストがかかります。
通常そういったコストは考えなくていいのですが、コストが無視できなくなってきた場合は、文字列をstringではなく[]byte
で扱うことを考えた始めた方がいいかも知れません。
文字列を[]byte
で扱い始めると、今までstringが裏でやってくれていた面倒な操作を自分で行う必要がでてきます。よくある操作は下記のパッケージに関数で用意されていますので、自分でやろうとする前に軽く目を通しておくといいかも知れません。